소스 검색

Merge branch 'features/http2_demo' into 'master'

HTTP2 Client Demo

See merge request !1475

Ivan Grokhotkov 8 년 전
부모
커밋
c6d25dd9cc

+ 2 - 1
components/openssl/include/internal/ssl_methods.h

@@ -28,7 +28,7 @@
                     new, free, \
                     handshake, shutdown, clear, \
                     read, send, pending, \
-                    set_fd, get_fd, \
+                    set_fd, set_hostname, get_fd,	\
                     set_bufflen, \
                     get_verify_result, \
                     get_state) \
@@ -42,6 +42,7 @@
                 send, \
                 pending, \
                 set_fd, \
+		set_hostname, \
                 get_fd, \
                 set_bufflen, \
                 get_verify_result, \

+ 17 - 3
components/openssl/include/internal/ssl_types.h

@@ -81,6 +81,9 @@ typedef struct x509_method_st X509_METHOD;
 struct pkey_method_st;
 typedef struct pkey_method_st PKEY_METHOD;
 
+struct ssl_alpn_st;
+typedef struct ssl_alpn_st SSL_ALPN;
+
 struct stack_st {
 
     char **data;
@@ -144,6 +147,16 @@ struct X509_VERIFY_PARAM_st {
 
 };
 
+typedef enum { ALPN_INIT, ALPN_ENABLE, ALPN_DISABLE, ALPN_ERROR } ALPN_STATUS;
+struct ssl_alpn_st {
+     ALPN_STATUS alpn_status;
+     /* This is dynamically allocated */
+     char *alpn_string;
+     /* This only points to the members in the string */
+#define ALPN_LIST_MAX 10
+     const char *alpn_list[ALPN_LIST_MAX];
+};
+
 struct ssl_ctx_st
 {
     int version;
@@ -152,9 +165,7 @@ struct ssl_ctx_st
 
     unsigned long options;
 
-    #if 0
-        struct alpn_protocols alpn_protocol;
-    #endif
+    SSL_ALPN ssl_alpn;
 
     const SSL_METHOD *method;
 
@@ -248,6 +259,8 @@ struct ssl_method_func_st {
 
     void (*ssl_set_fd)(SSL *ssl, int fd, int mode);
 
+    void (*ssl_set_hostname)(SSL *ssl, const char *hostname);
+
     int (*ssl_get_fd)(const SSL *ssl, int mode);
 
     void (*ssl_set_bufflen)(SSL *ssl, int len);
@@ -277,6 +290,7 @@ struct pkey_method_st {
     int (*pkey_load)(EVP_PKEY *pkey, const unsigned char *buf, int len);
 };
 
+
 typedef int (*next_proto_cb)(SSL *ssl, unsigned char **out,
                              unsigned char *outlen, const unsigned char *in,
                              unsigned int inlen, void *arg);

+ 12 - 0
components/openssl/include/openssl/ssl.h

@@ -145,6 +145,18 @@ int SSL_shutdown(SSL *ssl);
  */
 int SSL_set_fd(SSL *ssl, int fd);
 
+/**
+ * @brief Set the hostname for SNI
+ *
+ * @param ssl - the SSL context point
+ * @param hostname  - pointer to the hostname
+ *
+ * @return result
+ *     1 : OK
+ *     0 : failed
+ */
+int SSL_set_tlsext_host_name(SSL* ssl, const char *hostname);
+
 /**
  * @brief These functions load the private key into the SSL_CTX or SSL object
  *

+ 2 - 0
components/openssl/include/platform/ssl_pm.h

@@ -39,6 +39,8 @@ int ssl_pm_pending(const SSL *ssl);
 void ssl_pm_set_fd(SSL *ssl, int fd, int mode);
 int ssl_pm_get_fd(const SSL *ssl, int mode);
 
+void ssl_pm_set_hostname(SSL *ssl, const char *hostname);
+
 OSSL_HANDSHAKE_STATE ssl_pm_get_state(const SSL *ssl);
 
 void ssl_pm_set_bufflen(SSL *ssl, int len);

+ 53 - 0
components/openssl/library/ssl_lib.c

@@ -224,6 +224,10 @@ void SSL_CTX_free(SSL_CTX* ctx)
 
     X509_free(ctx->client_CA);
 
+    if (ctx->ssl_alpn.alpn_string) {
+	 ssl_mem_free((void *)ctx->ssl_alpn.alpn_string);
+    }
+
     ssl_mem_free(ctx);
 }
 
@@ -730,6 +734,19 @@ int SSL_set_wfd(SSL *ssl, int fd)
     return 1;
 }
 
+/**
+ * @brief SET TLS Hostname
+ */
+int SSL_set_tlsext_host_name(SSL* ssl, const char *hostname)
+{
+     SSL_ASSERT1(ssl);
+     SSL_ASSERT1(hostname);
+
+     SSL_METHOD_CALL(set_hostname, ssl, hostname);
+
+     return 1;
+}
+
 /**
  * @brief get SSL version
  */
@@ -1554,3 +1571,39 @@ void SSL_set_verify(SSL *ssl, int mode, int (*verify_callback)(int, X509_STORE_C
     ssl->verify_mode = mode;
     ssl->verify_callback = verify_callback;
 }
+
+/**
+ * @brief set the ALPN protocols in the preferred order. SSL APIs require the
+ * protocols in a <length><value><length2><value2> format. mbedtls doesn't need
+ * that though. We sanitize that here itself. So convert from:
+ * "\x02h2\x06spdy/1" to { {"h2"}, {"spdy/1}, {NULL}}
+ */
+int SSL_CTX_set_alpn_protos(SSL_CTX *ctx, const unsigned char *protos, unsigned protos_len)
+{
+     ctx->ssl_alpn.alpn_string = ssl_mem_zalloc(protos_len + 1);
+     if (! ctx->ssl_alpn.alpn_string) {
+	  return 1;
+     }
+     ctx->ssl_alpn.alpn_status = ALPN_ENABLE;
+     memcpy(ctx->ssl_alpn.alpn_string, protos, protos_len);
+
+     char *ptr = ctx->ssl_alpn.alpn_string;
+     int i;
+     /* Only running to 1 less than the actual size */
+     for (i = 0; i < ALPN_LIST_MAX - 1; i++) {
+	  char len = *ptr;
+	  *ptr = '\0'; // Overwrite the length to act as previous element's string terminator
+	  ptr++;
+	  protos_len--;
+	  ctx->ssl_alpn.alpn_list[i] = ptr;
+	  ptr += len;
+	  protos_len -= len;
+	  if (! protos_len) {
+	       i++;
+	       break;
+	  }
+     }
+     ctx->ssl_alpn.alpn_list[i] = NULL;
+     return 0;
+}
+

+ 1 - 1
components/openssl/library/ssl_methods.c

@@ -22,7 +22,7 @@ IMPLEMENT_TLS_METHOD_FUNC(TLS_method_func,
         ssl_pm_new, ssl_pm_free,
         ssl_pm_handshake, ssl_pm_shutdown, ssl_pm_clear,
         ssl_pm_read, ssl_pm_send, ssl_pm_pending,
-        ssl_pm_set_fd, ssl_pm_get_fd,
+        ssl_pm_set_fd, ssl_pm_set_hostname, ssl_pm_get_fd,
         ssl_pm_set_bufflen,
         ssl_pm_get_verify_result,
         ssl_pm_get_state);

+ 10 - 0
components/openssl/platform/ssl_pm.c

@@ -153,6 +153,9 @@ int ssl_pm_new(SSL *ssl)
         mbedtls_ssl_conf_min_version(&ssl_pm->conf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_0);
     }
 
+    if (ssl->ctx->ssl_alpn.alpn_status == ALPN_ENABLE) {
+	 mbedtls_ssl_conf_alpn_protocols( &ssl_pm->conf, ssl->ctx->ssl_alpn.alpn_list );
+    }
     mbedtls_ssl_conf_rng(&ssl_pm->conf, mbedtls_ctr_drbg_random, &ssl_pm->ctr_drbg);
 
 #ifdef CONFIG_OPENSSL_LOWLEVEL_DEBUG
@@ -364,6 +367,13 @@ void ssl_pm_set_fd(SSL *ssl, int fd, int mode)
     ssl_pm->fd.fd = fd;
 }
 
+void ssl_pm_set_hostname(SSL *ssl, const char *hostname)
+{
+    struct ssl_pm *ssl_pm = (struct ssl_pm *)ssl->ssl_pm;
+
+    mbedtls_ssl_set_hostname(&ssl_pm->ssl, hostname);
+}
+
 int ssl_pm_get_fd(const SSL *ssl, int mode)
 {
     struct ssl_pm *ssl_pm = (struct ssl_pm *)ssl->ssl_pm;

+ 9 - 0
examples/protocols/http2_request/Makefile

@@ -0,0 +1,9 @@
+#
+# This is a project Makefile. It is assumed the directory this Makefile resides in is a
+# project subdirectory.
+#
+
+PROJECT_NAME := http2-request
+
+include $(IDF_PATH)/make/project.mk
+

+ 6 - 0
examples/protocols/http2_request/README.md

@@ -0,0 +1,6 @@
+# HTTP/2 Request Example
+
+Established HTTP/2 connection with https://http2.golang.org
+- Performs a GET on /clockstream
+- Performs a PUT on /ECHO
+

+ 1 - 0
examples/protocols/http2_request/components/sh2lib/component.mk

@@ -0,0 +1 @@
+COMPONENT_ADD_INCLUDEDIRS := .

+ 164 - 0
examples/protocols/http2_request/components/sh2lib/connectlib.c

@@ -0,0 +1,164 @@
+/* With adaptations by Espressif Systems
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include <stdlib.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <netdb.h>
+
+#include "connectlib.h"
+
+/* Basic parser from nghttp2/examples/client.c */
+int parse_uri(struct uri *res, const char *uri)
+{
+    /* We only interested in https */
+    size_t len, i, offset;
+    int ipv6addr = 0;
+    memset(res, 0, sizeof(struct uri));
+    len = strlen(uri);
+    if (len < 9 || memcmp("https://", uri, 8) != 0) {
+        return -1;
+    }
+    offset = 8;
+    res->host = res->hostport = &uri[offset];
+    res->hostlen = 0;
+    if (uri[offset] == '[') {
+        /* IPv6 literal address */
+        ++offset;
+        ++res->host;
+        ipv6addr = 1;
+        for (i = offset; i < len; ++i) {
+            if (uri[i] == ']') {
+                res->hostlen = i - offset;
+                offset = i + 1;
+                break;
+            }
+        }
+    } else {
+        const char delims[] = ":/?#";
+        for (i = offset; i < len; ++i) {
+            if (strchr(delims, uri[i]) != NULL) {
+                break;
+            }
+        }
+        res->hostlen = i - offset;
+        offset = i;
+    }
+    if (res->hostlen == 0) {
+        return -1;
+    }
+    /* Assuming https */
+    res->port = 443;
+    if (offset < len) {
+        if (uri[offset] == ':') {
+            /* port */
+            const char delims[] = "/?#";
+            int port = 0;
+            ++offset;
+            for (i = offset; i < len; ++i) {
+                if (strchr(delims, uri[i]) != NULL) {
+                    break;
+                }
+                if ('0' <= uri[i] && uri[i] <= '9') {
+                    port *= 10;
+                    port += uri[i] - '0';
+                    if (port > 65535) {
+                        return -1;
+                    }
+                } else {
+                    return -1;
+                }
+            }
+            if (port == 0) {
+                return -1;
+            }
+            offset = i;
+            res->port = (uint16_t)port;
+        }
+    }
+    res->hostportlen = (size_t)(uri + offset + ipv6addr - res->host);
+    for (i = offset; i < len; ++i) {
+        if (uri[i] == '#') {
+            break;
+        }
+    }
+    if (i - offset == 0) {
+        res->path = "/";
+        res->pathlen = 1;
+    } else {
+        res->path = &uri[offset];
+        res->pathlen = i - offset;
+    }
+    return 0;
+}
+
+int connect_to_host(const char *host, size_t hostlen, uint16_t port)
+{
+    int ret;
+    struct addrinfo hints = {
+        .ai_family = AF_UNSPEC,
+        .ai_socktype = SOCK_STREAM,
+    };
+
+    char service[6];
+    snprintf(service, sizeof(service), "%u", port);
+
+    char *use_host = calloc(1, hostlen + 1);
+    if (!use_host) {
+         return -1;
+    }
+    strncpy(use_host, host, hostlen);
+
+    struct addrinfo *res;
+    ret = getaddrinfo(use_host, service, &hints, &res);
+    if (ret) {
+        free(use_host);
+        return -1;
+    }
+    free(use_host);
+
+    ret = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+    if (ret < 0) {
+        goto err_freeaddr;
+    }
+
+    int fd = ret;
+    ret = connect(fd, res->ai_addr, res->ai_addrlen);
+    if (ret < 0) {
+        goto err_freesocket;
+    }
+
+    return fd;
+
+err_freesocket:
+    close(fd);
+err_freeaddr:
+    freeaddrinfo(res);
+    return -1;
+}
+

+ 34 - 0
examples/protocols/http2_request/components/sh2lib/connectlib.h

@@ -0,0 +1,34 @@
+// Copyright 2017 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#ifndef __ESP_EXAMPLE_CONNECT_LIB_H_
+#define __ESP_EXAMPLE_CONNECT_LIB_H_
+
+struct uri {
+    const char *host;
+    /* In this program, path contains query component as well. */
+    const char *path;
+    size_t pathlen;
+    const char *hostport;
+    size_t hostlen;
+    size_t hostportlen;
+    uint16_t port;
+};
+
+/* connect() to a TCP host, return socket descriptor */
+int connect_to_host(const char *host, size_t hostlen, uint16_t port);
+
+/* Parse a URI into its components */
+int parse_uri(struct uri *res, const char *uri);
+
+#endif /* ! __ESP_EXAMPLE_CONNECT_LIB_H_ */

+ 418 - 0
examples/protocols/http2_request/components/sh2lib/sh2lib.c

@@ -0,0 +1,418 @@
+// Copyright 2017 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#include <stdlib.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <netdb.h>
+#include <esp_log.h>
+
+#include "connectlib.h"
+#include "sh2lib.h"
+
+static const char *TAG = "sh2lib";
+
+#define DBG_FRAME_SEND 1
+
+/* SSL connection on the TCP socket that is already connected */
+static int do_ssl_connect(struct sh2lib_handle *hd, int sockfd, const char *hostname)
+{
+    SSL_CTX *ssl_ctx = SSL_CTX_new(TLSv1_2_client_method());
+    if (!ssl_ctx) {
+        return -1;
+    }
+
+    unsigned char vector[] = "\x02h2";
+    SSL_CTX_set_alpn_protos(ssl_ctx, vector, strlen((char *)vector));
+    SSL *ssl = SSL_new(ssl_ctx);
+    if (!ssl) {
+        SSL_CTX_free(ssl_ctx);
+        return -1;
+    }
+
+    SSL_set_tlsext_host_name(ssl, hostname);
+    SSL_set_fd(ssl, sockfd);
+    int ret = SSL_connect(ssl);
+    if (ret < 1) {
+        int err = SSL_get_error(ssl, ret);
+        ESP_LOGE(TAG, "[ssl-connect] Failed SSL handshake ret=%d error=%d", ret, err);
+        SSL_CTX_free(ssl_ctx);
+        SSL_free(ssl);
+        return -1;
+    }
+    hd->ssl_ctx = ssl_ctx;
+    hd->ssl = ssl;
+    hd->sockfd = sockfd;
+
+    int flags = fcntl(hd->sockfd, F_GETFL, 0);
+    fcntl(hd->sockfd, F_SETFL, flags | O_NONBLOCK);
+
+    return 0;
+}
+
+/*
+ * 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 callback_send_inner(struct sh2lib_handle *hd, const uint8_t *data,
+                                   size_t length)
+{
+    int rv = SSL_write(hd->ssl, data, (int)length);
+    if (rv <= 0) {
+        int err = SSL_get_error(hd->ssl, rv);
+        if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) {
+            rv = NGHTTP2_ERR_WOULDBLOCK;
+        } else {
+            rv = NGHTTP2_ERR_CALLBACK_FAILURE;
+        }
+    }
+    return rv;
+}
+
+static ssize_t callback_send(nghttp2_session *session, const uint8_t *data,
+                             size_t length, int flags, void *user_data)
+{
+    int rv = 0;
+    struct sh2lib_handle *hd = user_data;
+
+    int copy_offset = 0;
+    int pending_data = length;
+
+    /* Send data in 1000 byte chunks */
+    while (copy_offset != (length - 1)) {
+        int chunk_len = pending_data > 1000 ? 1000 : pending_data;
+        int subrv = callback_send_inner(hd, data + copy_offset, chunk_len);
+        if (subrv <= 0) {
+            if (copy_offset) {
+                /* If some data was xferred, send the number of bytes
+                 * xferred */
+                rv = copy_offset;
+            } else {
+                /* If not, send the error code */
+                rv = subrv;
+            }
+            break;
+        }
+        copy_offset += chunk_len;
+        pending_data -= chunk_len;
+    }
+    return rv;
+}
+
+/*
+ * 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 callback_recv(nghttp2_session *session, uint8_t *buf,
+                             size_t length, int flags, void *user_data)
+{
+    struct sh2lib_handle *hd = user_data;
+    int rv;
+    rv = SSL_read(hd->ssl, buf, (int)length);
+    if (rv < 0) {
+        int err = SSL_get_error(hd->ssl, rv);
+        if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) {
+            rv = NGHTTP2_ERR_WOULDBLOCK;
+        } else {
+            rv = NGHTTP2_ERR_CALLBACK_FAILURE;
+        }
+    } else if (rv == 0) {
+        rv = NGHTTP2_ERR_EOF;
+    }
+    return rv;
+}
+
+char *sh2lib_frame_type_str(int type)
+{
+    switch (type) {
+    case NGHTTP2_HEADERS:
+        return "HEADERS";
+        break;
+    case NGHTTP2_RST_STREAM:
+        return "RST_STREAM";
+        break;
+    case NGHTTP2_GOAWAY:
+        return "GOAWAY";
+        break;
+    case NGHTTP2_DATA:
+        return "DATA";
+        break;
+    case NGHTTP2_SETTINGS:
+        return "SETTINGS";
+        break;
+    case NGHTTP2_PUSH_PROMISE:
+        return "PUSH_PROMISE";
+        break;
+    case NGHTTP2_PING:
+        return "PING";
+        break;
+    default:
+        return "other";
+        break;
+    }
+}
+
+static int callback_on_frame_send(nghttp2_session *session,
+                                  const nghttp2_frame *frame, void *user_data)
+{
+    ESP_LOGD(TAG, "[frame-send] frame type %s", sh2lib_frame_type_str(frame->hd.type));
+    switch (frame->hd.type) {
+    case NGHTTP2_HEADERS:
+        if (nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)) {
+            ESP_LOGD(TAG, "[frame-send] C ----------------------------> S (HEADERS)");
+#if DBG_FRAME_SEND
+            ESP_LOGD(TAG, "[frame-send] headers nv-len = %d", frame->headers.nvlen);
+            const nghttp2_nv *nva = frame->headers.nva;
+            size_t i;
+            for (i = 0; i < frame->headers.nvlen; ++i) {
+                ESP_LOGD(TAG, "[frame-send] %s : %s", nva[i].name, nva[i].value);
+            }
+#endif
+        }
+        break;
+    }
+    return 0;
+}
+
+static int callback_on_frame_recv(nghttp2_session *session,
+                                  const nghttp2_frame *frame, void *user_data)
+{
+    ESP_LOGD(TAG, "[frame-recv][sid: %d] frame type  %s", frame->hd.stream_id, sh2lib_frame_type_str(frame->hd.type));
+    if (frame->hd.type != NGHTTP2_DATA) {
+        return 0;
+    }
+    /* Subsequent processing only for data frame */
+    sh2lib_frame_data_recv_cb_t data_recv_cb = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
+    if (data_recv_cb) {
+        struct sh2lib_handle *h2 = user_data;
+        (*data_recv_cb)(h2, NULL, 0, DATA_RECV_FRAME_COMPLETE);
+    }
+    return 0;
+}
+
+static int callback_on_stream_close(nghttp2_session *session, int32_t stream_id,
+                                    uint32_t error_code, void *user_data)
+{
+
+    ESP_LOGD(TAG, "[stream-close][sid %d]", stream_id);
+    sh2lib_frame_data_recv_cb_t data_recv_cb = nghttp2_session_get_stream_user_data(session, stream_id);
+    if (data_recv_cb) {
+        struct sh2lib_handle *h2 = user_data;
+        (*data_recv_cb)(h2, NULL, 0, DATA_RECV_RST_STREAM);
+    }
+    return 0;
+}
+
+static int callback_on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
+                                       int32_t stream_id, const uint8_t *data,
+                                       size_t len, void *user_data)
+{
+    sh2lib_frame_data_recv_cb_t data_recv_cb;
+    ESP_LOGD(TAG, "[data-chunk][sid:%d]", stream_id);
+    data_recv_cb = nghttp2_session_get_stream_user_data(session, stream_id);
+    if (data_recv_cb) {
+        ESP_LOGD(TAG, "[data-chunk] C <---------------------------- S (DATA chunk)"
+                "%lu bytes",
+                (unsigned long int)len);
+        struct sh2lib_handle *h2 = user_data;
+        (*data_recv_cb)(h2, (char *)data, len, 0);
+        /* TODO: What to do with the return value: look for pause/abort */
+    }
+    return 0;
+}
+
+static int callback_on_header(nghttp2_session *session, const nghttp2_frame *frame,
+                              const uint8_t *name, size_t namelen, const uint8_t *value,
+                              size_t valuelen, uint8_t flags, void *user_data)
+{
+    ESP_LOGD(TAG, "[hdr-recv][sid:%d] %s : %s", frame->hd.stream_id, name, value);
+    return 0;
+}
+
+static int do_http2_connect(struct sh2lib_handle *hd)
+{
+    int ret;
+    nghttp2_session_callbacks *callbacks;
+    nghttp2_session_callbacks_new(&callbacks);
+    nghttp2_session_callbacks_set_send_callback(callbacks, callback_send);
+    nghttp2_session_callbacks_set_recv_callback(callbacks, callback_recv);
+    nghttp2_session_callbacks_set_on_frame_send_callback(callbacks, callback_on_frame_send);
+    nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, callback_on_frame_recv);
+    nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, callback_on_stream_close);
+    nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, callback_on_data_chunk_recv);
+    nghttp2_session_callbacks_set_on_header_callback(callbacks, callback_on_header);
+    ret = nghttp2_session_client_new(&hd->http2_sess, callbacks, hd);
+    if (ret != 0) {
+        ESP_LOGE(TAG, "[sh2-connect] New http2 session failed");
+        nghttp2_session_callbacks_del(callbacks);
+        return -1;
+    }
+    nghttp2_session_callbacks_del(callbacks);
+
+    /* Create the SETTINGS frame */
+    ret = nghttp2_submit_settings(hd->http2_sess, NGHTTP2_FLAG_NONE, NULL, 0);
+    if (ret != 0) {
+        ESP_LOGE(TAG, "[sh2-connect] Submit settings failed");
+        return -1;
+    }
+    return 0;
+}
+
+int sh2lib_connect(struct sh2lib_handle *hd, const char *uri)
+{
+    memset(hd, 0, sizeof(*hd));
+
+    struct uri res;
+    /* Parse the URI */
+    if (parse_uri(&res, uri) != 0) {
+        ESP_LOGE(TAG, "[sh2-connect] Failed to parse URI");
+        return -1;
+    }
+
+    /* TCP connection with the server */
+    int sockfd = connect_to_host(res.host, res.hostlen, res.port);
+    if (sockfd < 0) {
+        ESP_LOGE(TAG, "[sh2-connect] Failed to connect to %s", uri);
+        return -1;
+    }
+
+    /* SSL Connection on the socket */
+    if (do_ssl_connect(hd, sockfd, res.host) != 0) {
+        ESP_LOGE(TAG, "[sh2-connect] SSL Handshake failed with %s", uri);
+        goto error;
+    }
+
+    /* HTTP/2 Connection */
+    if (do_http2_connect(hd) != 0) {
+        ESP_LOGE(TAG, "[sh2-connect] HTTP2 Connection failed with %s", uri);
+        goto error;
+    }
+
+    return 0;
+error:
+    sh2lib_free(hd);
+    return -1;
+}
+
+void sh2lib_free(struct sh2lib_handle *hd)
+{
+    if (hd->http2_sess) {
+        nghttp2_session_del(hd->http2_sess);
+        hd->http2_sess = NULL;
+    }
+    if (hd->ssl) {
+        SSL_free(hd->ssl);
+        hd->ssl = NULL;
+    }
+    if (hd->ssl_ctx) {
+        SSL_CTX_free(hd->ssl_ctx);
+        hd->ssl_ctx = NULL;
+    }
+    if (hd->sockfd) {
+        close(hd->sockfd);
+        hd->ssl_ctx = 0;
+    }
+}
+
+int sh2lib_execute(struct sh2lib_handle *hd)
+{
+    int ret;
+    ret = nghttp2_session_send(hd->http2_sess);
+    if (ret != 0) {
+        ESP_LOGE(TAG, "[sh2-execute] HTTP2 session send failed %d", ret);
+        return -1;
+    }
+    ret = nghttp2_session_recv(hd->http2_sess);
+    if (ret != 0) {
+        ESP_LOGE(TAG, "[sh2-execute] HTTP2 session recv failed %d", ret);
+        return -1;
+    }
+    return 0;
+}
+
+int sh2lib_do_get_with_nv(struct sh2lib_handle *hd, const nghttp2_nv *nva, size_t nvlen, sh2lib_frame_data_recv_cb_t recv_cb)
+{
+    int ret = nghttp2_submit_request(hd->http2_sess, NULL, nva, nvlen, NULL, recv_cb);
+    if (ret < 0) {
+        ESP_LOGE(TAG, "[sh2-do-get] HEADERS call failed");
+        return -1;
+    }
+    return ret;
+}
+
+int sh2lib_do_get(struct sh2lib_handle *hd, const char *path, sh2lib_frame_data_recv_cb_t recv_cb)
+{
+#define HTTP2_PATH_NV ":path"
+    const nghttp2_nv nva[] = { SH2LIB_MAKE_NV(":method", "GET"),
+                               SH2LIB_MAKE_NV(":scheme", "https"),
+    {(uint8_t *)HTTP2_PATH_NV, (uint8_t *)path, strlen(HTTP2_PATH_NV), strlen(path), NGHTTP2_NV_FLAG_NONE},
+                             };
+    return sh2lib_do_get_with_nv(hd, nva, sizeof(nva) / sizeof(nva[0]), recv_cb);
+}
+
+ssize_t sh2lib_data_provider_cb(nghttp2_session *session, int32_t stream_id, uint8_t *buf,
+                                size_t length, uint32_t *data_flags,
+                                nghttp2_data_source *source, void *user_data)
+{
+    struct sh2lib_handle *h2 = user_data;
+    sh2lib_putpost_data_cb_t data_cb = source->ptr;
+    return (*data_cb)(h2, (char *)buf, length, data_flags);
+}
+
+int sh2lib_do_putpost_with_nv(struct sh2lib_handle *hd, const nghttp2_nv *nva, size_t nvlen,
+                              sh2lib_putpost_data_cb_t send_cb,
+                              sh2lib_frame_data_recv_cb_t recv_cb)
+{
+
+    nghttp2_data_provider sh2lib_data_provider;
+    sh2lib_data_provider.read_callback = sh2lib_data_provider_cb;
+    sh2lib_data_provider.source.ptr = send_cb;
+    int ret = nghttp2_submit_request(hd->http2_sess, NULL, nva, nvlen, &sh2lib_data_provider, recv_cb);
+    if (ret < 0) {
+        ESP_LOGE(TAG, "[sh2-do-putpost] HEADERS call failed");
+        return -1;
+    }
+    return ret;
+}
+
+int sh2lib_do_post(struct sh2lib_handle *hd, const char *path,
+                   sh2lib_putpost_data_cb_t send_cb,
+                   sh2lib_frame_data_recv_cb_t recv_cb)
+{
+    const nghttp2_nv nva[] = { SH2LIB_MAKE_NV(":method", "POST"),
+                               SH2LIB_MAKE_NV(":scheme", "https"),
+                               SH2LIB_MAKE_NV(":path", path),
+                             };
+    return sh2lib_do_putpost_with_nv(hd, nva, sizeof(nva) / sizeof(nva[0]), send_cb, recv_cb);
+}
+
+int sh2lib_do_put(struct sh2lib_handle *hd, const char *path,
+                  sh2lib_putpost_data_cb_t send_cb,
+                  sh2lib_frame_data_recv_cb_t recv_cb)
+{
+    const nghttp2_nv nva[] = { SH2LIB_MAKE_NV(":method", "PUT"),
+                               SH2LIB_MAKE_NV(":scheme", "https"),
+                               SH2LIB_MAKE_NV(":path", path),
+                             };
+    return sh2lib_do_putpost_with_nv(hd, nva, sizeof(nva) / sizeof(nva[0]), send_cb, recv_cb);
+}
+

+ 266 - 0
examples/protocols/http2_request/components/sh2lib/sh2lib.h

@@ -0,0 +1,266 @@
+// Copyright 2017 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#ifndef __ESP_EXAMPLE_SH2_LIB_H_
+#define __ESP_EXAMPLE_SH2_LIB_H_
+
+#include <openssl/ssl.h>
+#include <nghttp2/nghttp2.h>
+
+/*
+ * This is a thin API wrapper over nghttp2 that offers simplified APIs for usage
+ * in the application. The intention of this wrapper is to act as a stepping
+ * stone to quickly get started with using the HTTP/2 client. Since the focus is
+ * on simplicity, not all the features of HTTP/2 are supported through this
+ * wrapper. Once you are fairly comfortable with nghttp2, feel free to directly
+ * use nghttp2 APIs or make changes to sh2lib.c for realising your objectives.
+ *
+ * TODO:
+ * - Allowing to query response code, content-type etc in the receive callback
+ * - A simple function for per-stream header callback
+ */
+/**
+ * @brief Handle for working with sh2lib APIs
+ */
+struct sh2lib_handle {
+    /* Ideally, CTX is per-program, so we could potentially take it out of this
+     * per-connection structure
+     */
+    SSL_CTX         *ssl_ctx;      /*!< Pointer to the SSL context */
+    SSL             *ssl;          /*!< Pointer to the SSL handle */
+    nghttp2_session *http2_sess;   /*!< Pointer to the HTTP2 session handle */
+    int             sockfd;        /*!< Socket file descriptor */
+};
+
+/** Flag indicating receive stream is reset */
+#define DATA_RECV_RST_STREAM      1
+/** Flag indicating frame is completely received  */
+#define DATA_RECV_FRAME_COMPLETE  2
+
+/**
+ * @brief Function Prototype for data receive callback
+ *
+ * This function gets called whenever data is received on any stream. The
+ * function is also called for indicating events like frame receive complete, or
+ * end of stream. The function may get called multiple times as long as data is
+ * received on the stream.
+ *
+ * @param[in] handle     Pointer to the sh2lib handle.
+ * @param[in] data       Pointer to a buffer that contains the data received.
+ * @param[in] len        The length of valid data stored at the 'data' pointer.
+ * @param[in] flags      Flags indicating whether the stream is reset (DATA_RECV_RST_STREAM) or
+ *                       this particularly frame is completely received
+ *                       DATA_RECV_FRAME_COMPLETE).
+ *
+ * @return The function should return 0
+ */
+typedef int (*sh2lib_frame_data_recv_cb_t)(struct sh2lib_handle *handle, const char *data, size_t len, int flags);
+
+/**
+ * @brief Function Prototype for callback to send data in PUT/POST
+ *
+ * This function gets called whenever nghttp2 wishes to send data, like for
+ * PUT/POST requests to the server. The function keeps getting called until this
+ * function sets the flag NGHTTP2_DATA_FLAG_EOF to indicate end of data.
+ *
+ * @param[in] handle       Pointer to the sh2lib handle.
+ * @param[out] data        Pointer to a buffer that should contain the data to send.
+ * @param[in] len          The maximum length of data that can be sent out by this function.
+ * @param[out] data_flags  Pointer to the data flags. The NGHTTP2_DATA_FLAG_EOF
+ *                         should be set in the data flags to indicate end of new data.
+ *
+ * @return The function should return the number of valid bytes stored in the
+ * data pointer
+ */
+typedef int (*sh2lib_putpost_data_cb_t)(struct sh2lib_handle *handle, char *data, size_t len, uint32_t *data_flags);
+
+/**
+ * @brief Connect to a URI using HTTP/2
+ *
+ * This API opens an HTTP/2 connection with the provided URI. If successful, the
+ * hd pointer is populated with a valid handle for subsequent communication.
+ *
+ * Only 'https' URIs are supported.
+ *
+ * @param[out] hd      Pointer to a variable of the type 'struct sh2lib_handle'.
+ * @param[in]  uri      Pointer to the URI that should be connected to.
+ *
+ * @return
+ *             - ESP_OK if the connection was successful
+ *             - ESP_FAIL if the connection fails
+ */
+int sh2lib_connect(struct sh2lib_handle *hd, const char *uri);
+
+/**
+ * @brief Free a sh2lib handle
+ *
+ * This API frees-up an sh2lib handle, thus closing any open connections that
+ * may be associated with this handle, and freeing up any resources.
+ *
+ * @param[in] hd      Pointer to a variable of the type 'struct sh2lib_handle'.
+ *
+ */
+void sh2lib_free(struct sh2lib_handle *hd);
+
+/**
+ * @brief Setup an HTTP GET request stream
+ *
+ * This API sets up an HTTP GET request to be sent out to the server. A new
+ * stream is created for handling the request. Once the request is setup, the
+ * API sh2lib_execute() must be called to actually perform the socket I/O with
+ * the server.
+ *
+ * @param[in] hd        Pointer to a variable of the type 'struct sh2lib_handle'.
+ * @param[in] path      Pointer to the string that contains the resource to
+ *                      perform the HTTP GET operation on (for example, /users).
+ * @param[in] recv_cb   The callback function that should be called for
+ *                      processing the request's response
+ *
+ * @return
+ *             - ESP_OK if request setup is successful
+ *             - ESP_FAIL if the request setup fails
+ */
+int sh2lib_do_get(struct sh2lib_handle *hd, const char *path, sh2lib_frame_data_recv_cb_t recv_cb);
+
+/**
+ * @brief Setup an HTTP POST request stream
+ *
+ * This API sets up an HTTP POST request to be sent out to the server. A new
+ * stream is created for handling the request. Once the request is setup, the
+ * API sh2lib_execute() must be called to actually perform the socket I/O with
+ * the server.
+ *
+ * @param[in] hd        Pointer to a variable of the type 'struct sh2lib_handle'.
+ * @param[in] path      Pointer to the string that contains the resource to
+ *                      perform the HTTP POST operation on (for example, /users).
+ * @param[in] send_cb   The callback function that should be called for
+ *                      sending data as part of this request.
+ * @param[in] recv_cb   The callback function that should be called for
+ *                      processing the request's response
+ *
+ * @return
+ *             - ESP_OK if request setup is successful
+ *             - ESP_FAIL if the request setup fails
+ */
+int sh2lib_do_post(struct sh2lib_handle *hd, const char *path,
+                   sh2lib_putpost_data_cb_t send_cb,
+                   sh2lib_frame_data_recv_cb_t recv_cb);
+
+/**
+ * @brief Setup an HTTP PUT request stream
+ *
+ * This API sets up an HTTP PUT request to be sent out to the server. A new
+ * stream is created for handling the request. Once the request is setup, the
+ * API sh2lib_execute() must be called to actually perform the socket I/O with
+ * the server.
+ *
+ * @param[in] hd        Pointer to a variable of the type 'struct sh2lib_handle'.
+ * @param[in] path      Pointer to the string that contains the resource to
+ *                      perform the HTTP PUT operation on (for example, /users).
+ * @param[in] send_cb   The callback function that should be called for
+ *                      sending data as part of this request.
+ * @param[in] recv_cb   The callback function that should be called for
+ *                      processing the request's response
+ *
+ * @return
+ *             - ESP_OK if request setup is successful
+ *             - ESP_FAIL if the request setup fails
+ */
+int sh2lib_do_put(struct sh2lib_handle *hd, const char *path,
+                  sh2lib_putpost_data_cb_t send_cb,
+                  sh2lib_frame_data_recv_cb_t recv_cb);
+
+/**
+ * @brief Execute send/receive on an HTTP/2 connection
+ *
+ * While the API sh2lib_do_get(), sh2lib_do_post() setup the requests to be
+ * initiated with the server, this API performs the actual data send/receive
+ * operations on the HTTP/2 connection. The callback functions are accordingly
+ * called during the processing of these requests.
+ *
+ * @param[in] hd      Pointer to a variable of the type 'struct sh2lib_handle'
+ *
+ * @return
+ *             - ESP_OK if the connection was successful
+ *             - ESP_FAIL if the connection fails
+ */
+int sh2lib_execute(struct sh2lib_handle *hd);
+
+#define SH2LIB_MAKE_NV(NAME, VALUE)                                    \
+  {                                                                    \
+    (uint8_t *)NAME, (uint8_t *)VALUE, strlen(NAME), strlen(VALUE),    \
+        NGHTTP2_NV_FLAG_NONE                                           \
+  }
+
+/**
+ * @brief Setup an HTTP GET request stream with custom name-value pairs
+ *
+ * For a simpler version of the API, please refer to sh2lib_do_get().
+ *
+ * This API sets up an HTTP GET request to be sent out to the server. A new
+ * stream is created for handling the request. Once the request is setup, the
+ * API sh2lib_execute() must be called to actually perform the socket I/O with
+ * the server.
+ *
+ * Please note that the following name value pairs MUST be a part of the request
+ *     -  name:value
+ *     -  ":method":"GET"
+ *     -  ":scheme":"https"
+ *     -  ":path":<the-path-for-the-GET-operation>  (for example, /users)
+ *
+ * @param[in] hd        Pointer to a variable of the type 'struct sh2lib_handle'.
+ * @param[in] nva       An array of name-value pairs that should be part of the request.
+ * @param[in] nvlen     The number of elements in the array pointed to by 'nva'.
+ * @param[in] recv_cb   The callback function that should be called for
+ *                      processing the request's response
+ *
+ * @return
+ *             - ESP_OK if request setup is successful
+ *             - ESP_FAIL if the request setup fails
+ */
+int sh2lib_do_get_with_nv(struct sh2lib_handle *hd, const nghttp2_nv *nva, size_t nvlen, sh2lib_frame_data_recv_cb_t recv_cb);
+
+/**
+ * @brief Setup an HTTP PUT/POST request stream with custom name-value pairs
+ *
+ * For a simpler version of the API, please refer to sh2lib_do_put() or
+ * sh2lib_do_post().
+ *
+ * This API sets up an HTTP PUT/POST request to be sent out to the server. A new
+ * stream is created for handling the request. Once the request is setup, the
+ * API sh2lib_execute() must be called to actually perform the socket I/O with
+ * the server.
+ *
+ * Please note that the following name value pairs MUST be a part of the request
+ *     -  name:value
+ *     -  ":method":"PUT" (or POST)
+ *     -  ":scheme":"https"
+ *     -  ":path":<the-path-for-the-PUT-operation>  (for example, /users)
+ *
+ * @param[in] hd        Pointer to a variable of the type 'struct sh2lib_handle'.
+ * @param[in] nva       An array of name-value pairs that should be part of the request.
+ * @param[in] nvlen     The number of elements in the array pointed to by 'nva'.
+ * @param[in] send_cb   The callback function that should be called for
+ *                      sending data as part of this request.
+ * @param[in] recv_cb   The callback function that should be called for
+ *                      processing the request's response
+ *
+ * @return
+ *             - ESP_OK if request setup is successful
+ *             - ESP_FAIL if the request setup fails
+ */
+int sh2lib_do_putpost_with_nv(struct sh2lib_handle *hd, const nghttp2_nv *nva, size_t nvlen,
+                              sh2lib_putpost_data_cb_t send_cb,
+                              sh2lib_frame_data_recv_cb_t recv_cb);
+
+#endif /* ! __ESP_EXAMPLE_SH2_LIB_H_ */

+ 17 - 0
examples/protocols/http2_request/main/Kconfig.projbuild

@@ -0,0 +1,17 @@
+menu "Example Configuration"
+
+config WIFI_SSID
+    string "WiFi SSID"
+	default "myssid"
+	help
+		SSID (network name) for the example to connect to.
+
+config WIFI_PASSWORD
+    string "WiFi Password"
+	default "myssid"
+	help
+		WiFi password (WPA or WPA2) for the example to use.
+
+		Can be left blank if the network has no security set.
+
+endmenu

+ 0 - 0
examples/protocols/http2_request/main/component.mk


+ 199 - 0
examples/protocols/http2_request/main/http2_request_example_main.c

@@ -0,0 +1,199 @@
+/* HTTP2 GET Example using nghttp2
+ 
+   Contacts http2.golang.org and executes the GET/PUT requests. A thin API
+   wrapper on top of nghttp2, to properly demonstrate the interactions.
+
+   This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+   Unless required by applicable law or agreed to in writing, this
+   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+   CONDITIONS OF ANY KIND, either express or implied.
+
+ */
+#include <string.h>
+#include <stdlib.h>
+#include <sys/time.h>
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/event_groups.h"
+#include "esp_wifi.h"
+#include "esp_event_loop.h"
+#include "apps/sntp/sntp.h"
+#include "esp_log.h"
+#include "esp_system.h"
+#include "nvs_flash.h"
+
+#include "sh2lib.h"
+
+/* The examples use simple WiFi configuration that you can set via
+   'make menuconfig'.
+
+   If you'd rather not, just change the below entries to strings with
+   the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid"
+*/
+#define EXAMPLE_WIFI_SSID CONFIG_WIFI_SSID
+#define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD
+
+/* FreeRTOS event group to signal when we are connected & ready to make a request */
+static EventGroupHandle_t wifi_event_group;
+
+/* The event group allows multiple bits for each event,
+   but we only care about one event - are we connected
+   to the AP with an IP? */
+const int CONNECTED_BIT = BIT0;
+
+static const char *TAG = "http2-req";
+
+/* The HTTP/2 server to connect to */
+#define HTTP2_SERVER_URI  "https://http2.golang.org"
+/* A GET request that keeps streaming current time every second */
+#define HTTP2_STREAMING_GET_PATH  "/clockstream"
+/* A PUT request that echoes whatever we had sent to it */
+#define HTTP2_PUT_PATH            "/ECHO"
+
+int handle_get_response(struct sh2lib_handle *handle, const char *data, size_t len, int flags)
+{
+    if (len) {
+        printf("[get-response] %.*s\n", len, data);
+    }
+    if (flags == DATA_RECV_FRAME_COMPLETE) {
+        printf("[get-response] Frame fully received\n");
+    }
+    if (flags == DATA_RECV_RST_STREAM) {
+        printf("[get-response] Stream Closed\n");
+    }
+    return 0;
+}
+
+int handle_echo_response(struct sh2lib_handle *handle, const char *data, size_t len, int flags)
+{
+    if (len) {
+        printf("[echo-response] %.*s\n", len, data);
+    }
+    if (flags == DATA_RECV_FRAME_COMPLETE) {
+        printf("[echo-response] Frame fully received\n");
+    }
+    if (flags == DATA_RECV_RST_STREAM) {
+        printf("[echo-response] Stream Closed\n");
+    }
+    return 0;
+}
+
+int send_put_data(struct sh2lib_handle *handle, char *buf, size_t length, uint32_t *data_flags)
+{
+#define DATA_TO_SEND "Hello World"
+    int copylen = strlen(DATA_TO_SEND);
+    if (copylen < length) {
+        printf("[data-prvd] Sending %d bytes\n", copylen);
+        memcpy(buf, DATA_TO_SEND, copylen);
+    } else {
+        copylen = 0;
+    }
+
+    (*data_flags) |= NGHTTP2_DATA_FLAG_EOF;
+    return copylen;
+}
+
+static void set_time(void)
+{
+    struct timeval tv = {
+        .tv_sec = 1509449941,
+    };
+    struct timezone tz = {
+        0, 0
+    };
+    settimeofday(&tv, &tz);
+
+    /* Start SNTP service */
+    sntp_setoperatingmode(SNTP_OPMODE_POLL);
+    sntp_init();
+}
+
+static void http2_task(void *args)
+{
+    /* Waiting for connection */
+    xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
+                        false, true, portMAX_DELAY);
+
+    /* Set current time: proper system time is required for TLS based
+     * certificate verification.
+     */
+    set_time();
+
+    /* HTTP2: one connection multiple requests. Do the TLS/TCP connection first */
+    printf("Connecting to server\n");
+    struct sh2lib_handle hd;
+    if (sh2lib_connect(&hd, HTTP2_SERVER_URI) != 0) {
+        printf("Failed to connect\n");
+        return;
+    }
+    printf("Connection done\n");
+
+    /* HTTP GET  */
+    sh2lib_do_get(&hd, HTTP2_STREAMING_GET_PATH, handle_get_response);
+
+    /* HTTP PUT  */
+    sh2lib_do_put(&hd, HTTP2_PUT_PATH, send_put_data, handle_echo_response);
+
+    while (1) {
+        /* Process HTTP2 send/receive */
+        if (sh2lib_execute(&hd) < 0) {
+            printf("Error in send/receive\n");
+            break;
+        }
+        vTaskDelay(2);
+    }
+
+    sh2lib_free(&hd);
+}
+
+static esp_err_t event_handler(void *ctx, system_event_t *event)
+{
+    switch (event->event_id) {
+    case SYSTEM_EVENT_STA_START:
+        ESP_LOGI(TAG, "SYSTEM_EVENT_STA_START");
+        ESP_ERROR_CHECK(esp_wifi_connect());
+        break;
+    case SYSTEM_EVENT_STA_GOT_IP:
+        ESP_LOGI(TAG, "SYSTEM_EVENT_STA_GOT_IP");
+        ESP_LOGI(TAG, "got ip:%s\n", ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip));
+        xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
+        break;
+    case SYSTEM_EVENT_STA_DISCONNECTED:
+        ESP_LOGI(TAG, "SYSTEM_EVENT_STA_DISCONNECTED");
+        ESP_ERROR_CHECK(esp_wifi_connect());
+        xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
+        break;
+    default:
+        break;
+    }
+    return ESP_OK;
+}
+
+static void initialise_wifi(void)
+{
+    tcpip_adapter_init();
+    wifi_event_group = xEventGroupCreate();
+    ESP_ERROR_CHECK( esp_event_loop_init(event_handler, NULL) );
+    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
+    ESP_ERROR_CHECK( esp_wifi_init(&cfg) );
+    ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) );
+    wifi_config_t wifi_config = {
+        .sta = {
+            .ssid = EXAMPLE_WIFI_SSID,
+            .password = EXAMPLE_WIFI_PASS,
+        },
+    };
+    ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid);
+    ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );
+    ESP_ERROR_CHECK( esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) );
+    ESP_ERROR_CHECK( esp_wifi_start() );
+}
+
+void app_main()
+{
+    ESP_ERROR_CHECK( nvs_flash_init() );
+    initialise_wifi();
+
+    xTaskCreate(&http2_task, "http2_task", (1024 * 32), NULL, 5, NULL);
+}