| /* |
| * 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, BUF_ALLOC_TIMEOUT); |
| if (!pkt) { |
| return -ENOMEM; |
| } |
| |
| if (!net_pkt_append_all(pkt, strlen(method), (u8_t *)method, |
| BUF_ALLOC_TIMEOUT)) { |
| goto out; |
| } |
| |
| /* Space after method string. */ |
| if (!net_pkt_append_all(pkt, 1, (u8_t *)" ", BUF_ALLOC_TIMEOUT)) { |
| goto out; |
| } |
| |
| if (!net_pkt_append_all(pkt, strlen(req->url), (u8_t *)req->url, |
| BUF_ALLOC_TIMEOUT)) { |
| goto out; |
| } |
| |
| if (!net_pkt_append_all(pkt, strlen(req->protocol), |
| (u8_t *)req->protocol, BUF_ALLOC_TIMEOUT)) { |
| goto out; |
| } |
| |
| if (req->host) { |
| if (!net_pkt_append_all(pkt, strlen(HTTP_HOST), |
| (u8_t *)HTTP_HOST, |
| BUF_ALLOC_TIMEOUT)) { |
| goto out; |
| } |
| |
| if (!net_pkt_append_all(pkt, strlen(req->host), |
| (u8_t *)req->host, |
| BUF_ALLOC_TIMEOUT)) { |
| goto out; |
| } |
| |
| if (!net_pkt_append_all(pkt, strlen(HTTP_CRLF), |
| (u8_t *)HTTP_CRLF, |
| BUF_ALLOC_TIMEOUT)) { |
| goto out; |
| } |
| } |
| |
| if (req->header_fields) { |
| if (!net_pkt_append_all(pkt, strlen(req->header_fields), |
| (u8_t *)req->header_fields, |
| BUF_ALLOC_TIMEOUT)) { |
| goto out; |
| } |
| } |
| |
| if (req->content_type_value) { |
| if (!net_pkt_append_all(pkt, strlen(HTTP_CONTENT_TYPE), |
| (u8_t *)HTTP_CONTENT_TYPE, |
| BUF_ALLOC_TIMEOUT)) { |
| goto out; |
| } |
| |
| if (!net_pkt_append_all(pkt, strlen(req->content_type_value), |
| (u8_t *)req->content_type_value, |
| BUF_ALLOC_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, |
| BUF_ALLOC_TIMEOUT)) { |
| ret = -ENOMEM; |
| goto out; |
| } |
| |
| if (!net_pkt_append_all(pkt, req->payload_size, |
| (u8_t *)req->payload, |
| BUF_ALLOC_TIMEOUT)) { |
| ret = -ENOMEM; |
| goto out; |
| } |
| } else { |
| if (!net_pkt_append_all(pkt, strlen(HTTP_EOF), |
| (u8_t *)HTTP_EOF, |
| BUF_ALLOC_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 && |
| (u8_t *)at != (u8_t *)ctx->rsp.response_buf) { |
| /* This fragment contains the start of the body */ |
| 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; |
| ctx->rsp.body_start = NULL; |
| } |
| |
| 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, 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 */ |