blob: 12e4f47c7709d21af2c4bba03202ae77673592d5 [file] [log] [blame]
/*
* Copyright (c) 2017 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#if 1
#define SYS_LOG_DOMAIN "https-client"
#define NET_SYS_LOG_LEVEL SYS_LOG_LEVEL_DEBUG
#define NET_LOG_ENABLED 1
#endif
#include <zephyr.h>
#include <errno.h>
#include <net/net_core.h>
#include <net/net_ip.h>
#include <net/http.h>
#include <net/net_app.h>
#include "config.h"
#define INSTANCE_INFO "Zephyr HTTPS example client #1"
#define MAX_ITERATIONS 20
#define WAIT_TIME (APP_REQ_TIMEOUT * 2)
#define RESULT_BUF_SIZE 2048
static u8_t result[RESULT_BUF_SIZE];
/* Note that each HTTPS context needs its own stack as there will be
* a separate thread for each HTTPS context.
*/
NET_STACK_DEFINE(HTTPS_CLIENT, https_stack,
CONFIG_HTTPS_STACK_SIZE, CONFIG_HTTPS_STACK_SIZE);
#define RX_FIFO_DEPTH 4
K_MEM_POOL_DEFINE(ssl_rx_pool, 4, 64, RX_FIFO_DEPTH, 4);
/*
* Note that the http_client_ctx is quite large so be careful if that is
* allocated from stack.
*/
static struct http_client_ctx https_ctx;
#if defined(CONFIG_NET_CONTEXT_NET_PKT_POOL)
NET_PKT_TX_SLAB_DEFINE(http_cli_tls_tx, 15);
NET_PKT_DATA_POOL_DEFINE(http_cli_tls_data, 30);
static struct k_mem_slab *tx_slab(void)
{
return &http_cli_tls_tx;
}
static struct net_buf_pool *data_pool(void)
{
return &http_cli_tls_data;
}
#else
#if defined(CONFIG_NET_L2_BT)
#error "TCP connections over Bluetooth need CONFIG_NET_CONTEXT_NET_PKT_POOL "\
"defined."
#endif /* CONFIG_NET_L2_BT */
#define tx_slab NULL
#define data_pool NULL
#endif /* CONFIG_NET_CONTEXT_NET_PKT_POOL */
struct waiter {
struct http_client_ctx *ctx;
struct k_sem wait;
size_t total_len;
size_t header_len;
};
#include "test_certs.h"
void panic(const char *msg)
{
if (msg) {
NET_ERR("%s", msg);
}
for (;;) {
k_sleep(K_FOREVER);
}
}
/* Load the certificates etc. In this sample app, we verify that
* the server is the test server we are communicating against to.
*/
static int setup_cert(struct http_client_ctx *ctx, void *cert)
{
#if defined(MBEDTLS_KEY_EXCHANGE__SOME__PSK_ENABLED)
mbedtls_ssl_conf_psk(&ctx->https.mbedtls.conf,
client_psk, sizeof(client_psk),
(const unsigned char *)client_psk_id,
sizeof(client_psk_id) - 1);
#endif
#if defined(MBEDTLS_X509_CRT_PARSE_C)
{
mbedtls_x509_crt *ca_cert = cert;
int ret;
ret = mbedtls_x509_crt_parse_der(ca_cert,
ca_certificate,
sizeof(ca_certificate));
if (ret != 0) {
NET_ERR("mbedtls_x509_crt_parse_der failed "
"(-0x%x)", -ret);
return ret;
}
mbedtls_ssl_conf_ca_chain(&ctx->https.mbedtls.conf,
ca_cert, NULL);
mbedtls_ssl_conf_authmode(&ctx->https.mbedtls.conf,
MBEDTLS_SSL_VERIFY_REQUIRED);
mbedtls_ssl_conf_cert_profile(&ctx->https.mbedtls.conf,
&mbedtls_x509_crt_profile_default);
}
#endif /* MBEDTLS_X509_CRT_PARSE_C */
return 0;
}
static int do_sync_http_req(struct http_client_ctx *ctx,
enum http_method method,
const char *url,
const char *content_type,
const char *payload)
{
struct http_client_request req = {};
int ret;
req.method = method;
req.url = url;
req.protocol = " " HTTP_PROTOCOL HTTP_CRLF;
ret = http_client_send_req(ctx, &req, NULL, result, sizeof(result),
NULL, APP_REQ_TIMEOUT);
if (ret < 0) {
NET_ERR("Cannot send %s request (%d)", http_method_str(method),
ret);
goto out;
}
if (ctx->rsp.data_len > sizeof(result)) {
NET_ERR("Result buffer overflow by %zd bytes",
ctx->rsp.data_len - sizeof(result));
ret = -E2BIG;
} else {
NET_INFO("HTTP server response status: %s",
ctx->rsp.http_status);
if (ctx->parser.http_errno) {
if (method == HTTP_OPTIONS) {
/* Ignore error if OPTIONS is not found */
goto out;
}
NET_INFO("HTTP parser status: %s",
http_errno_description(ctx->parser.http_errno));
ret = -EINVAL;
goto out;
}
/* Our example server does not support OPTIONS so check that
* here too.
*/
if (method != HTTP_HEAD && method != HTTP_OPTIONS) {
if (ctx->rsp.body_found) {
NET_INFO("HTTP body: %zd bytes, "
"expected: %zd bytes",
ctx->rsp.processed,
ctx->rsp.content_length);
} else {
NET_ERR("Error detected during HTTP msg "
"processing");
}
}
}
out:
return ret;
}
void response(struct http_client_ctx *ctx,
u8_t *data, size_t buflen,
size_t datalen,
enum http_final_call data_end,
void *user_data)
{
struct waiter *waiter = user_data;
int ret;
if (data_end == HTTP_DATA_MORE) {
NET_INFO("Received %zd bytes piece of data", datalen);
/* Do something with the data here. For this example
* we just ignore the received data.
*/
waiter->total_len += datalen;
if (ctx->rsp.body_start) {
/* This fragment contains the start of the body
* Note that the header length is not proper if
* the header is spanning over multiple recv
* fragments.
*/
waiter->header_len = ctx->rsp.body_start -
ctx->rsp.response_buf;
}
return;
}
waiter->total_len += datalen;
NET_INFO("HTTP server response status: %s", ctx->rsp.http_status);
if (ctx->parser.http_errno) {
if (ctx->req.method == HTTP_OPTIONS) {
/* Ignore error if OPTIONS is not found */
goto out;
}
NET_INFO("HTTP parser status: %s",
http_errno_description(ctx->parser.http_errno));
ret = -EINVAL;
goto out;
}
if (ctx->req.method != HTTP_HEAD && ctx->req.method != HTTP_OPTIONS) {
if (ctx->rsp.body_found) {
NET_INFO("HTTP body: %zd bytes, expected: %zd bytes",
ctx->rsp.processed, ctx->rsp.content_length);
} else {
NET_ERR("Error detected during HTTP msg processing");
}
if (waiter->total_len !=
waiter->header_len + ctx->rsp.content_length) {
NET_ERR("Error while receiving data, "
"received %zd expected %zd bytes",
waiter->total_len, waiter->header_len +
ctx->rsp.content_length);
}
}
out:
k_sem_give(&waiter->wait);
}
static int do_async_http_req(struct http_client_ctx *ctx,
enum http_method method,
const char *url,
const char *content_type,
const char *payload)
{
struct http_client_request req = {};
struct waiter waiter;
int ret;
req.method = method;
req.url = url;
req.protocol = " " HTTP_PROTOCOL HTTP_CRLF;
k_sem_init(&waiter.wait, 0, 1);
waiter.total_len = 0;
ret = http_client_send_req(ctx, &req, response, result, sizeof(result),
&waiter, APP_REQ_TIMEOUT);
if (ret < 0 && ret != -EINPROGRESS) {
NET_ERR("Cannot send %s request (%d)", http_method_str(method),
ret);
goto out;
}
if (k_sem_take(&waiter.wait, WAIT_TIME)) {
NET_ERR("Timeout while waiting HTTP response");
http_client_release(ctx);
ret = -ETIMEDOUT;
goto out;
}
ret = 0;
out:
return ret;
}
static inline int do_sync_reqs(struct http_client_ctx *ctx, int count)
{
int ret;
/* These examples use the HTTP client API synchronously so they
* do not set the callback parameter.
*/
while (count--) {
ret = do_sync_http_req(ctx, HTTP_GET, "/index.html",
NULL, NULL);
if (ret < 0) {
goto out;
}
ret = do_sync_http_req(ctx, HTTP_HEAD, "/",
NULL, NULL);
if (ret < 0) {
goto out;
}
ret = do_sync_http_req(ctx, HTTP_OPTIONS, "/index.html",
NULL, NULL);
if (ret < 0) {
goto out;
}
ret = do_sync_http_req(ctx, HTTP_POST, "/post_test.php",
POST_CONTENT_TYPE, POST_PAYLOAD);
if (ret < 0) {
goto out;
}
/* Note that we cannot receive data bigger than RESULT_BUF_SIZE
* if we wait the buffer synchronously. If you want to receive
* bigger data, then you need to set the callback when sending
* the HTTP request using http_client_send_req()
*/
}
out:
return ret;
}
static inline int do_async_reqs(struct http_client_ctx *ctx, int count)
{
int ret;
/* These examples use the HTTP client API asynchronously so they
* do set the callback parameter.
*/
while (count--) {
ret = do_async_http_req(ctx, HTTP_GET, "/index.html",
NULL, NULL);
if (ret < 0) {
goto out;
}
ret = do_async_http_req(ctx, HTTP_HEAD, "/",
NULL, NULL);
if (ret < 0) {
goto out;
}
ret = do_async_http_req(ctx, HTTP_OPTIONS, "/index.html",
NULL, NULL);
if (ret < 0) {
goto out;
}
ret = do_async_http_req(ctx, HTTP_POST, "/post_test.php",
POST_CONTENT_TYPE, POST_PAYLOAD);
if (ret < 0) {
goto out;
}
/* FIXME: There is a mbedtls SSL buffer size issue if we try to
* receive large amount of data. So disable the big-file.html
* fetch for time being.
*/
if (0) {
ret = do_async_http_req(ctx, HTTP_GET,
"/big-file.html",
NULL, NULL);
if (ret < 0) {
goto out;
}
}
}
out:
return ret;
}
void main(void)
{
bool failure = false;
int ret;
ret = https_client_init(&https_ctx, SERVER_ADDR, SERVER_PORT,
(u8_t *)INSTANCE_INFO, strlen(INSTANCE_INFO),
setup_cert, HOSTNAME, NULL, &ssl_rx_pool,
https_stack, sizeof(https_stack));
if (ret < 0) {
NET_ERR("HTTPS init failed (%d)", ret);
panic(NULL);
}
http_client_set_net_pkt_pool(&https_ctx, tx_slab, data_pool);
ret = do_sync_reqs(&https_ctx, MAX_ITERATIONS);
if (ret < 0) {
failure = true;
}
ret = do_async_reqs(&https_ctx, MAX_ITERATIONS);
if (ret < 0) {
failure = true;
}
if (failure) {
NET_ERR("Some of the tests failed.");
goto out;
}
out:
http_client_release(&https_ctx);
NET_INFO("Done!");
}