The original commit 8ebaf29927 ("net: http: dont timeout
on HTTP requests w/o body") was intended to handle a case
where an HTTP response had been retrieved from the server but
the HTTP parser couldn't meet the criteria for calling
"on_message_complete". For example, a POST to a REST API
where the server doesn't return anything but an HTTP
status code.
It was a really bad idea to check a semaphore count. There
is a lot of kernel logic built into semaphores and how the
count is adjusted. The assumption that the value is 0
after the k_sem_give() is incorrect. It's STILL 0 if
something is pending with a k_sem_take(). By the time
k_sem_give() is done executing the other thread has now
been kicked and the count is back to 0.
This caused the original check to always pass and in turn
breakage was noticed in the http_client sample.
Let's do this the right way by setting a flag when
on_message_complete is called and if that flag is not set
by the time we reach recv_cb, let's give back the semaphore
to avoid a timeout.
Jira: ZEP-2561
Signed-off-by: Michael Scott <michael.scott@linaro.org>
1775 lines
40 KiB
C
1775 lines
40 KiB
C
/*
|
|
* Copyright (c) 2017 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#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>
|
|
|
|
#include <net/net_core.h>
|
|
#include <net/net_pkt.h>
|
|
#include <net/dns_resolve.h>
|
|
|
|
#include <net/http.h>
|
|
|
|
#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"
|
|
|
|
#define HTTP_HOST "Host: "
|
|
#define HTTP_CONTENT_TYPE "Content-Type: "
|
|
#define HTTP_CONT_LEN_SIZE 64
|
|
|
|
/* Default network activity timeout in seconds */
|
|
#define HTTP_NETWORK_TIMEOUT K_SECONDS(CONFIG_HTTP_CLIENT_NETWORK_TIMEOUT)
|
|
|
|
struct waiter {
|
|
struct http_client_ctx *ctx;
|
|
struct k_sem wait;
|
|
};
|
|
|
|
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(ctx->tcp.ctx, timeout);
|
|
if (!pkt) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if (!net_pkt_append_all(pkt, strlen(method), (u8_t *)method,
|
|
timeout)) {
|
|
goto out;
|
|
}
|
|
|
|
/* Space after method string. */
|
|
if (!net_pkt_append_all(pkt, 1, (u8_t *)" ", timeout)) {
|
|
goto out;
|
|
}
|
|
|
|
if (!net_pkt_append_all(pkt, strlen(req->url), (u8_t *)req->url,
|
|
timeout)) {
|
|
goto out;
|
|
}
|
|
|
|
if (!net_pkt_append_all(pkt, strlen(req->protocol),
|
|
(u8_t *)req->protocol, timeout)) {
|
|
goto out;
|
|
}
|
|
|
|
if (req->host) {
|
|
if (!net_pkt_append_all(pkt, strlen(HTTP_HOST),
|
|
(u8_t *)HTTP_HOST, timeout)) {
|
|
goto out;
|
|
}
|
|
|
|
if (!net_pkt_append_all(pkt, strlen(req->host),
|
|
(u8_t *)req->host, timeout)) {
|
|
goto out;
|
|
}
|
|
|
|
if (!net_pkt_append_all(pkt, strlen(HTTP_CRLF),
|
|
(u8_t *)HTTP_CRLF, timeout)) {
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (req->header_fields) {
|
|
if (!net_pkt_append_all(pkt, strlen(req->header_fields),
|
|
(u8_t *)req->header_fields,
|
|
timeout)) {
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (req->content_type_value) {
|
|
if (!net_pkt_append_all(pkt, strlen(HTTP_CONTENT_TYPE),
|
|
(u8_t *)HTTP_CONTENT_TYPE,
|
|
timeout)) {
|
|
goto out;
|
|
}
|
|
|
|
if (!net_pkt_append_all(pkt, strlen(req->content_type_value),
|
|
(u8_t *)req->content_type_value,
|
|
timeout)) {
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (req->payload && req->payload_size) {
|
|
char content_len_str[HTTP_CONT_LEN_SIZE];
|
|
|
|
ret = snprintk(content_len_str, HTTP_CONT_LEN_SIZE,
|
|
HTTP_CRLF "Content-Length: %u"
|
|
HTTP_CRLF HTTP_CRLF,
|
|
req->payload_size);
|
|
if (ret <= 0 || ret >= HTTP_CONT_LEN_SIZE) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
if (!net_pkt_append_all(pkt, ret, (u8_t *)content_len_str,
|
|
timeout)) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
if (!net_pkt_append_all(pkt, req->payload_size,
|
|
(u8_t *)req->payload,
|
|
timeout)) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
} else {
|
|
if (!net_pkt_append_all(pkt, strlen(HTTP_EOF),
|
|
(u8_t *)HTTP_EOF,
|
|
timeout)) {
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
#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);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void print_header_field(size_t len, const char *str)
|
|
{
|
|
#if defined(CONFIG_NET_DEBUG_HTTP)
|
|
#define MAX_OUTPUT_LEN 128
|
|
char output[MAX_OUTPUT_LEN];
|
|
|
|
/* The value of len does not count \0 so we need to increase it
|
|
* by one.
|
|
*/
|
|
if ((len + 1) > sizeof(output)) {
|
|
len = sizeof(output) - 1;
|
|
}
|
|
|
|
snprintk(output, len + 1, "%s", str);
|
|
|
|
NET_DBG("[%zd] %s", len, output);
|
|
#endif
|
|
}
|
|
|
|
static int on_url(struct http_parser *parser, const char *at, size_t length)
|
|
{
|
|
ARG_UNUSED(parser);
|
|
|
|
print_header_field(length, at);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int on_status(struct http_parser *parser, const char *at, size_t length)
|
|
{
|
|
struct http_client_ctx *ctx;
|
|
u16_t len;
|
|
|
|
ctx = CONTAINER_OF(parser, struct http_client_ctx, parser);
|
|
len = min(length, sizeof(ctx->rsp.http_status) - 1);
|
|
memcpy(ctx->rsp.http_status, at, len);
|
|
ctx->rsp.http_status[len] = 0;
|
|
|
|
NET_DBG("HTTP response status %s", ctx->rsp.http_status);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int on_header_field(struct http_parser *parser, const char *at,
|
|
size_t length)
|
|
{
|
|
char *content_len = "Content-Length";
|
|
struct http_client_ctx *ctx;
|
|
u16_t len;
|
|
|
|
ctx = CONTAINER_OF(parser, struct http_client_ctx, parser);
|
|
|
|
len = strlen(content_len);
|
|
if (length >= len && memcmp(at, content_len, len) == 0) {
|
|
ctx->rsp.cl_present = true;
|
|
}
|
|
|
|
print_header_field(length, at);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define MAX_NUM_DIGITS 16
|
|
|
|
static int on_header_value(struct http_parser *parser, const char *at,
|
|
size_t length)
|
|
{
|
|
struct http_client_ctx *ctx;
|
|
char str[MAX_NUM_DIGITS];
|
|
|
|
ctx = CONTAINER_OF(parser, struct http_client_ctx, parser);
|
|
|
|
if (ctx->rsp.cl_present) {
|
|
if (length <= MAX_NUM_DIGITS - 1) {
|
|
long int num;
|
|
|
|
memcpy(str, at, length);
|
|
str[length] = 0;
|
|
num = strtol(str, NULL, 10);
|
|
if (num == LONG_MIN || num == LONG_MAX) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
ctx->rsp.content_length = num;
|
|
}
|
|
|
|
ctx->rsp.cl_present = false;
|
|
}
|
|
|
|
print_header_field(length, at);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int on_body(struct http_parser *parser, const char *at, size_t length)
|
|
{
|
|
struct http_client_ctx *ctx = CONTAINER_OF(parser,
|
|
struct http_client_ctx,
|
|
parser);
|
|
|
|
ctx->rsp.body_found = 1;
|
|
ctx->rsp.processed += length;
|
|
|
|
NET_DBG("Processed %zd length %zd", ctx->rsp.processed, length);
|
|
|
|
if (!ctx->rsp.body_start) {
|
|
ctx->rsp.body_start = (u8_t *)at;
|
|
}
|
|
|
|
if (ctx->rsp.cb) {
|
|
NET_DBG("Calling callback for partitioned %zd len data",
|
|
ctx->rsp.data_len);
|
|
|
|
ctx->rsp.cb(ctx,
|
|
ctx->rsp.response_buf,
|
|
ctx->rsp.response_buf_len,
|
|
ctx->rsp.data_len,
|
|
HTTP_DATA_MORE,
|
|
ctx->req.user_data);
|
|
|
|
/* Re-use the result buffer and start to fill it again */
|
|
ctx->rsp.data_len = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int on_headers_complete(struct http_parser *parser)
|
|
{
|
|
struct http_client_ctx *ctx = CONTAINER_OF(parser,
|
|
struct http_client_ctx,
|
|
parser);
|
|
|
|
if (parser->status_code >= 500 && parser->status_code < 600) {
|
|
NET_DBG("Status %d, skipping body", parser->status_code);
|
|
|
|
return 1;
|
|
}
|
|
|
|
if ((ctx->req.method == HTTP_HEAD || ctx->req.method == HTTP_OPTIONS)
|
|
&& ctx->rsp.content_length > 0) {
|
|
NET_DBG("No body expected");
|
|
return 1;
|
|
}
|
|
|
|
NET_DBG("Headers complete");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int on_message_begin(struct http_parser *parser)
|
|
{
|
|
#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);
|
|
|
|
NET_DBG("-- HTTP %s response (headers) --",
|
|
http_method_str(ctx->req.method));
|
|
#else
|
|
ARG_UNUSED(parser);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static int on_message_complete(struct http_parser *parser)
|
|
{
|
|
struct http_client_ctx *ctx = CONTAINER_OF(parser,
|
|
struct http_client_ctx,
|
|
parser);
|
|
|
|
NET_DBG("-- HTTP %s response (complete) --",
|
|
http_method_str(ctx->req.method));
|
|
|
|
if (ctx->rsp.cb) {
|
|
ctx->rsp.cb(ctx,
|
|
ctx->rsp.response_buf,
|
|
ctx->rsp.response_buf_len,
|
|
ctx->rsp.data_len,
|
|
HTTP_DATA_FINAL,
|
|
ctx->req.user_data);
|
|
}
|
|
|
|
ctx->rsp.message_complete = 1;
|
|
k_sem_give(&ctx->req.wait);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int on_chunk_header(struct http_parser *parser)
|
|
{
|
|
ARG_UNUSED(parser);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int on_chunk_complete(struct http_parser *parser)
|
|
{
|
|
ARG_UNUSED(parser);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void http_receive_cb(struct http_client_ctx *ctx,
|
|
struct net_pkt *pkt)
|
|
{
|
|
size_t start = ctx->rsp.data_len;
|
|
size_t len = 0;
|
|
struct net_buf *frag;
|
|
int header_len;
|
|
|
|
if (!pkt) {
|
|
return;
|
|
}
|
|
|
|
/* Get rid of possible IP headers in the first fragment. */
|
|
frag = pkt->frags;
|
|
|
|
header_len = net_pkt_appdata(pkt) - frag->data;
|
|
|
|
NET_DBG("Received %d bytes data", net_pkt_appdatalen(pkt));
|
|
|
|
/* After this pull, the frag->data points directly to application data.
|
|
*/
|
|
net_buf_pull(frag, header_len);
|
|
|
|
while (frag) {
|
|
/* If this fragment cannot be copied to result buf,
|
|
* then parse what we have which will cause the callback to be
|
|
* called in function on_body(), and continue copying.
|
|
*/
|
|
if (ctx->rsp.data_len + frag->len > ctx->rsp.response_buf_len) {
|
|
|
|
/* If the caller has not supplied a callback, then
|
|
* we cannot really continue if the response buffer
|
|
* overflows. Set the data_len to mark how many bytes
|
|
* should be needed in the response_buf.
|
|
*/
|
|
if (!ctx->rsp.cb) {
|
|
ctx->rsp.data_len = net_pkt_get_len(pkt);
|
|
goto out;
|
|
}
|
|
|
|
http_parser_execute(&ctx->parser,
|
|
&ctx->settings,
|
|
ctx->rsp.response_buf + start,
|
|
len);
|
|
|
|
ctx->rsp.data_len = 0;
|
|
len = 0;
|
|
start = 0;
|
|
}
|
|
|
|
memcpy(ctx->rsp.response_buf + ctx->rsp.data_len,
|
|
frag->data, frag->len);
|
|
|
|
ctx->rsp.data_len += frag->len;
|
|
len += frag->len;
|
|
frag = frag->frags;
|
|
}
|
|
|
|
out:
|
|
/* The parser's error can be catched outside, reading the
|
|
* http_errno struct member
|
|
*/
|
|
http_parser_execute(&ctx->parser, &ctx->settings,
|
|
ctx->rsp.response_buf + start, len);
|
|
|
|
net_pkt_unref(pkt);
|
|
}
|
|
|
|
int client_reset(struct http_client_ctx *ctx)
|
|
{
|
|
http_parser_init(&ctx->parser, HTTP_RESPONSE);
|
|
|
|
memset(ctx->rsp.http_status, 0, sizeof(ctx->rsp.http_status));
|
|
|
|
ctx->rsp.cl_present = 0;
|
|
ctx->rsp.content_length = 0;
|
|
ctx->rsp.processed = 0;
|
|
ctx->rsp.body_found = 0;
|
|
ctx->rsp.message_complete = 0;
|
|
ctx->rsp.body_start = NULL;
|
|
|
|
memset(ctx->rsp.response_buf, 0, ctx->rsp.response_buf_len);
|
|
ctx->rsp.data_len = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void tcp_disconnect(struct http_client_ctx *ctx)
|
|
{
|
|
if (ctx->tcp.ctx) {
|
|
net_context_put(ctx->tcp.ctx);
|
|
ctx->tcp.ctx = NULL;
|
|
}
|
|
}
|
|
|
|
static void recv_cb(struct net_context *net_ctx, struct net_pkt *pkt,
|
|
int status, void *data)
|
|
{
|
|
struct http_client_ctx *ctx = data;
|
|
|
|
ARG_UNUSED(net_ctx);
|
|
|
|
if (status) {
|
|
return;
|
|
}
|
|
|
|
if (!pkt || net_pkt_appdatalen(pkt) == 0) {
|
|
/*
|
|
* This block most likely handles a TCP_FIN message.
|
|
* (this means the connection is now closed)
|
|
* If we get here, and rsp.message_complete is still 0
|
|
* this means the HTTP client is still waiting to parse a
|
|
* response body.
|
|
* This will will never happen now. Instead of generating
|
|
* an ETIMEDOUT error in the future, let's unlock the
|
|
* req.wait semaphore and let the app deal with whatever
|
|
* data was parsed in the header (IE: http status, etc).
|
|
*/
|
|
if (ctx->rsp.message_complete == 0) {
|
|
k_sem_give(&ctx->req.wait);
|
|
}
|
|
|
|
goto out;
|
|
}
|
|
|
|
/* receive_cb must take ownership of the received packet */
|
|
if (ctx->tcp.receive_cb) {
|
|
ctx->tcp.receive_cb(ctx, pkt);
|
|
return;
|
|
}
|
|
|
|
out:
|
|
if (pkt) {
|
|
net_pkt_unref(pkt);
|
|
}
|
|
}
|
|
|
|
static int get_local_addr(struct http_client_ctx *ctx)
|
|
{
|
|
if (ctx->tcp.local.sa_family == AF_INET6) {
|
|
#if defined(CONFIG_NET_IPV6)
|
|
struct in6_addr *dst = &net_sin6(&ctx->tcp.remote)->sin6_addr;
|
|
|
|
net_ipaddr_copy(&net_sin6(&ctx->tcp.local)->sin6_addr,
|
|
net_if_ipv6_select_src_addr(NULL, dst));
|
|
#else
|
|
return -EPFNOSUPPORT;
|
|
#endif
|
|
} else if (ctx->tcp.local.sa_family == AF_INET) {
|
|
#if defined(CONFIG_NET_IPV4)
|
|
struct net_if *iface = net_if_get_default();
|
|
|
|
/* For IPv4 we take the first address in the interface */
|
|
net_ipaddr_copy(&net_sin(&ctx->tcp.local)->sin_addr,
|
|
&iface->ipv4.unicast[0].address.in_addr);
|
|
#else
|
|
return -EPFNOSUPPORT;
|
|
#endif
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
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.sa_family == AF_INET6) {
|
|
addrlen = sizeof(struct sockaddr_in6);
|
|
|
|
/* If we are reconnecting, then make sure the source port
|
|
* is re-calculated so that the peer will not get confused
|
|
* which connection the connection is related to.
|
|
* This was seen in Linux which dropped packets when the same
|
|
* source port was for a new connection after the old connection
|
|
* was terminated.
|
|
*/
|
|
net_sin6(&ctx->tcp.local)->sin6_port = 0;
|
|
} else {
|
|
net_sin(&ctx->tcp.local)->sin_port = 0;
|
|
}
|
|
|
|
ret = get_local_addr(ctx);
|
|
if (ret < 0) {
|
|
NET_DBG("Cannot get local address (%d)", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = net_context_get(ctx->tcp.remote.sa_family, SOCK_STREAM,
|
|
IPPROTO_TCP, &ctx->tcp.ctx);
|
|
if (ret) {
|
|
NET_DBG("Get context error (%d)", ret);
|
|
return ret;
|
|
}
|
|
|
|
net_context_setup_pools(ctx->tcp.ctx, ctx->tx_slab, ctx->data_pool);
|
|
|
|
ret = net_context_bind(ctx->tcp.ctx, &ctx->tcp.local,
|
|
addrlen);
|
|
if (ret) {
|
|
NET_DBG("Bind error (%d)", ret);
|
|
goto out;
|
|
}
|
|
|
|
ret = net_context_connect(ctx->tcp.ctx,
|
|
&ctx->tcp.remote, addrlen,
|
|
NULL, ctx->tcp.timeout, NULL);
|
|
if (ret) {
|
|
NET_DBG("Connect error (%d)", ret);
|
|
goto out;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
#if defined(CONFIG_NET_DEBUG_HTTP)
|
|
static void sprint_addr(char *buf, int len,
|
|
sa_family_t family,
|
|
struct sockaddr *addr)
|
|
{
|
|
if (family == AF_INET6) {
|
|
net_addr_ntop(AF_INET6, &net_sin6(addr)->sin6_addr, buf, len);
|
|
} else if (family == AF_INET) {
|
|
net_addr_ntop(AF_INET, &net_sin(addr)->sin_addr, buf, len);
|
|
} else {
|
|
NET_DBG("Invalid protocol family");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static inline void print_info(struct http_client_ctx *ctx,
|
|
enum http_method method)
|
|
{
|
|
#if defined(CONFIG_NET_DEBUG_HTTP)
|
|
char local[NET_IPV6_ADDR_LEN];
|
|
char remote[NET_IPV6_ADDR_LEN];
|
|
|
|
sprint_addr(local, NET_IPV6_ADDR_LEN, ctx->tcp.local.sa_family,
|
|
&ctx->tcp.local);
|
|
|
|
sprint_addr(remote, NET_IPV6_ADDR_LEN, ctx->tcp.remote.sa_family,
|
|
&ctx->tcp.remote);
|
|
|
|
NET_DBG("HTTP %s (%s) %s -> %s port %d",
|
|
http_method_str(method), ctx->req.host, local, remote,
|
|
ntohs(net_sin(&ctx->tcp.remote)->sin_port));
|
|
#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);
|
|
|
|
/* Coverity tells in CID 170746 that the mbedtls_ssl_init()
|
|
* is overwriting the ssl struct. This looks like false positive as
|
|
* the struct is initialized with proper size.
|
|
*/
|
|
mbedtls_ssl_init(&ctx->https.mbedtls.ssl);
|
|
|
|
/* Coverity tells in CID 170745 that the mbedtls_ssl_config_init()
|
|
* is overwriting the conf struct. This looks like false positive as
|
|
* the struct is initialized with proper size.
|
|
*/
|
|
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,
|
|
u8_t *response_buf,
|
|
size_t response_buf_len,
|
|
void *user_data,
|
|
s32_t timeout)
|
|
{
|
|
int ret;
|
|
|
|
if (!response_buf || response_buf_len == 0) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
ctx->rsp.response_buf = response_buf;
|
|
ctx->rsp.response_buf_len = response_buf_len;
|
|
|
|
client_reset(ctx);
|
|
|
|
/* 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) {
|
|
req->host = ctx->server;
|
|
}
|
|
|
|
ctx->req.host = req->host;
|
|
ctx->req.method = req->method;
|
|
ctx->req.user_data = user_data;
|
|
|
|
ctx->rsp.cb = cb;
|
|
|
|
#if defined(CONFIG_HTTPS)
|
|
if (ctx->is_https) {
|
|
struct tx_fifo_block *tx_data;
|
|
struct k_mem_block block;
|
|
|
|
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)) {
|
|
ret = -ETIMEDOUT;
|
|
goto out;
|
|
}
|
|
|
|
if (timeout == 0) {
|
|
return -EINPROGRESS;
|
|
}
|
|
|
|
return 0;
|
|
|
|
out:
|
|
tcp_disconnect(ctx);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#if defined(CONFIG_DNS_RESOLVER)
|
|
static void dns_cb(enum dns_resolve_status status,
|
|
struct dns_addrinfo *info,
|
|
void *user_data)
|
|
{
|
|
struct waiter *waiter = user_data;
|
|
struct http_client_ctx *ctx = waiter->ctx;
|
|
|
|
if (!(status == DNS_EAI_INPROGRESS && info)) {
|
|
return;
|
|
}
|
|
|
|
if (info->ai_family == AF_INET) {
|
|
#if defined(CONFIG_NET_IPV4)
|
|
net_ipaddr_copy(&net_sin(&ctx->tcp.remote)->sin_addr,
|
|
&net_sin(&info->ai_addr)->sin_addr);
|
|
#else
|
|
goto out;
|
|
#endif
|
|
} else if (info->ai_family == AF_INET6) {
|
|
#if defined(CONFIG_NET_IPV6)
|
|
net_ipaddr_copy(&net_sin6(&ctx->tcp.remote)->sin6_addr,
|
|
&net_sin6(&info->ai_addr)->sin6_addr);
|
|
#else
|
|
goto out;
|
|
#endif
|
|
} else {
|
|
goto out;
|
|
}
|
|
|
|
ctx->tcp.remote.sa_family = info->ai_family;
|
|
|
|
out:
|
|
k_sem_give(&waiter->wait);
|
|
}
|
|
|
|
#define DNS_WAIT K_SECONDS(2)
|
|
#define DNS_WAIT_SEM (DNS_WAIT + K_SECONDS(1))
|
|
|
|
static int resolve_name(struct http_client_ctx *ctx,
|
|
const char *server,
|
|
enum dns_query_type type)
|
|
{
|
|
struct waiter dns_waiter;
|
|
int ret;
|
|
|
|
dns_waiter.ctx = ctx;
|
|
k_sem_init(&dns_waiter.wait, 0, 1);
|
|
|
|
ret = dns_get_addr_info(server, type, &ctx->dns_id, dns_cb,
|
|
&dns_waiter, DNS_WAIT);
|
|
if (ret < 0) {
|
|
NET_ERR("Cannot resolve %s (%d)", server, ret);
|
|
ctx->dns_id = 0;
|
|
return ret;
|
|
}
|
|
|
|
/* Wait a little longer for the DNS to finish so that
|
|
* the DNS will timeout before the semaphore.
|
|
*/
|
|
if (k_sem_take(&dns_waiter.wait, DNS_WAIT_SEM)) {
|
|
NET_ERR("Timeout while resolving %s", server);
|
|
ctx->dns_id = 0;
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
ctx->dns_id = 0;
|
|
|
|
if (ctx->tcp.remote.sa_family == AF_UNSPEC) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_DNS_RESOLVER */
|
|
|
|
static inline int set_remote_addr(struct http_client_ctx *ctx,
|
|
const char *server, u16_t server_port)
|
|
{
|
|
int ret;
|
|
|
|
#if defined(CONFIG_NET_IPV6) && !defined(CONFIG_NET_IPV4)
|
|
ret = net_addr_pton(AF_INET6, server,
|
|
&net_sin6(&ctx->tcp.remote)->sin6_addr);
|
|
if (ret < 0) {
|
|
/* Could be hostname, try DNS if configured. */
|
|
#if !defined(CONFIG_DNS_RESOLVER)
|
|
NET_ERR("Invalid IPv6 address %s", server);
|
|
return -EINVAL;
|
|
#else
|
|
ret = resolve_name(ctx, server, DNS_QUERY_TYPE_AAAA);
|
|
if (ret < 0) {
|
|
NET_ERR("Cannot resolve %s (%d)", server, ret);
|
|
return ret;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
net_sin6(&ctx->tcp.remote)->sin6_port = htons(server_port);
|
|
net_sin6(&ctx->tcp.remote)->sin6_family = AF_INET6;
|
|
#endif /* IPV6 && !IPV4 */
|
|
|
|
#if defined(CONFIG_NET_IPV4) && !defined(CONFIG_NET_IPV6)
|
|
ret = net_addr_pton(AF_INET, server,
|
|
&net_sin(&ctx->tcp.remote)->sin_addr);
|
|
if (ret < 0) {
|
|
/* Could be hostname, try DNS if configured. */
|
|
#if !defined(CONFIG_DNS_RESOLVER)
|
|
NET_ERR("Invalid IPv4 address %s", server);
|
|
return -EINVAL;
|
|
#else
|
|
ret = resolve_name(ctx, server, DNS_QUERY_TYPE_A);
|
|
if (ret < 0) {
|
|
NET_ERR("Cannot resolve %s (%d)", server, ret);
|
|
return ret;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
net_sin(&ctx->tcp.remote)->sin_port = htons(server_port);
|
|
net_sin(&ctx->tcp.remote)->sin_family = AF_INET;
|
|
#endif /* IPV6 && !IPV4 */
|
|
|
|
#if defined(CONFIG_NET_IPV4) && defined(CONFIG_NET_IPV6)
|
|
ret = net_addr_pton(AF_INET, server,
|
|
&net_sin(&ctx->tcp.remote)->sin_addr);
|
|
if (ret < 0) {
|
|
ret = net_addr_pton(AF_INET6, server,
|
|
&net_sin6(&ctx->tcp.remote)->sin6_addr);
|
|
if (ret < 0) {
|
|
/* Could be hostname, try DNS if configured. */
|
|
#if !defined(CONFIG_DNS_RESOLVER)
|
|
NET_ERR("Invalid IPv4 or IPv6 address %s", server);
|
|
return -EINVAL;
|
|
#else
|
|
ret = resolve_name(ctx, server, DNS_QUERY_TYPE_A);
|
|
if (ret < 0) {
|
|
ret = resolve_name(ctx, server,
|
|
DNS_QUERY_TYPE_AAAA);
|
|
if (ret < 0) {
|
|
NET_ERR("Cannot resolve %s (%d)",
|
|
server, ret);
|
|
return ret;
|
|
}
|
|
|
|
goto ipv6;
|
|
}
|
|
|
|
goto ipv4;
|
|
#endif /* !CONFIG_DNS_RESOLVER */
|
|
} else {
|
|
#if defined(CONFIG_DNS_RESOLVER)
|
|
ipv6:
|
|
#endif
|
|
net_sin6(&ctx->tcp.remote)->sin6_port =
|
|
htons(server_port);
|
|
net_sin6(&ctx->tcp.remote)->sin6_family = AF_INET6;
|
|
}
|
|
} else {
|
|
#if defined(CONFIG_DNS_RESOLVER)
|
|
ipv4:
|
|
#endif
|
|
net_sin(&ctx->tcp.remote)->sin_port = htons(server_port);
|
|
net_sin(&ctx->tcp.remote)->sin_family = AF_INET;
|
|
}
|
|
#endif /* IPV4 && IPV6 */
|
|
|
|
/* If we have not yet figured out what is the protocol family,
|
|
* then we cannot continue.
|
|
*/
|
|
if (ctx->tcp.remote.sa_family == AF_UNSPEC) {
|
|
NET_ERR("Unknown protocol family.");
|
|
return -EPFNOSUPPORT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int http_client_init(struct http_client_ctx *ctx,
|
|
const char *server, u16_t server_port)
|
|
{
|
|
int ret;
|
|
|
|
/* Coverity tells in CID 170743 that the next memset() is
|
|
* is overwriting the ctx struct. This is false positive as
|
|
* the struct is initialized with proper size.
|
|
*/
|
|
memset(ctx, 0, sizeof(*ctx));
|
|
|
|
if (server) {
|
|
ret = set_remote_addr(ctx, server, server_port);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
ctx->tcp.local.sa_family = ctx->tcp.remote.sa_family;
|
|
ctx->server = server;
|
|
}
|
|
|
|
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.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,
|
|
k_thread_stack_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.sa_family = ctx->tcp.remote.sa_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;
|
|
}
|
|
|
|
#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);
|
|
|
|
#if defined(CONFIG_DNS_RESOLVER)
|
|
if (ctx->dns_id) {
|
|
dns_cancel_addr_info(ctx->dns_id);
|
|
}
|
|
#endif
|
|
|
|
/* Let all the pending waiters run */
|
|
k_yield();
|
|
|
|
/* Coverity tells in CID 170742 that the next memset() is
|
|
* is overwriting the ctx struct. This is false positive as
|
|
* the struct is initialized with proper size.
|
|
*/
|
|
memset(ctx, 0, sizeof(*ctx));
|
|
}
|
|
|
|
#if defined(CONFIG_NET_CONTEXT_NET_PKT_POOL)
|
|
int http_client_set_net_pkt_pool(struct http_client_ctx *ctx,
|
|
net_pkt_get_slab_func_t tx_slab,
|
|
net_pkt_get_pool_func_t data_pool)
|
|
{
|
|
ctx->tx_slab = tx_slab;
|
|
ctx->data_pool = data_pool;
|
|
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_NET_CONTEXT_NET_PKT_POOL */
|