net: http: Add HTTPS client support
Add HTTPS support into http-client library. The init of the HTTPS client connection is different compared to HTTP client, but the actual HTTP request sending is using the same API as HTTP client. Signed-off-by: Jukka Rissanen <jukka.rissanen@linux.intel.com>
This commit is contained in:
parent
0d43bbfb77
commit
1b82463dee
2 changed files with 1013 additions and 50 deletions
|
@ -5,10 +5,16 @@
|
|||
*/
|
||||
|
||||
#if defined(CONFIG_NET_DEBUG_HTTP)
|
||||
#if defined(CONFIG_HTTPS)
|
||||
#define SYS_LOG_DOMAIN "https/client"
|
||||
#else
|
||||
#define SYS_LOG_DOMAIN "http/client"
|
||||
#endif
|
||||
#define NET_LOG_ENABLED 1
|
||||
#endif
|
||||
|
||||
#define RX_EXTRA_DEBUG 0
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <misc/printk.h>
|
||||
|
||||
|
@ -18,7 +24,23 @@
|
|||
|
||||
#include <net/http.h>
|
||||
|
||||
#define BUF_ALLOC_TIMEOUT K_SECONDS(1)
|
||||
#if defined(CONFIG_HTTPS)
|
||||
#if defined(MBEDTLS_DEBUG_C)
|
||||
#include <mbedtls/debug.h>
|
||||
/* - Debug levels (from ext/lib/crypto/mbedtls/include/mbedtls/debug.h)
|
||||
* - 0 No debug
|
||||
* - 1 Error
|
||||
* - 2 State change
|
||||
* - 3 Informational
|
||||
* - 4 Verbose
|
||||
*/
|
||||
#define DEBUG_THRESHOLD 0
|
||||
#endif
|
||||
#endif /* CONFIG_HTTPS */
|
||||
|
||||
#define BUF_ALLOC_TIMEOUT 100
|
||||
|
||||
#define HTTPS_STARTUP_TIMEOUT K_SECONDS(5)
|
||||
|
||||
/* HTTP client defines */
|
||||
#define HTTP_EOF "\r\n\r\n"
|
||||
|
@ -31,14 +53,15 @@ struct waiter {
|
|||
struct k_sem wait;
|
||||
};
|
||||
|
||||
int http_request(struct net_context *net_ctx, struct http_client_request *req,
|
||||
int http_request(struct http_client_ctx *ctx,
|
||||
struct http_client_request *req,
|
||||
s32_t timeout)
|
||||
{
|
||||
const char *method = http_method_str(req->method);
|
||||
struct net_pkt *pkt;
|
||||
int ret = -ENOMEM;
|
||||
|
||||
pkt = net_pkt_get_tx(net_ctx, timeout);
|
||||
pkt = net_pkt_get_tx(ctx->tcp.ctx, timeout);
|
||||
if (!pkt) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
@ -129,7 +152,22 @@ int http_request(struct net_context *net_ctx, struct http_client_request *req,
|
|||
}
|
||||
}
|
||||
|
||||
return net_context_send(pkt, NULL, timeout, NULL, NULL);
|
||||
#if defined(CONFIG_NET_IPV6)
|
||||
if (net_pkt_family(pkt) == AF_INET6) {
|
||||
net_pkt_set_appdatalen(pkt, net_pkt_get_len(pkt) -
|
||||
net_pkt_ip_hdr_len(pkt) -
|
||||
net_pkt_ipv6_ext_opt_len(pkt));
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
net_pkt_set_appdatalen(pkt, net_pkt_get_len(pkt) -
|
||||
net_pkt_ip_hdr_len(pkt));
|
||||
}
|
||||
|
||||
ret = ctx->tcp.send_data(pkt, NULL, timeout, NULL, ctx);
|
||||
if (ret == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
out:
|
||||
net_pkt_unref(pkt);
|
||||
|
@ -289,7 +327,7 @@ static int on_headers_complete(struct http_parser *parser)
|
|||
|
||||
static int on_message_begin(struct http_parser *parser)
|
||||
{
|
||||
#if defined(CONFIG_NET_DEBUG_HTTP)
|
||||
#if defined(CONFIG_NET_DEBUG_HTTP) && (CONFIG_SYS_LOG_NET_LEVEL > 2)
|
||||
struct http_client_ctx *ctx = CONTAINER_OF(parser,
|
||||
struct http_client_ctx,
|
||||
parser);
|
||||
|
@ -491,6 +529,12 @@ static int tcp_connect(struct http_client_ctx *ctx)
|
|||
socklen_t addrlen = sizeof(struct sockaddr_in);
|
||||
int ret;
|
||||
|
||||
if (ctx->tcp.ctx && net_context_is_used(ctx->tcp.ctx) &&
|
||||
net_context_get_state(ctx->tcp.ctx) == NET_CONTEXT_CONNECTED) {
|
||||
/* If we are already connected, then just return */
|
||||
return -EALREADY;
|
||||
}
|
||||
|
||||
if (ctx->tcp.remote.family == AF_INET6) {
|
||||
addrlen = sizeof(struct sockaddr_in6);
|
||||
|
||||
|
@ -534,10 +578,11 @@ static int tcp_connect(struct http_client_ctx *ctx)
|
|||
goto out;
|
||||
}
|
||||
|
||||
return net_context_recv(ctx->tcp.ctx, recv_cb, K_NO_WAIT, ctx);
|
||||
return net_context_recv(ctx->tcp.ctx, ctx->tcp.recv_cb, K_NO_WAIT, ctx);
|
||||
|
||||
out:
|
||||
net_context_put(ctx->tcp.ctx);
|
||||
ctx->tcp.ctx = NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -576,6 +621,612 @@ static inline void print_info(struct http_client_ctx *ctx,
|
|||
#endif
|
||||
}
|
||||
|
||||
#if defined(CONFIG_HTTPS)
|
||||
#if defined(MBEDTLS_ERROR_C)
|
||||
#define print_error(fmt, ret) \
|
||||
do { \
|
||||
char error[64]; \
|
||||
\
|
||||
mbedtls_strerror(ret, error, sizeof(error)); \
|
||||
\
|
||||
NET_ERR(fmt " (%s)", -ret, error); \
|
||||
} while (0)
|
||||
#else
|
||||
#define print_error(fmt, ret) NET_ERR(fmt, -ret)
|
||||
#endif /* MBEDTLS_ERROR_C */
|
||||
|
||||
static void ssl_sent(struct net_context *context,
|
||||
int status, void *token, void *user_data)
|
||||
{
|
||||
struct http_client_ctx *http_ctx = user_data;
|
||||
|
||||
k_sem_give(&http_ctx->https.mbedtls.ssl_ctx.tx_sem);
|
||||
}
|
||||
|
||||
/* Send encrypted data */
|
||||
static int ssl_tx(void *context, const unsigned char *buf, size_t size)
|
||||
{
|
||||
struct http_client_ctx *ctx = context;
|
||||
struct net_pkt *send_pkt;
|
||||
int ret, len;
|
||||
|
||||
send_pkt = net_pkt_get_tx(ctx->tcp.ctx, BUF_ALLOC_TIMEOUT);
|
||||
if (!send_pkt) {
|
||||
return MBEDTLS_ERR_SSL_ALLOC_FAILED;
|
||||
}
|
||||
|
||||
ret = net_pkt_append_all(send_pkt, size, (u8_t *)buf,
|
||||
BUF_ALLOC_TIMEOUT);
|
||||
if (!ret) {
|
||||
/* Cannot append data */
|
||||
net_pkt_unref(send_pkt);
|
||||
return MBEDTLS_ERR_SSL_ALLOC_FAILED;
|
||||
}
|
||||
|
||||
len = size;
|
||||
|
||||
ret = net_context_send(send_pkt, ssl_sent, K_NO_WAIT, NULL, ctx);
|
||||
if (ret < 0) {
|
||||
net_pkt_unref(send_pkt);
|
||||
return ret;
|
||||
}
|
||||
|
||||
k_sem_take(&ctx->https.mbedtls.ssl_ctx.tx_sem, K_FOREVER);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
struct rx_fifo_block {
|
||||
sys_snode_t snode;
|
||||
struct k_mem_block block;
|
||||
struct net_pkt *pkt;
|
||||
};
|
||||
|
||||
struct tx_fifo_block {
|
||||
struct k_mem_block block;
|
||||
struct http_client_request *req;
|
||||
};
|
||||
|
||||
/* Receive encrypted data from network. Put that data into fifo
|
||||
* that will be read by https thread.
|
||||
*/
|
||||
static void ssl_received(struct net_context *context,
|
||||
struct net_pkt *pkt,
|
||||
int status,
|
||||
void *user_data)
|
||||
{
|
||||
struct http_client_ctx *http_ctx = user_data;
|
||||
struct rx_fifo_block *rx_data = NULL;
|
||||
struct k_mem_block block;
|
||||
int ret;
|
||||
|
||||
ARG_UNUSED(context);
|
||||
ARG_UNUSED(status);
|
||||
|
||||
if (pkt && !net_pkt_appdatalen(pkt)) {
|
||||
net_pkt_unref(pkt);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = k_mem_pool_alloc(http_ctx->https.pool, &block,
|
||||
sizeof(struct rx_fifo_block),
|
||||
BUF_ALLOC_TIMEOUT);
|
||||
if (ret < 0) {
|
||||
if (pkt) {
|
||||
net_pkt_unref(pkt);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
rx_data = block.data;
|
||||
rx_data->pkt = pkt;
|
||||
|
||||
/* For freeing memory later */
|
||||
memcpy(&rx_data->block, &block, sizeof(struct k_mem_block));
|
||||
|
||||
k_fifo_put(&http_ctx->https.mbedtls.ssl_ctx.rx_fifo, (void *)rx_data);
|
||||
|
||||
/* Let the ssl_rx() to run */
|
||||
k_yield();
|
||||
}
|
||||
|
||||
int ssl_rx(void *context, unsigned char *buf, size_t size)
|
||||
{
|
||||
struct http_client_ctx *ctx = context;
|
||||
struct rx_fifo_block *rx_data;
|
||||
u16_t read_bytes;
|
||||
u8_t *ptr;
|
||||
int pos;
|
||||
int len;
|
||||
int ret = 0;
|
||||
|
||||
if (!ctx->https.mbedtls.ssl_ctx.frag) {
|
||||
rx_data = k_fifo_get(&ctx->https.mbedtls.ssl_ctx.rx_fifo,
|
||||
K_FOREVER);
|
||||
if (!rx_data || !rx_data->pkt) {
|
||||
NET_DBG("Closing %p connection", ctx);
|
||||
|
||||
if (rx_data) {
|
||||
k_mem_pool_free(&rx_data->block);
|
||||
}
|
||||
|
||||
return MBEDTLS_ERR_SSL_CONN_EOF;
|
||||
}
|
||||
|
||||
ctx->https.mbedtls.ssl_ctx.rx_pkt = rx_data->pkt;
|
||||
|
||||
k_mem_pool_free(&rx_data->block);
|
||||
|
||||
read_bytes = net_pkt_appdatalen(
|
||||
ctx->https.mbedtls.ssl_ctx.rx_pkt);
|
||||
|
||||
ctx->https.mbedtls.ssl_ctx.remaining = read_bytes;
|
||||
ctx->https.mbedtls.ssl_ctx.frag =
|
||||
ctx->https.mbedtls.ssl_ctx.rx_pkt->frags;
|
||||
|
||||
ptr = net_pkt_appdata(ctx->https.mbedtls.ssl_ctx.rx_pkt);
|
||||
len = ptr - ctx->https.mbedtls.ssl_ctx.frag->data;
|
||||
|
||||
if (len > ctx->https.mbedtls.ssl_ctx.frag->size) {
|
||||
NET_ERR("Buf overflow (%d > %u)", len,
|
||||
ctx->https.mbedtls.ssl_ctx.frag->size);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* This will get rid of IP header */
|
||||
net_buf_pull(ctx->https.mbedtls.ssl_ctx.frag, len);
|
||||
} else {
|
||||
read_bytes = ctx->https.mbedtls.ssl_ctx.remaining;
|
||||
ptr = ctx->https.mbedtls.ssl_ctx.frag->data;
|
||||
}
|
||||
|
||||
len = ctx->https.mbedtls.ssl_ctx.frag->len;
|
||||
pos = 0;
|
||||
if (read_bytes > size) {
|
||||
while (ctx->https.mbedtls.ssl_ctx.frag) {
|
||||
read_bytes = len < (size - pos) ? len : (size - pos);
|
||||
|
||||
#if RX_EXTRA_DEBUG == 1
|
||||
NET_DBG("Copying %d bytes", read_bytes);
|
||||
#endif
|
||||
|
||||
memcpy(buf + pos, ptr, read_bytes);
|
||||
|
||||
pos += read_bytes;
|
||||
if (pos < size) {
|
||||
ctx->https.mbedtls.ssl_ctx.frag =
|
||||
ctx->https.mbedtls.ssl_ctx.frag->frags;
|
||||
ptr = ctx->https.mbedtls.ssl_ctx.frag->data;
|
||||
len = ctx->https.mbedtls.ssl_ctx.frag->len;
|
||||
} else {
|
||||
if (read_bytes == len) {
|
||||
ctx->https.mbedtls.ssl_ctx.frag =
|
||||
ctx->https.mbedtls.ssl_ctx.frag->frags;
|
||||
} else {
|
||||
net_buf_pull(
|
||||
ctx->https.mbedtls.ssl_ctx.frag,
|
||||
read_bytes);
|
||||
}
|
||||
|
||||
ctx->https.mbedtls.ssl_ctx.remaining -= size;
|
||||
return size;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
while (ctx->https.mbedtls.ssl_ctx.frag) {
|
||||
#if RX_EXTRA_DEBUG == 1
|
||||
NET_DBG("Copying all %d bytes", len);
|
||||
#endif
|
||||
|
||||
memcpy(buf + pos, ptr, len);
|
||||
|
||||
pos += len;
|
||||
ctx->https.mbedtls.ssl_ctx.frag =
|
||||
ctx->https.mbedtls.ssl_ctx.frag->frags;
|
||||
if (!ctx->https.mbedtls.ssl_ctx.frag) {
|
||||
break;
|
||||
}
|
||||
|
||||
ptr = ctx->https.mbedtls.ssl_ctx.frag->data;
|
||||
len = ctx->https.mbedtls.ssl_ctx.frag->len;
|
||||
}
|
||||
|
||||
net_pkt_unref(ctx->https.mbedtls.ssl_ctx.rx_pkt);
|
||||
ctx->https.mbedtls.ssl_ctx.rx_pkt = NULL;
|
||||
ctx->https.mbedtls.ssl_ctx.frag = NULL;
|
||||
ctx->https.mbedtls.ssl_ctx.remaining = 0;
|
||||
|
||||
if (read_bytes != pos) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
ret = read_bytes;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if defined(MBEDTLS_DEBUG_C) && defined(CONFIG_NET_DEBUG_HTTP)
|
||||
static void my_debug(void *ctx, int level,
|
||||
const char *file, int line, const char *str)
|
||||
{
|
||||
const char *p, *basename;
|
||||
int len;
|
||||
|
||||
ARG_UNUSED(ctx);
|
||||
|
||||
/* Extract basename from file */
|
||||
for (p = basename = file; *p != '\0'; p++) {
|
||||
if (*p == '/' || *p == '\\') {
|
||||
basename = p + 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Avoid printing double newlines */
|
||||
len = strlen(str);
|
||||
if (str[len - 1] == '\n') {
|
||||
((char *)str)[len - 1] = '\0';
|
||||
}
|
||||
|
||||
NET_DBG("%s:%04d: |%d| %s", basename, line, level, str);
|
||||
}
|
||||
#endif /* MBEDTLS_DEBUG_C && CONFIG_NET_DEBUG_HTTP */
|
||||
|
||||
static int entropy_source(void *data, unsigned char *output, size_t len,
|
||||
size_t *olen)
|
||||
{
|
||||
u32_t seed;
|
||||
|
||||
ARG_UNUSED(data);
|
||||
|
||||
seed = sys_rand32_get();
|
||||
|
||||
if (len > sizeof(seed)) {
|
||||
len = sizeof(seed);
|
||||
}
|
||||
|
||||
memcpy(output, &seed, len);
|
||||
|
||||
*olen = len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int https_init(struct http_client_ctx *ctx)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
k_sem_init(&ctx->https.mbedtls.ssl_ctx.tx_sem, 0, UINT_MAX);
|
||||
k_fifo_init(&ctx->https.mbedtls.ssl_ctx.rx_fifo);
|
||||
k_fifo_init(&ctx->https.mbedtls.ssl_ctx.tx_fifo);
|
||||
|
||||
mbedtls_platform_set_printf(printk);
|
||||
|
||||
http_heap_init();
|
||||
|
||||
mbedtls_ssl_init(&ctx->https.mbedtls.ssl);
|
||||
mbedtls_ssl_config_init(&ctx->https.mbedtls.conf);
|
||||
mbedtls_entropy_init(&ctx->https.mbedtls.entropy);
|
||||
mbedtls_ctr_drbg_init(&ctx->https.mbedtls.ctr_drbg);
|
||||
|
||||
#if defined(MBEDTLS_X509_CRT_PARSE_C)
|
||||
mbedtls_x509_crt_init(&ctx->https.mbedtls.ca_cert);
|
||||
#endif
|
||||
|
||||
#if defined(MBEDTLS_DEBUG_C) && defined(CONFIG_NET_DEBUG_HTTP)
|
||||
mbedtls_debug_set_threshold(DEBUG_THRESHOLD);
|
||||
mbedtls_ssl_conf_dbg(&ctx->https.mbedtls.conf, my_debug, NULL);
|
||||
#endif
|
||||
|
||||
/* Seed the RNG */
|
||||
mbedtls_entropy_add_source(&ctx->https.mbedtls.entropy,
|
||||
ctx->https.mbedtls.entropy_src_cb,
|
||||
NULL,
|
||||
MBEDTLS_ENTROPY_MAX_GATHER,
|
||||
MBEDTLS_ENTROPY_SOURCE_STRONG);
|
||||
|
||||
ret = mbedtls_ctr_drbg_seed(
|
||||
&ctx->https.mbedtls.ctr_drbg,
|
||||
mbedtls_entropy_func,
|
||||
&ctx->https.mbedtls.entropy,
|
||||
(const unsigned char *)ctx->https.mbedtls.personalization_data,
|
||||
ctx->https.mbedtls.personalization_data_len);
|
||||
if (ret != 0) {
|
||||
print_error("mbedtls_ctr_drbg_seed returned -0x%x", ret);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Setup SSL defaults etc. */
|
||||
ret = mbedtls_ssl_config_defaults(&ctx->https.mbedtls.conf,
|
||||
MBEDTLS_SSL_IS_CLIENT,
|
||||
MBEDTLS_SSL_TRANSPORT_STREAM,
|
||||
MBEDTLS_SSL_PRESET_DEFAULT);
|
||||
if (ret != 0) {
|
||||
print_error("mbedtls_ssl_config_defaults returned -0x%x", ret);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
mbedtls_ssl_conf_rng(&ctx->https.mbedtls.conf,
|
||||
mbedtls_ctr_drbg_random,
|
||||
&ctx->https.mbedtls.ctr_drbg);
|
||||
|
||||
/* Load the certificates and other related stuff. This needs to be done
|
||||
* by the user so we call a callback that user must have provided.
|
||||
*/
|
||||
ret = ctx->https.mbedtls.cert_cb(ctx, &ctx->https.mbedtls.ca_cert);
|
||||
if (ret != 0) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
ret = mbedtls_ssl_setup(&ctx->https.mbedtls.ssl,
|
||||
&ctx->https.mbedtls.conf);
|
||||
if (ret != 0) {
|
||||
NET_ERR("mbedtls_ssl_setup returned -0x%x", ret);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
#if defined(MBEDTLS_X509_CRT_PARSE_C)
|
||||
if (ctx->https.cert_host) {
|
||||
ret = mbedtls_ssl_set_hostname(&ctx->https.mbedtls.ssl,
|
||||
ctx->https.cert_host);
|
||||
if (ret != 0) {
|
||||
print_error("mbedtls_ssl_set_hostname returned -0x%x",
|
||||
ret);
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
NET_DBG("SSL setup done");
|
||||
|
||||
/* The HTTPS thread is started do initiate HTTPS handshake etc when
|
||||
* the first HTTP request is being done.
|
||||
*/
|
||||
|
||||
exit:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void https_handler(struct http_client_ctx *ctx,
|
||||
struct k_sem *startup_sync)
|
||||
{
|
||||
struct tx_fifo_block *tx_data;
|
||||
struct http_client_request req;
|
||||
size_t len;
|
||||
int ret;
|
||||
|
||||
/* First mbedtls specific initialization */
|
||||
ret = https_init(ctx);
|
||||
|
||||
k_sem_give(startup_sync);
|
||||
|
||||
if (ret < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
reset:
|
||||
http_parser_init(&ctx->parser, HTTP_RESPONSE);
|
||||
ctx->rsp.data_len = 0;
|
||||
|
||||
/* Wait that the sender sends the data, and the peer to respond to.
|
||||
*/
|
||||
tx_data = k_fifo_get(&ctx->https.mbedtls.ssl_ctx.tx_fifo, K_FOREVER);
|
||||
if (tx_data) {
|
||||
/* Because the req pointer might disappear as it is controlled
|
||||
* by application, copy the data here.
|
||||
*/
|
||||
memcpy(&req, tx_data->req, sizeof(req));
|
||||
} else {
|
||||
NET_ASSERT(tx_data);
|
||||
goto reset;
|
||||
}
|
||||
|
||||
print_info(ctx, ctx->req.method);
|
||||
|
||||
/* If the connection is not active, then re-connect */
|
||||
ret = tcp_connect(ctx);
|
||||
if (ret < 0 && ret != -EALREADY) {
|
||||
k_sem_give(&ctx->req.wait);
|
||||
goto reset;
|
||||
}
|
||||
|
||||
mbedtls_ssl_session_reset(&ctx->https.mbedtls.ssl);
|
||||
mbedtls_ssl_set_bio(&ctx->https.mbedtls.ssl, ctx, ssl_tx,
|
||||
ssl_rx, NULL);
|
||||
|
||||
/* SSL handshake. The ssl_rx() function will be called next by
|
||||
* mbedtls library. The ssl_rx() will block and wait that data is
|
||||
* received by ssl_received() and passed to it via fifo. After
|
||||
* receiving the data, this function will then proceed with secure
|
||||
* connection establishment.
|
||||
*/
|
||||
/* Waiting SSL handshake */
|
||||
do {
|
||||
ret = mbedtls_ssl_handshake(&ctx->https.mbedtls.ssl);
|
||||
if (ret != MBEDTLS_ERR_SSL_WANT_READ &&
|
||||
ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
|
||||
if (ret == MBEDTLS_ERR_SSL_CONN_EOF) {
|
||||
goto close;
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
print_error("mbedtls_ssl_handshake returned "
|
||||
"-0x%x", ret);
|
||||
goto close;
|
||||
}
|
||||
}
|
||||
} while (ret != 0);
|
||||
|
||||
ret = http_request(ctx, &req, BUF_ALLOC_TIMEOUT);
|
||||
|
||||
k_mem_pool_free(&tx_data->block);
|
||||
|
||||
if (ret < 0) {
|
||||
NET_DBG("Send error (%d)", ret);
|
||||
goto close;
|
||||
}
|
||||
|
||||
NET_DBG("Read HTTPS response");
|
||||
|
||||
do {
|
||||
len = ctx->rsp.response_buf_len - 1;
|
||||
memset(ctx->rsp.response_buf, 0, ctx->rsp.response_buf_len);
|
||||
|
||||
ret = mbedtls_ssl_read(&ctx->https.mbedtls.ssl,
|
||||
ctx->rsp.response_buf, len);
|
||||
if (ret == 0) {
|
||||
goto close;
|
||||
}
|
||||
|
||||
if (ret == MBEDTLS_ERR_SSL_WANT_READ ||
|
||||
ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
|
||||
NET_DBG("Connection was closed gracefully");
|
||||
goto close;
|
||||
}
|
||||
|
||||
if (ret == MBEDTLS_ERR_NET_CONN_RESET) {
|
||||
NET_DBG("Connection was reset by peer");
|
||||
goto close;
|
||||
}
|
||||
|
||||
if (ret == -EIO) {
|
||||
NET_DBG("Response received, waiting another ctx %p",
|
||||
ctx);
|
||||
goto next;
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
print_error("mbedtls_ssl_read returned -0x%x", ret);
|
||||
goto close;
|
||||
}
|
||||
|
||||
/* The data_len will count how many bytes we have read,
|
||||
* this value is passed to user supplied response callback
|
||||
* by on_body() and on_message_complete() functions.
|
||||
*/
|
||||
ctx->rsp.data_len += ret;
|
||||
|
||||
ret = http_parser_execute(&ctx->parser,
|
||||
&ctx->settings,
|
||||
ctx->rsp.response_buf,
|
||||
ret);
|
||||
if (!ret) {
|
||||
goto close;
|
||||
}
|
||||
|
||||
ctx->rsp.data_len = 0;
|
||||
|
||||
if (ret > 0) {
|
||||
/* Get more data */
|
||||
ret = MBEDTLS_ERR_SSL_WANT_READ;
|
||||
}
|
||||
} while (ret < 0);
|
||||
|
||||
close:
|
||||
/* If there is any pending data that have not been processed yet,
|
||||
* we need to free it here.
|
||||
*/
|
||||
if (ctx->https.mbedtls.ssl_ctx.rx_pkt) {
|
||||
net_pkt_unref(ctx->https.mbedtls.ssl_ctx.rx_pkt);
|
||||
ctx->https.mbedtls.ssl_ctx.rx_pkt = NULL;
|
||||
ctx->https.mbedtls.ssl_ctx.frag = NULL;
|
||||
}
|
||||
|
||||
NET_DBG("Resetting HTTPS connection %p", ctx);
|
||||
|
||||
tcp_disconnect(ctx);
|
||||
|
||||
next:
|
||||
mbedtls_ssl_close_notify(&ctx->https.mbedtls.ssl);
|
||||
|
||||
goto reset;
|
||||
}
|
||||
|
||||
static void https_shutdown(struct http_client_ctx *ctx)
|
||||
{
|
||||
if (!ctx->https.tid) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Empty the fifo just in case there is any received packets
|
||||
* still there.
|
||||
*/
|
||||
while (1) {
|
||||
struct rx_fifo_block *rx_data;
|
||||
|
||||
rx_data = k_fifo_get(&ctx->https.mbedtls.ssl_ctx.rx_fifo,
|
||||
K_NO_WAIT);
|
||||
if (!rx_data) {
|
||||
break;
|
||||
}
|
||||
|
||||
net_pkt_unref(rx_data->pkt);
|
||||
|
||||
k_mem_pool_free(&rx_data->block);
|
||||
}
|
||||
|
||||
k_fifo_cancel_wait(&ctx->https.mbedtls.ssl_ctx.rx_fifo);
|
||||
|
||||
/* Let the ssl_rx() run if there is anything there waiting */
|
||||
k_yield();
|
||||
|
||||
mbedtls_ssl_close_notify(&ctx->https.mbedtls.ssl);
|
||||
mbedtls_ssl_free(&ctx->https.mbedtls.ssl);
|
||||
mbedtls_ssl_config_free(&ctx->https.mbedtls.conf);
|
||||
mbedtls_ctr_drbg_free(&ctx->https.mbedtls.ctr_drbg);
|
||||
mbedtls_entropy_free(&ctx->https.mbedtls.entropy);
|
||||
|
||||
#if defined(MBEDTLS_X509_CRT_PARSE_C)
|
||||
mbedtls_x509_crt_free(&ctx->https.mbedtls.ca_cert);
|
||||
#endif
|
||||
|
||||
tcp_disconnect(ctx);
|
||||
|
||||
NET_DBG("HTTPS thread %p stopped for %p", ctx->https.tid, ctx);
|
||||
|
||||
k_thread_abort(ctx->https.tid);
|
||||
ctx->https.tid = 0;
|
||||
}
|
||||
|
||||
static int start_https(struct http_client_ctx *ctx)
|
||||
{
|
||||
struct k_sem startup_sync;
|
||||
|
||||
/* Start the thread that handles HTTPS traffic. */
|
||||
if (ctx->https.tid) {
|
||||
return -EALREADY;
|
||||
}
|
||||
|
||||
NET_DBG("Starting HTTPS thread for %p", ctx);
|
||||
|
||||
k_sem_init(&startup_sync, 0, 1);
|
||||
|
||||
ctx->https.tid = k_thread_create(&ctx->https.thread,
|
||||
ctx->https.stack,
|
||||
ctx->https.stack_size,
|
||||
(k_thread_entry_t)https_handler,
|
||||
ctx, &startup_sync, 0,
|
||||
K_PRIO_COOP(7), 0, 0);
|
||||
|
||||
/* Wait until we know that the HTTPS thread startup was ok */
|
||||
if (k_sem_take(&startup_sync, HTTPS_STARTUP_TIMEOUT) < 0) {
|
||||
https_shutdown(ctx);
|
||||
return -ECANCELED;
|
||||
}
|
||||
|
||||
NET_DBG("HTTPS thread %p started for %p", ctx->https.tid, ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
#define start_https(...) 0
|
||||
#endif /* CONFIG_HTTPS */
|
||||
|
||||
int http_client_send_req(struct http_client_ctx *ctx,
|
||||
struct http_client_request *req,
|
||||
http_response_cb_t cb,
|
||||
|
@ -590,12 +1241,18 @@ int http_client_send_req(struct http_client_ctx *ctx,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
ctx->rsp.response_buf = response_buf;
|
||||
ctx->rsp.response_buf_len = response_buf_len;
|
||||
|
||||
client_reset(ctx);
|
||||
|
||||
ret = tcp_connect(ctx);
|
||||
if (ret) {
|
||||
NET_DBG("TCP connect error (%d)", ret);
|
||||
return ret;
|
||||
/* HTTPS connection is established in https_handler() */
|
||||
if (!ctx->is_https) {
|
||||
ret = tcp_connect(ctx);
|
||||
if (ret < 0 && ret != -EALREADY) {
|
||||
NET_DBG("TCP connect error (%d)", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (!req->host) {
|
||||
|
@ -607,15 +1264,55 @@ int http_client_send_req(struct http_client_ctx *ctx,
|
|||
ctx->req.user_data = user_data;
|
||||
|
||||
ctx->rsp.cb = cb;
|
||||
ctx->rsp.response_buf = response_buf;
|
||||
ctx->rsp.response_buf_len = response_buf_len;
|
||||
|
||||
print_info(ctx, ctx->req.method);
|
||||
#if defined(CONFIG_HTTPS)
|
||||
if (ctx->is_https) {
|
||||
struct tx_fifo_block *tx_data;
|
||||
struct k_mem_block block;
|
||||
|
||||
ret = http_request(ctx->tcp.ctx, req, BUF_ALLOC_TIMEOUT);
|
||||
if (ret) {
|
||||
NET_DBG("Send error (%d)", ret);
|
||||
goto out;
|
||||
ret = start_https(ctx);
|
||||
if (ret != 0 && ret != -EALREADY) {
|
||||
NET_ERR("HTTPS init failed (%d)", ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = k_mem_pool_alloc(ctx->https.pool, &block,
|
||||
sizeof(struct tx_fifo_block),
|
||||
BUF_ALLOC_TIMEOUT);
|
||||
if (ret < 0) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
tx_data = block.data;
|
||||
tx_data->req = req;
|
||||
|
||||
memcpy(&tx_data->block, &block, sizeof(struct k_mem_block));
|
||||
|
||||
/* We need to pass the HTTPS request to HTTPS thread because
|
||||
* of the mbedtls API stack size requirements.
|
||||
*/
|
||||
k_fifo_put(&ctx->https.mbedtls.ssl_ctx.tx_fifo,
|
||||
(void *)tx_data);
|
||||
|
||||
/* Let the https_handler() to start to process the message.
|
||||
*
|
||||
* Note that if the timeout > 0 or is K_FOREVER, then this
|
||||
* yield is not really necessary as the k_sem_take() will
|
||||
* let the https handler thread to run. But if the timeout
|
||||
* is K_NO_WAIT, then we need to let the https handler to
|
||||
* run now.
|
||||
*/
|
||||
k_yield();
|
||||
} else
|
||||
#endif /* CONFIG_HTTPS */
|
||||
{
|
||||
print_info(ctx, ctx->req.method);
|
||||
|
||||
ret = http_request(ctx, req, BUF_ALLOC_TIMEOUT);
|
||||
if (ret < 0) {
|
||||
NET_DBG("Send error (%d)", ret);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (timeout != 0 && k_sem_take(&ctx->req.wait, timeout)) {
|
||||
|
@ -843,19 +1540,167 @@ int http_client_init(struct http_client_ctx *ctx,
|
|||
|
||||
ctx->tcp.receive_cb = http_receive_cb;
|
||||
ctx->tcp.timeout = HTTP_NETWORK_TIMEOUT;
|
||||
ctx->tcp.send_data = net_context_send;
|
||||
ctx->tcp.recv_cb = recv_cb;
|
||||
|
||||
k_sem_init(&ctx->req.wait, 0, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_HTTPS)
|
||||
/* This gets plain data and it sends encrypted one to peer */
|
||||
static int https_send(struct net_pkt *pkt,
|
||||
net_context_send_cb_t cb,
|
||||
s32_t timeout,
|
||||
void *token,
|
||||
void *user_data)
|
||||
{
|
||||
struct http_client_ctx *ctx = user_data;
|
||||
int ret;
|
||||
u16_t len;
|
||||
|
||||
if (!ctx->rsp.response_buf || ctx->rsp.response_buf_len == 0) {
|
||||
NET_DBG("Response buf not setup");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
len = net_pkt_appdatalen(pkt);
|
||||
if (len == 0) {
|
||||
NET_DBG("No application data to send");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = net_frag_linearize(ctx->rsp.response_buf,
|
||||
ctx->rsp.response_buf_len,
|
||||
pkt,
|
||||
net_pkt_ip_hdr_len(pkt) +
|
||||
net_pkt_ipv6_ext_opt_len(pkt),
|
||||
len);
|
||||
if (ret < 0) {
|
||||
NET_DBG("Cannot linearize send data (%d)", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (ret != len) {
|
||||
NET_DBG("Linear copy error (%u vs %d)", len, ret);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
do {
|
||||
ret = mbedtls_ssl_write(&ctx->https.mbedtls.ssl,
|
||||
ctx->rsp.response_buf, len);
|
||||
if (ret == MBEDTLS_ERR_NET_CONN_RESET) {
|
||||
NET_ERR("peer closed the connection -0x%x", ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (ret != MBEDTLS_ERR_SSL_WANT_READ &&
|
||||
ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
|
||||
if (ret < 0) {
|
||||
print_error("mbedtls_ssl_write returned -0x%x",
|
||||
ret);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
} while (ret <= 0);
|
||||
|
||||
out:
|
||||
if (cb) {
|
||||
cb(net_pkt_context(pkt), ret, token, user_data);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int https_client_init(struct http_client_ctx *ctx,
|
||||
const char *server, u16_t server_port,
|
||||
u8_t *personalization_data,
|
||||
size_t personalization_data_len,
|
||||
https_ca_cert_cb_t cert_cb,
|
||||
const char *cert_host,
|
||||
https_entropy_src_cb_t entropy_src_cb,
|
||||
struct k_mem_pool *pool,
|
||||
u8_t *https_stack,
|
||||
size_t https_stack_size)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!cert_cb) {
|
||||
NET_ERR("Cert callback must be set");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
|
||||
if (server) {
|
||||
ret = set_remote_addr(ctx, server, server_port);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ctx->tcp.local.family = ctx->tcp.remote.family;
|
||||
ctx->server = server;
|
||||
}
|
||||
|
||||
k_sem_init(&ctx->req.wait, 0, 1);
|
||||
|
||||
ctx->is_https = true;
|
||||
|
||||
ctx->settings.on_body = on_body;
|
||||
ctx->settings.on_chunk_complete = on_chunk_complete;
|
||||
ctx->settings.on_chunk_header = on_chunk_header;
|
||||
ctx->settings.on_headers_complete = on_headers_complete;
|
||||
ctx->settings.on_header_field = on_header_field;
|
||||
ctx->settings.on_header_value = on_header_value;
|
||||
ctx->settings.on_message_begin = on_message_begin;
|
||||
ctx->settings.on_message_complete = on_message_complete;
|
||||
ctx->settings.on_status = on_status;
|
||||
ctx->settings.on_url = on_url;
|
||||
|
||||
ctx->tcp.timeout = HTTP_NETWORK_TIMEOUT;
|
||||
ctx->tcp.send_data = https_send;
|
||||
ctx->tcp.recv_cb = ssl_received;
|
||||
|
||||
ctx->https.cert_host = cert_host;
|
||||
ctx->https.stack = https_stack;
|
||||
ctx->https.stack_size = https_stack_size;
|
||||
ctx->https.mbedtls.cert_cb = cert_cb;
|
||||
ctx->https.pool = pool;
|
||||
ctx->https.mbedtls.personalization_data = personalization_data;
|
||||
ctx->https.mbedtls.personalization_data_len = personalization_data_len;
|
||||
|
||||
if (entropy_src_cb) {
|
||||
ctx->https.mbedtls.entropy_src_cb = entropy_src_cb;
|
||||
} else {
|
||||
ctx->https.mbedtls.entropy_src_cb = entropy_source;
|
||||
}
|
||||
|
||||
/* The mbedtls is initialized in HTTPS thread because of mbedtls stack
|
||||
* requirements.
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
#endif /* CONFIG_HTTPS */
|
||||
|
||||
void http_client_release(struct http_client_ctx *ctx)
|
||||
{
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
net_context_put(ctx->tcp.ctx);
|
||||
#if defined(CONFIG_HTTPS)
|
||||
if (ctx->is_https) {
|
||||
https_shutdown(ctx);
|
||||
}
|
||||
#endif /* CONFIG_HTTPS */
|
||||
|
||||
/* https_shutdown() might have released the context already */
|
||||
if (ctx->tcp.ctx) {
|
||||
net_context_put(ctx->tcp.ctx);
|
||||
ctx->tcp.ctx = NULL;
|
||||
}
|
||||
|
||||
ctx->tcp.receive_cb = NULL;
|
||||
ctx->rsp.cb = NULL;
|
||||
k_sem_give(&ctx->req.wait);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue