diff --git a/ext/lib/crypto/mbedtls/configs/config-tls1_2.h b/ext/lib/crypto/mbedtls/configs/config-tls1_2.h new file mode 100644 index 0000000000000..640064b03098a --- /dev/null +++ b/ext/lib/crypto/mbedtls/configs/config-tls1_2.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018 Linaro Limited. + * + * SPDX-License-Identifier: Apache-2.0 + * + * More complete mbedTLS configuration for TLS 1.2 (RFC 5246) for Zephyr, + * extending config-mini-tls1_2.h. + */ + +#ifndef MBEDTLS_CONFIG_TLS1_2_H +#define MBEDTLS_CONFIG_TLS1_2_H + +#if 1 + +/* DHE config - slow but moderate code size impact (~5K x86) */ +#define MBEDTLS_KEY_EXCHANGE_DHE_RSA_ENABLED +#define MBEDTLS_DHM_C + +#else + +/* ECDHE config - faster but higher code size impact (~15K x86) */ +#define MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED +#define MBEDTLS_ECP_DP_SECP256R1_ENABLED +#define MBEDTLS_ECDH_C +#define MBEDTLS_ECP_C + +#endif + +#include + +#endif /* MBEDTLS_CONFIG_TLS1_2_H */ diff --git a/include/net/async_socket.h b/include/net/async_socket.h new file mode 100644 index 0000000000000..99b4f8ac29330 --- /dev/null +++ b/include/net/async_socket.h @@ -0,0 +1,75 @@ +/** + * @file + * @brief Asynchronous sockets API definitions + * + * An API for adapting synchronous BSD socket APIs to applications + * requiring asynchronous callbacks. + * + * Created to ease adaptation of asynchronous IP protocols from + * net_app/net_context to sockets. + */ + +/* + * Copyright (c) 2018, Texas Instruments Incorporated + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __NET_ASYNC_SOCKET_H +#define __NET_ASYNC_SOCKET_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Callbacks, similar in semantics to those of net_context.h */ +typedef void (*async_connect_cb_t)(int sock, + int status, + void *cb_data); + +typedef void (*async_send_cb_t)(int sock, + int bytes_sent, + void *cb_data); + +typedef void (*async_recv_cb_t)(int sock, + void *data, + size_t bytes_received, + void *cb_data); + +/* + * Errors are the same as the corresponding POSIX socket functions: i.e., + * a return value of -1 implicitly sets errno. + */ + +/* For now, same semantics as socket() call: */ +static inline int async_socket(int family, int type, int proto) +{ + return socket(family, type, proto); +} + +int async_close(int sock, struct zstream *stream); + +int async_bind(int sock, const struct sockaddr *addr, socklen_t addrlen); + +int async_connect(int sock, const struct sockaddr *addr, socklen_t addrlen, + async_connect_cb_t cb, void *cb_data); + +ssize_t async_send(struct zstream *sock, const void *buf, size_t len, + async_send_cb_t cb, void *cb_data, int flags); + +/* buf must be unique per sock */ +ssize_t async_recv(int sock, struct zstream *stream, void *buf, size_t max_len, + async_recv_cb_t cb, void *cb_data); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __NET_ASYNC_SOCKET_H */ diff --git a/include/net/mqtt.h b/include/net/mqtt.h index 109b34c93bfe1..7221fabc97955 100644 --- a/include/net/mqtt.h +++ b/include/net/mqtt.h @@ -7,9 +7,13 @@ #ifndef _MQTT_H_ #define _MQTT_H_ +#include +#include #include +#if defined(CONFIG_MQTT_LIB_TLS) #include #include +#endif #ifdef __cplusplus extern "C" { @@ -63,8 +67,13 @@ enum mqtt_app { * the state of the received and sent messages. */ struct mqtt_ctx { - /** Net app context structure */ - struct net_app_ctx net_app_ctx; + int sock; + /* Points to either stream_sock or stream_tls below */ + struct zstream *stream; + struct zstream_sock stream_sock; +#if defined(CONFIG_MBEDTLS) + struct zstream_tls stream_tls; +#endif s32_t net_init_timeout; s32_t net_timeout; @@ -180,7 +189,10 @@ struct mqtt_ctx { void (*malformed)(struct mqtt_ctx *ctx, u16_t pkt_type); /* Internal use only */ - int (*rcv)(struct mqtt_ctx *ctx, struct net_pkt *); + int (*rcv)(struct mqtt_ctx *ctx, void *buf, size_t len); + + /* Receive buffer for async receive callbacks */ + void *rcv_buf; /** Application type, see: enum mqtt_app */ u8_t app_type; @@ -200,7 +212,7 @@ struct mqtt_ctx { * * @param ctx MQTT context structure * @param app_type See enum mqtt_app - * @retval 0 always + * @retval 0 on success, and <0 if error */ int mqtt_init(struct mqtt_ctx *ctx, enum mqtt_app app_type); @@ -366,12 +378,13 @@ int mqtt_tx_unsubscribe(struct mqtt_ctx *ctx, u16_t pkt_id, u8_t items, * * @param [in] ctx MQTT context structure * @param [in] rx Data buffer + * @param [in] len Length of data * @param [in] clean_session MQTT clean session parameter * * @retval 0 on success * @retval -EINVAL */ -int mqtt_rx_connack(struct mqtt_ctx *ctx, struct net_buf *rx, +int mqtt_rx_connack(struct mqtt_ctx *ctx, void *rx, size_t len, int clean_session); /** @@ -379,89 +392,97 @@ int mqtt_rx_connack(struct mqtt_ctx *ctx, struct net_buf *rx, * * @param [in] ctx MQTT context structure * @param [in] rx Data buffer + * @param [in] len Length of data * * @retval 0 on success * @retval -EINVAL */ -int mqtt_rx_puback(struct mqtt_ctx *ctx, struct net_buf *rx); +int mqtt_rx_puback(struct mqtt_ctx *ctx, void *rx, size_t len); /** * Parses and validates the MQTT PUBCOMP message * * @param [in] ctx MQTT context structure * @param [in] rx Data buffer + * @param [in] len Length of data * * @retval 0 on success * @retval -EINVAL */ -int mqtt_rx_pubcomp(struct mqtt_ctx *ctx, struct net_buf *rx); +int mqtt_rx_pubcomp(struct mqtt_ctx *ctx, void *rx, size_t len); /** * Parses and validates the MQTT PUBREC message * * @param [in] ctx MQTT context structure * @param [in] rx Data buffer + * @param [in] len Length of data * * @retval 0 on success * @retval -EINVAL */ -int mqtt_rx_pubrec(struct mqtt_ctx *ctx, struct net_buf *rx); +int mqtt_rx_pubrec(struct mqtt_ctx *ctx, void *rx, size_t len); /** * Parses and validates the MQTT PUBREL message * * @param [in] ctx MQTT context structure * @param [in] rx Data buffer + * @param [in] len Length of data * * @retval 0 on success * @retval -EINVAL */ -int mqtt_rx_pubrel(struct mqtt_ctx *ctx, struct net_buf *rx); +int mqtt_rx_pubrel(struct mqtt_ctx *ctx, void *rx, size_t len); /** * Parses the MQTT PINGRESP message * * @param [in] ctx MQTT context structure * @param [in] rx Data buffer + * @param [in] len Length of data * * @retval 0 on success * @retval -EINVAL */ -int mqtt_rx_pingresp(struct mqtt_ctx *ctx, struct net_buf *rx); +int mqtt_rx_pingresp(struct mqtt_ctx *ctx, void *rx, size_t len); /** * Parses the MQTT SUBACK message * * @param [in] ctx MQTT context structure * @param [in] rx Data buffer + * @param [in] len Length of data * * @retval 0 on success * @retval -EINVAL */ -int mqtt_rx_suback(struct mqtt_ctx *ctx, struct net_buf *rx); +int mqtt_rx_suback(struct mqtt_ctx *ctx, void *rx, size_t len); /** * Parses the MQTT UNSUBACK message * * @param [in] ctx MQTT context structure * @param [in] rx Data buffer + * @param [in] len Length of data * * @retval 0 on success * @retval -EINVAL */ -int mqtt_rx_unsuback(struct mqtt_ctx *ctx, struct net_buf *rx); +int mqtt_rx_unsuback(struct mqtt_ctx *ctx, void *rx, size_t len); /** * Parses the MQTT PUBLISH message * * @param [in] ctx MQTT context structure * @param [in] rx Data buffer + * @param [in] len Length of data * * @retval 0 on success * @retval -EINVAL * @retval -ENOMEM */ -int mqtt_rx_publish(struct mqtt_ctx *ctx, struct net_buf *rx); +int mqtt_rx_publish(struct mqtt_ctx *ctx, void *rx, size_t len); /** * @} diff --git a/include/net/mqtt_types.h b/include/net/mqtt_types.h index 4e65642f8d628..5ed0e31adb668 100644 --- a/include/net/mqtt_types.h +++ b/include/net/mqtt_types.h @@ -7,6 +7,7 @@ #ifndef _MQTT_TYPES_H_ #define _MQTT_TYPES_H_ +#include #include #ifdef __cplusplus diff --git a/include/net/tls_conf.h b/include/net/tls_conf.h new file mode 100644 index 0000000000000..9ff0bc975624b --- /dev/null +++ b/include/net/tls_conf.h @@ -0,0 +1,32 @@ +/** + * @file + * @brief TLS configuration API definitions + * + * Convenience configuration API for mbedTLS. + */ + +/* + * Copyright (c) 2018 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +struct ztls_cert_key_pair { + mbedtls_x509_crt cert; + mbedtls_pk_context priv_key; +}; + + +int ztls_get_tls_client_conf(mbedtls_ssl_config **out_conf); +int ztls_get_tls_server_conf(mbedtls_ssl_config **out_conf); + +int ztls_conf_add_own_cert_key_pair(mbedtls_ssl_config *conf, + struct ztls_cert_key_pair *pair); + +int ztls_parse_cert_key_pair(struct ztls_cert_key_pair *pair, + const unsigned char *cert, + size_t cert_len, + const unsigned char *priv_key, + size_t priv_key_len); diff --git a/include/net/zstream.h b/include/net/zstream.h new file mode 100644 index 0000000000000..280c70f165b0c --- /dev/null +++ b/include/net/zstream.h @@ -0,0 +1,65 @@ +/** + * @file + * @brief Network stream API definitions + * + * An API to abstract different transport protocols for SOCK_STREAMs, etc. + */ + +/* + * Copyright (c) 2018 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __NET_ZSTREAM_H +#define __NET_ZSTREAM_H + +#include + +struct zstream_api; + +struct zstream { + const struct zstream_api *api; +}; + +struct zstream_api { + ssize_t (*read)(struct zstream *stream, void *buf, size_t size); + ssize_t (*write)(struct zstream *stream, const void *buf, size_t size); + int (*flush)(struct zstream *stream); + int (*close)(struct zstream *stream); +}; + +static inline ssize_t zstream_read(struct zstream *stream, void *buf, + size_t size) +{ + return stream->api->read(stream, buf, size); +} + +static inline ssize_t zstream_write(struct zstream *stream, const void *buf, + size_t size) +{ + return stream->api->write(stream, buf, size); +} + +ssize_t zstream_writeall(struct zstream *stream, const void *buf, size_t size, + size_t *written); + +static inline ssize_t zstream_flush(struct zstream *stream) +{ + return stream->api->flush(stream); +} + +static inline ssize_t zstream_close(struct zstream *stream) +{ + return stream->api->close(stream); +} + +/* Stream object implementation for socket. */ +struct zstream_sock { + const struct zstream_api *api; + int fd; +}; + +int zstream_sock_init(struct zstream_sock *self, int fd); + +#endif /* __NET_ZSTREAM_H */ diff --git a/include/net/zstream_tls.h b/include/net/zstream_tls.h new file mode 100644 index 0000000000000..e7a9b24e03293 --- /dev/null +++ b/include/net/zstream_tls.h @@ -0,0 +1,54 @@ +/** + * @file + * @brief TLS stream API definitions + * + * Implementation of stream wrapper for mbedTLS. + */ + +/* + * Copyright (c) 2018 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __NET_ZSTREAM_TLS_H +#define __NET_ZSTREAM_TLS_H + +#if defined(CONFIG_MBEDTLS) + +#include +#include + +#include "mbedtls/platform.h" +#include "mbedtls/net.h" +#include "mbedtls/ssl.h" +#include "mbedtls/x509_crt.h" +#include "mbedtls/pk.h" +#include "mbedtls/entropy.h" +#include "mbedtls/ctr_drbg.h" +#include "mbedtls/debug.h" + +#include + +/* Stream object implementation for TLS adapter. */ +struct zstream_tls { + const struct zstream_api *api; + + /* Wrapped stream */ + struct zstream *sock; + + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; + mbedtls_ssl_context ssl; + mbedtls_ssl_config conf; + mbedtls_x509_crt ca_cert; + mbedtls_x509_crt srv_cert; + mbedtls_pk_context srv_priv_key; +}; + +int zstream_tls_init(struct zstream_tls *self, struct zstream *sock, + mbedtls_ssl_config *conf, const char *hostname); + +#endif /* CONFIG_MBEDTLS */ + +#endif /* __NET_ZSTREAM_TLS_H */ diff --git a/samples/net/mqtt_publisher/prj_qemu_x86.conf b/samples/net/mqtt_publisher/prj_qemu_x86.conf index 611fb60d677ed..71baf740921b3 100644 --- a/samples/net/mqtt_publisher/prj_qemu_x86.conf +++ b/samples/net/mqtt_publisher/prj_qemu_x86.conf @@ -5,6 +5,13 @@ CONFIG_TEST_RANDOM_GENERATOR=y CONFIG_NET_LOG=y CONFIG_INIT_STACKS=y +CONFIG_MBEDTLS=y +CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=2000 +CONFIG_MBEDTLS_ENABLE_HEAP=y +CONFIG_MBEDTLS_HEAP_SIZE=15000 +#CONFIG_MBEDTLS_DEBUG=y +#CONFIG_MBEDTLS_DEBUG_LEVEL=3 + CONFIG_NET_PKT_RX_COUNT=16 CONFIG_NET_PKT_TX_COUNT=16 CONFIG_NET_BUF_RX_COUNT=16 @@ -31,7 +38,8 @@ CONFIG_NET_APP_PEER_IPV6_ADDR="2001:db8::2" CONFIG_NET_APP_MY_IPV4_ADDR="192.0.2.1" CONFIG_NET_APP_PEER_IPV4_ADDR="192.0.2.2" -CONFIG_MAIN_STACK_SIZE=2048 +# Big value required for mbedTLS +CONFIG_MAIN_STACK_SIZE=4096 # For IPv6 CONFIG_NET_BUF_DATA_SIZE=256 diff --git a/samples/net/mqtt_publisher/src/main.c b/samples/net/mqtt_publisher/src/main.c index a69fd367eb542..41bbaf81ce354 100644 --- a/samples/net/mqtt_publisher/src/main.c +++ b/samples/net/mqtt_publisher/src/main.c @@ -7,8 +7,6 @@ #include #include -#include - #include #include #include diff --git a/samples/net/sockets/big_http_download/Makefile.posix b/samples/net/sockets/big_http_download/Makefile.posix index 78e33f7717d08..a5a12452e53bc 100644 --- a/samples/net/sockets/big_http_download/Makefile.posix +++ b/samples/net/sockets/big_http_download/Makefile.posix @@ -2,5 +2,15 @@ # It required mbedTLS to be installed on the system, e.g. # libmbedtls-dev package on Debian/Ubuntu Linux. +INC = -I$(ZEPHYR_BASE)/include +LIBPATH = $(ZEPHYR_BASE)/subsys/net/lib/zstream +LIBSRC = $(LIBPATH)/zstream_sock.c \ + $(LIBPATH)/zstream_tls.c \ + $(LIBPATH)/../tls_conf/tls_conf.c \ + +LDLIBS = -lmbedtls -lmbedx509 -lmbedcrypto + +CFLAGS = -DCONFIG_MBEDTLS -DCONFIG_MBEDTLS_DEBUG_LEVEL=0 + big_http_download: src/big_http_download.c - $(CC) $^ -lmbedcrypto -o $@ + $(CC) $(INC) $(CFLAGS) $^ $(LIBSRC) $(LDLIBS) -o $@ diff --git a/samples/net/sockets/big_http_download/prj.conf b/samples/net/sockets/big_http_download/prj.conf index 2a7830c47ff46..0cfc398eeab6c 100644 --- a/samples/net/sockets/big_http_download/prj.conf +++ b/samples/net/sockets/big_http_download/prj.conf @@ -1,7 +1,12 @@ # General config CONFIG_NEWLIB_LIBC=y +CONFIG_MAIN_STACK_SIZE=6000 CONFIG_MBEDTLS=y +CONFIG_MBEDTLS_CFG_FILE="config-tls1_2.h" +CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=16384 CONFIG_MBEDTLS_ENABLE_HEAP=y +CONFIG_MBEDTLS_HEAP_SIZE=60000 +#CONFIG_MBEDTLS_DEBUG=y # Networking config CONFIG_NETWORKING=y diff --git a/samples/net/sockets/big_http_download/src/big_http_download.c b/samples/net/sockets/big_http_download/src/big_http_download.c index ccd4a9da4d567..6d807d5d657e1 100644 --- a/samples/net/sockets/big_http_download/src/big_http_download.c +++ b/samples/net/sockets/big_http_download/src/big_http_download.c @@ -27,20 +27,28 @@ #endif +#include +#include +#include + /* This URL is parsed in-place, so buffer must be non-const. */ static char download_url[] = - "http://archive.ubuntu.com/ubuntu/dists/xenial/main/installer-amd64/current/images/hd-media/vmlinuz"; + /*"http://archive.ubuntu.com/ubuntu/dists/xenial/main/installer-amd64/current/images/hd-media/vmlinuz";*/ + "https://ftp.gnu.org/gnu/tar/tar-1.13.tar"; /* Quick testing. */ /* "http://google.com/foo";*/ +/* To generate: */ /* print("".join(["\\x%02x" % x for x in list(binascii.unhexlify("hash"))])) */ -static uint8_t download_hash[32] = "\x33\x7c\x37\xd7\xec\x00\x34\x84\x14\x22\x4b\xaa\x6b\xdb\x2d\x43\xf2\xa3\x4e\xf5\x67\x6b\xaf\xcd\xca\xd9\x16\xf1\x48\xb5\xb3\x17"; +static uint8_t download_hash[32] = + /*"\x33\x7c\x37\xd7\xec\x00\x34\x84\x14\x22\x4b\xaa\x6b\xdb\x2d\x43\xf2\xa3\x4e\xf5\x67\x6b\xaf\xcd\xca\xd9\x16\xf1\x48\xb5\xb3\x17";*/ + "\xbe\x12\xfe\x40\xe1\xb2\x02\xd7\x0c\x45\x5d\x78\x4f\xbe\xc8\xcd\xb3\x38\xfe\x01\x1a\xb9\xe8\x62\x95\x81\x35\x68\x90\x6f\x60\x73"; #define SSTRLEN(s) (sizeof(s) - 1) #define CHECK(r) { if (r == -1) { printf("Error: " #r "\n"); exit(1); } } const char *host; -const char *port = "80"; +const char *port; const char *uri_path = ""; static char response[1024]; static char response_hash[32]; @@ -63,10 +71,10 @@ void fatal(const char *msg) exit(1); } -ssize_t sendall(int sock, const void *buf, size_t len) +ssize_t sendall(struct zstream *stream, const void *buf, size_t len) { while (len) { - ssize_t out_len = send(sock, buf, len, 0); + ssize_t out_len = zstream_write(stream, buf, len); if (out_len < 0) { return out_len; } @@ -77,7 +85,7 @@ ssize_t sendall(int sock, const void *buf, size_t len) return 0; } -int skip_headers(int sock) +int skip_headers(struct zstream *stream) { int state = 0; @@ -85,7 +93,7 @@ int skip_headers(int sock) char c; int st; - st = recv(sock, &c, 1, 0); + st = zstream_read(stream, &c, 1); if (st <= 0) { return st; } @@ -113,9 +121,12 @@ void print_hex(const unsigned char *p, int len) } } -void download(struct addrinfo *ai) +void download(struct addrinfo *ai, mbedtls_ssl_config *tls_conf) { int sock; + struct zstream_sock stream_sock; + struct zstream_tls stream_tls; + struct zstream *stream; cur_bytes = 0; @@ -123,14 +134,24 @@ void download(struct addrinfo *ai) CHECK(sock); printf("sock = %d\n", sock); CHECK(connect(sock, ai->ai_addr, ai->ai_addrlen)); - sendall(sock, "GET /", SSTRLEN("GET /")); - sendall(sock, uri_path, strlen(uri_path)); - sendall(sock, " HTTP/1.0\r\n", SSTRLEN(" HTTP/1.0\r\n")); - sendall(sock, "Host: ", SSTRLEN("Host: ")); - sendall(sock, host, strlen(host)); - sendall(sock, "\r\n\r\n", SSTRLEN("\r\n\r\n")); - - if (skip_headers(sock) <= 0) { + + zstream_sock_init(&stream_sock, sock); + stream = (struct zstream *)&stream_sock; + + if (tls_conf) { + zstream_tls_init(&stream_tls, stream, tls_conf, host); + stream = (struct zstream *)&stream_tls; + } + + sendall(stream, "GET /", SSTRLEN("GET /")); + sendall(stream, uri_path, strlen(uri_path)); + sendall(stream, " HTTP/1.0\r\n", SSTRLEN(" HTTP/1.0\r\n")); + sendall(stream, "Host: ", SSTRLEN("Host: ")); + sendall(stream, host, strlen(host)); + sendall(stream, "\r\n\r\n", SSTRLEN("\r\n\r\n")); + zstream_flush(stream); + + if (skip_headers(stream) <= 0) { printf("EOF or error in response headers\n"); goto error; } @@ -138,7 +159,7 @@ void download(struct addrinfo *ai) mbedtls_md_starts(&hash_ctx); while (1) { - int len = recv(sock, response, sizeof(response) - 1, 0); + int len = zstream_read(stream, response, sizeof(response) - 1); if (len < 0) { printf("Error reading response\n"); @@ -172,7 +193,7 @@ void download(struct addrinfo *ai) } error: - (void)close(sock); + (void)zstream_close(sock); } int main(void) @@ -183,15 +204,27 @@ int main(void) char *p; unsigned int total_bytes = 0; int resolve_attempts = 10; + mbedtls_ssl_config *tls_conf = NULL; setbuf(stdout, NULL); - if (strncmp(download_url, "http://", SSTRLEN("http://")) != 0) { - fatal("Only http: URLs are supported"); + if (strncmp(download_url, "http://", SSTRLEN("http://")) == 0) { + port = "80"; + p = download_url + SSTRLEN("http://"); + } else if (strncmp(download_url, "https://", + SSTRLEN("https://")) == 0) { + if (ztls_get_tls_client_conf(&tls_conf) < 0) { + printf("Unable to initialize TLS\n"); + return 1; + } + mbedtls_ssl_conf_authmode(tls_conf, MBEDTLS_SSL_VERIFY_NONE); + printf("Warning: site certificate is not validated\n"); + port = "443"; + p = download_url + SSTRLEN("https://"); + } else { + fatal("Only http: and https: URLs are supported"); } - p = download_url + SSTRLEN("http://"); - /* Parse host part */ host = p; while (*p && *p != ':' && *p != '/') { @@ -214,8 +247,8 @@ int main(void) uri_path = p; } - printf("Preparing HTTP GET request for http://%s:%s/%s\n", - host, port, uri_path); + printf("Preparing HTTP GET request for http%s://%s:%s/%s\n", + (tls_conf ? "s" : ""), host, port, uri_path); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; @@ -248,7 +281,7 @@ int main(void) } while (1) { - download(res); + download(res, tls_conf); total_bytes += cur_bytes; printf("Total downloaded so far: %uMB\n", total_bytes / (1024 * 1024)); diff --git a/samples/net/sockets/echo/Makefile.posix b/samples/net/sockets/echo/Makefile.posix index 7bf5def40dd92..8468bd89fe3a7 100644 --- a/samples/net/sockets/echo/Makefile.posix +++ b/samples/net/sockets/echo/Makefile.posix @@ -1,4 +1,18 @@ -# This makefile builds socket_echo sample for POSIX system, like Linux +# This makefile builds the sample for a POSIX system, like Linux +# It required mbedTLS to be installed on the system, e.g. +# libmbedtls-dev package on Debian/Ubuntu Linux. +# To test connection: +# openssl s_client -connect localhost:4242 + +INC = -I$(ZEPHYR_BASE)/include +LIBPATH = $(ZEPHYR_BASE)/subsys/net/lib/zstream +LIBSRC = $(LIBPATH)/zstream_sock.c \ + $(LIBPATH)/zstream_tls.c \ + $(LIBPATH)/../tls_conf/tls_conf.c \ + +LDLIBS = -lmbedtls -lmbedx509 -lmbedcrypto + +CFLAGS = -DCONFIG_MBEDTLS -DCONFIG_MBEDTLS_DEBUG_LEVEL=0 socket_echo: src/socket_echo.c - $(CC) $^ -o $@ + $(CC) $(INC) $(CFLAGS) $^ $(LIBSRC) $(LDLIBS) -o $@ diff --git a/samples/net/sockets/echo/prj.conf b/samples/net/sockets/echo/prj.conf index 96344b682e2e5..875c7081d1f16 100644 --- a/samples/net/sockets/echo/prj.conf +++ b/samples/net/sockets/echo/prj.conf @@ -1,5 +1,11 @@ # General config CONFIG_NEWLIB_LIBC=y +CONFIG_MAIN_STACK_SIZE=6000 +CONFIG_MBEDTLS=y +#CONFIG_MBEDTLS_CFG_FILE="config-mini-tls1_2.h" +CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=16384 +CONFIG_MBEDTLS_ENABLE_HEAP=y +CONFIG_MBEDTLS_HEAP_SIZE=50000 # Networking config CONFIG_NETWORKING=y diff --git a/samples/net/sockets/echo/src/socket_echo.c b/samples/net/sockets/echo/src/socket_echo.c index 539cdaedc0178..19990b5587152 100644 --- a/samples/net/sockets/echo/src/socket_echo.c +++ b/samples/net/sockets/echo/src/socket_echo.c @@ -22,6 +22,12 @@ #endif +#include +#include +#include + +#include "../../../echo_server/src/test_certs.h" + #define PORT 4242 int main(void) @@ -29,6 +35,30 @@ int main(void) int serv; struct sockaddr_in bind_addr; static int counter; + mbedtls_ssl_config *tls_conf; + static struct ztls_cert_key_pair cert_key; + int res; + + if (ztls_get_tls_server_conf(&tls_conf) < 0) { + printf("Unable to initialize TLS\n"); + return 1; + } + + res = ztls_parse_cert_key_pair(&cert_key, + rsa_example_cert_der, + rsa_example_cert_der_len, + rsa_example_keypair_der, + rsa_example_keypair_der_len); + if (res < 0) { + printf("Unable to parse cert/privkey\n"); + return 1; + } + + res = ztls_conf_add_own_cert_key_pair(tls_conf, &cert_key); + if (res < 0) { + printf("Unable to set cert/privkey\n"); + return 1; + } serv = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); @@ -45,15 +75,26 @@ int main(void) struct sockaddr_in client_addr; socklen_t client_addr_len = sizeof(client_addr); char addr_str[32]; + struct zstream_sock stream_sock; + struct zstream_tls stream_tls; + struct zstream *stream; int client = accept(serv, (struct sockaddr *)&client_addr, &client_addr_len); inet_ntop(client_addr.sin_family, &client_addr.sin_addr, addr_str, sizeof(addr_str)); printf("Connection #%d from %s\n", counter++, addr_str); + zstream_sock_init(&stream_sock, client); + stream = (struct zstream *)&stream_sock; + + if (zstream_tls_init(&stream_tls, stream, tls_conf, NULL) < 0) { + printf("Error creating TLS connection\n"); + goto error; + } + stream = (struct zstream *)&stream_tls; while (1) { char buf[128], *p; - int len = recv(client, buf, sizeof(buf), 0); + int len = zstream_read(stream, buf, sizeof(buf)); int out_len; if (len <= 0) { @@ -65,7 +106,7 @@ int main(void) p = buf; do { - out_len = send(client, p, len, 0); + out_len = zstream_write(stream, p, len); if (out_len < 0) { printf("error: send: %d\n", errno); goto error; @@ -73,10 +114,15 @@ int main(void) p += out_len; len -= out_len; } while (len); + + if (zstream_flush(stream) < 0) { + printf("error: flush: %d\n", errno); + goto error; + } } error: - close(client); + zstream_close(stream); printf("Connection from %s closed\n", addr_str); } } diff --git a/samples/net/sockets/echo_async/Makefile.posix b/samples/net/sockets/echo_async/Makefile.posix index a166f75d2c247..65d011218ce2c 100644 --- a/samples/net/sockets/echo_async/Makefile.posix +++ b/samples/net/sockets/echo_async/Makefile.posix @@ -1,4 +1,14 @@ # This makefile builds sample for POSIX system, like Linux +INC = -I$(ZEPHYR_BASE)/include +LIBPATH = $(ZEPHYR_BASE)/subsys/net/lib/zstream +LIBSRC = $(LIBPATH)/zstream_sock.c \ + $(LIBPATH)/zstream_tls.c \ + $(LIBPATH)/../tls_conf/tls_conf.c \ + +LDLIBS = -lmbedtls -lmbedx509 -lmbedcrypto + +CFLAGS = -DCONFIG_MBEDTLS -DCONFIG_MBEDTLS_DEBUG_LEVEL=0 + socket_echo: src/socket_echo.c - $(CC) $^ -o $@ + $(CC) $(INC) $(CFLAGS) $^ $(LIBSRC) $(LDLIBS) -o $@ diff --git a/samples/net/sockets/echo_async/prj.conf b/samples/net/sockets/echo_async/prj.conf index bfab069ab9b9a..68096403065bc 100644 --- a/samples/net/sockets/echo_async/prj.conf +++ b/samples/net/sockets/echo_async/prj.conf @@ -1,5 +1,10 @@ # General config CONFIG_NEWLIB_LIBC=y +CONFIG_MAIN_STACK_SIZE=6000 +CONFIG_MBEDTLS=y +CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=16384 +CONFIG_MBEDTLS_ENABLE_HEAP=y +CONFIG_MBEDTLS_HEAP_SIZE=50000 # Networking config CONFIG_NETWORKING=y diff --git a/samples/net/sockets/echo_async/src/socket_echo.c b/samples/net/sockets/echo_async/src/socket_echo.c index a5a077606d079..2a88421be88d7 100644 --- a/samples/net/sockets/echo_async/src/socket_echo.c +++ b/samples/net/sockets/echo_async/src/socket_echo.c @@ -25,6 +25,12 @@ #endif +#include +#include +#include + +#include "../../../echo_server/src/test_certs.h" + /* For Zephyr, keep max number of fd's in sync with max poll() capacity */ #ifdef CONFIG_NET_SOCKETS_POLL_MAX #define NUM_FDS CONFIG_NET_SOCKETS_POLL_MAX @@ -32,11 +38,17 @@ #define NUM_FDS 5 #endif +#define NUM_LISTEN_FDS 2 + #define PORT 4242 /* Number of simultaneous client connections will be NUM_FDS be minus 2 */ struct pollfd pollfds[NUM_FDS]; +struct zstream_sock streams_sock[NUM_FDS]; +struct zstream_tls streams_tls[NUM_FDS]; int pollnum; +static mbedtls_ssl_config *tls_conf; +static struct ztls_cert_key_pair cert_key; static void nonblock(int fd) { @@ -50,7 +62,7 @@ static void block(int fd) fcntl(fd, F_SETFL, fl & ~O_NONBLOCK); } -int pollfds_add(int fd) +int register_sock(int fd) { int i; if (pollnum < NUM_FDS) { @@ -66,20 +78,29 @@ int pollfds_add(int fd) } found: + /* Don't create streams for listening sockets */ + if (i >= NUM_LISTEN_FDS) { + struct zstream *stream; + + zstream_sock_init(&streams_sock[i], fd); + stream = (struct zstream *)&streams_sock[i]; + + if (zstream_tls_init(&streams_tls[i], stream, tls_conf, NULL) < 0) { + printf("Error creating TLS connection\n"); + return -1; + } + } + pollfds[i].fd = fd; pollfds[i].events = POLLIN; return 0; } -void pollfds_del(int fd) +void unregister_sock(int idx) { - for (int i = 0; i < pollnum; i++) { - if (pollfds[i].fd == fd) { - pollfds[i].fd = -1; - break; - } - } + zstream_close((struct zstream *)&streams_tls[idx]); + pollfds[idx].fd = -1; } int main(void) @@ -100,6 +121,27 @@ int main(void) .sin6_addr = IN6ADDR_ANY_INIT, }; + if (ztls_get_tls_server_conf(&tls_conf) < 0) { + printf("Unable to initialize TLS\n"); + return 1; + } + + res = ztls_parse_cert_key_pair(&cert_key, + rsa_example_cert_der, + rsa_example_cert_der_len, + rsa_example_keypair_der, + rsa_example_keypair_der_len); + if (res < 0) { + printf("Unable to parse cert/privkey\n"); + return 1; + } + + res = ztls_conf_add_own_cert_key_pair(tls_conf, &cert_key); + if (res < 0) { + printf("Unable to set cert/privkey\n"); + return 1; + } + serv4 = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); res = bind(serv4, (struct sockaddr *)&bind_addr4, sizeof(bind_addr4)); if (res == -1) { @@ -124,8 +166,8 @@ int main(void) listen(serv4, 5); listen(serv6, 5); - pollfds_add(serv4); - pollfds_add(serv6); + register_sock(serv4); + register_sock(serv6); printf("Asynchronous TCP echo server waits for connections on port %d...\n", PORT); @@ -145,7 +187,7 @@ int main(void) continue; } int fd = pollfds[i].fd; - if (i < 2) { + if (i < NUM_LISTEN_FDS) { /* If server socket */ int client = accept(fd, (struct sockaddr *)&client_addr, &client_addr_len); @@ -155,23 +197,24 @@ int main(void) addr_str, sizeof(addr_str)); printf("Connection #%d from %s fd=%d\n", counter++, addr_str, client); - if (pollfds_add(client) < 0) { - static char msg[] = "Too many connections\n"; - send(client, msg, sizeof(msg) - 1, 0); + if (register_sock(client) < 0) { close(client); } else { nonblock(client); } } else { char buf[128]; - int len = recv(fd, buf, sizeof(buf), 0); + int len = zstream_read((struct zstream *)&streams_tls[i], buf, sizeof(buf)); if (len <= 0) { if (len < 0) { + if (errno == EAGAIN) { + /* Underlying socket could be readable, but stream still have EAGAIN */ + continue; + } printf("error: recv: %d\n", errno); } error: - pollfds_del(fd); - close(fd); + unregister_sock(i); printf("Connection fd=%d closed\n", fd); } else { int out_len; @@ -185,7 +228,7 @@ int main(void) */ block(fd); for (p = buf; len; len -= out_len) { - out_len = send(fd, p, len, 0); + out_len = zstream_write((struct zstream *)&streams_tls[i], p, len); if (out_len < 0) { printf("error: " "send: %d\n", diff --git a/samples/net/sockets/http_get/Makefile.posix b/samples/net/sockets/http_get/Makefile.posix index 7f0573c2f708d..b3c25ab57f070 100644 --- a/samples/net/sockets/http_get/Makefile.posix +++ b/samples/net/sockets/http_get/Makefile.posix @@ -1,4 +1,17 @@ # This makefile builds the sample for a POSIX system, like Linux +INC = -I$(ZEPHYR_BASE)/include + +LIBPATH = $(ZEPHYR_BASE)/subsys/net/lib/zstream + +LIBSRC = \ + $(LIBPATH)/zstream_sock.c \ + $(LIBPATH)/zstream_tls.c \ + $(LIBPATH)/../tls_conf/tls_conf.c \ + +LDLIBS = -lmbedtls -lmbedx509 -lmbedcrypto + +CFLAGS = -DCONFIG_MBEDTLS -DCONFIG_MBEDTLS_DEBUG_LEVEL=0 + http_get: src/http_get.c - $(CC) $^ -o $@ + $(CC) $(INC) $(CFLAGS) $^ $(LIBSRC) $(LDLIBS) -o $@ diff --git a/samples/net/sockets/http_get/prj.conf b/samples/net/sockets/http_get/prj.conf index c2e01b27647ee..13240bd49c253 100644 --- a/samples/net/sockets/http_get/prj.conf +++ b/samples/net/sockets/http_get/prj.conf @@ -1,5 +1,11 @@ # General config CONFIG_NEWLIB_LIBC=y +CONFIG_MAIN_STACK_SIZE=4096 +CONFIG_MBEDTLS=y +CONFIG_MBEDTLS_CFG_FILE="config-mini-tls1_2.h" +CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=16384 +CONFIG_MBEDTLS_ENABLE_HEAP=y +CONFIG_MBEDTLS_HEAP_SIZE=50000 # Networking config CONFIG_NETWORKING=y diff --git a/samples/net/sockets/http_get/sample.yaml b/samples/net/sockets/http_get/sample.yaml index c0e36f1ccbcb2..6d27170938ce4 100644 --- a/samples/net/sockets/http_get/sample.yaml +++ b/samples/net/sockets/http_get/sample.yaml @@ -4,5 +4,6 @@ sample: tests: test: harness: net - min_ram: 32 + min_flash: 160 + min_ram: 90 tags: net diff --git a/samples/net/sockets/http_get/src/http_get.c b/samples/net/sockets/http_get/src/http_get.c index 8785eb8a554a8..cc8b9da3d9f49 100644 --- a/samples/net/sockets/http_get/src/http_get.c +++ b/samples/net/sockets/http_get/src/http_get.c @@ -23,10 +23,14 @@ #endif +#include +#include +#include + /* HTTP server to connect to */ #define HTTP_HOST "google.com" /* Port to connect to, as string */ -#define HTTP_PORT "80" +#define HTTP_PORT "443" /* HTTP path to request */ #define HTTP_PATH "/" @@ -36,6 +40,8 @@ #define REQUEST "GET " HTTP_PATH " HTTP/1.0\r\nHost: " HTTP_HOST "\r\n\r\n" +int http_request(struct zstream *stream); + static char response[1024]; void dump_addrinfo(const struct addrinfo *ai) @@ -52,6 +58,15 @@ int main(void) static struct addrinfo hints; struct addrinfo *res; int st, sock; + struct zstream_sock stream_sock; + struct zstream_tls stream_tls; + struct zstream *stream; + mbedtls_ssl_config *tls_conf; + + if (ztls_get_tls_client_conf(&tls_conf) < 0) { + printf("Unable to initialize TLS\n"); + return 1; + } printf("Preparing HTTP GET request for http://" HTTP_HOST ":" HTTP_PORT HTTP_PATH "\n"); @@ -78,12 +93,34 @@ int main(void) CHECK(sock); printf("sock = %d\n", sock); CHECK(connect(sock, res->ai_addr, res->ai_addrlen)); - CHECK(send(sock, REQUEST, SSTRLEN(REQUEST), 0)); + + /* Wrap socket into a stream */ + zstream_sock_init(&stream_sock, sock); + stream = (struct zstream *)&stream_sock; + + /* Wrap socket stream into a TLS stream */ + mbedtls_ssl_conf_authmode(tls_conf, MBEDTLS_SSL_VERIFY_NONE); + printf("Warning: site certificate is not validated\n"); + + st = zstream_tls_init(&stream_tls, stream, tls_conf, NULL); + if (st < 0) { + printf("Unable to create TLS connection\n"); + return 1; + } + stream = (struct zstream *)&stream_tls; + + return http_request(stream); +} + +int http_request(struct zstream *stream) +{ + CHECK(zstream_write(stream, REQUEST, SSTRLEN(REQUEST))); + CHECK(zstream_flush(stream)); printf("Response:\n\n"); while (1) { - int len = recv(sock, response, sizeof(response) - 1, 0); + int len = zstream_read(stream, response, sizeof(response) - 1); if (len < 0) { printf("Error reading response\n"); @@ -98,7 +135,7 @@ int main(void) printf("%s\n", response); } - (void)close(sock); + (void)zstream_close(sock); return 0; } diff --git a/samples/net/zstream/big_http_download/CMakeLists.txt b/samples/net/zstream/big_http_download/CMakeLists.txt new file mode 100644 index 0000000000000..e6bf10afba6f5 --- /dev/null +++ b/samples/net/zstream/big_http_download/CMakeLists.txt @@ -0,0 +1,7 @@ +include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE) +project(NONE) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) + +include($ENV{ZEPHYR_BASE}/samples/net/common/common.cmake) diff --git a/samples/net/zstream/big_http_download/Makefile.posix b/samples/net/zstream/big_http_download/Makefile.posix new file mode 100644 index 0000000000000..a5a12452e53bc --- /dev/null +++ b/samples/net/zstream/big_http_download/Makefile.posix @@ -0,0 +1,16 @@ +# This makefile builds the sample for a POSIX system, like Linux +# It required mbedTLS to be installed on the system, e.g. +# libmbedtls-dev package on Debian/Ubuntu Linux. + +INC = -I$(ZEPHYR_BASE)/include +LIBPATH = $(ZEPHYR_BASE)/subsys/net/lib/zstream +LIBSRC = $(LIBPATH)/zstream_sock.c \ + $(LIBPATH)/zstream_tls.c \ + $(LIBPATH)/../tls_conf/tls_conf.c \ + +LDLIBS = -lmbedtls -lmbedx509 -lmbedcrypto + +CFLAGS = -DCONFIG_MBEDTLS -DCONFIG_MBEDTLS_DEBUG_LEVEL=0 + +big_http_download: src/big_http_download.c + $(CC) $(INC) $(CFLAGS) $^ $(LIBSRC) $(LDLIBS) -o $@ diff --git a/samples/net/zstream/big_http_download/README.rst b/samples/net/zstream/big_http_download/README.rst new file mode 100644 index 0000000000000..972d7c96743b1 --- /dev/null +++ b/samples/net/zstream/big_http_download/README.rst @@ -0,0 +1,84 @@ +.. _zstream-big-http-download: + +Zstrean API Big HTTP Download Example +##################################### + +Overview +******** + +The zstream/big_http_download sample application for Zephyr implements +a simple HTTP GET client using a BSD Sockets compatible API. It is a +straightforward convesion of the `sockets-big-http-download` sample +to zstream API, thanks to which the sample acquires HTTPS support. + +The source code for this sample application can be found at: +:file:`samples/net/zstream/big_http_download`. + +Requirements +************ + +- :ref:`networking_with_qemu` +- or, a board with hardware networking +- NAT/routing should be set up to allow connections to the Internet +- DNS server should be available on the host to resolve domain names + +Building and Running +******************** + +Build the Zephyr version of the application like this: + +.. zephyr-app-commands:: + :zephyr-app: zstream/net/sockets/big_http_download + :board: + :goals: build + :compact: + +``board_to_use`` defaults to ``qemu_x86``. In this case, you can run the +application in QEMU using ``make run``. If you used another BOARD, you +will need to consult its documentation for application deployment +instructions. You can read about Zephyr support for specific boards in +the documentation at :ref:`boards`. + +After the sample starts, it issues an HTTPS GET request for +https://ftp.gnu.org/gnu/tar/tar-1.13.tar . This site was selected as +providing files of variety of sizes. The particular file selected is +3.8MB in size, so it can show how reliably Zephyr streams non-trivial +amounts of data, while still taking a reasonable amount of time to +complete. While the file is downloaded, its hash is computed (SHA-256 +is used in the source code), and an error message is printed if it +differs from the reference value, as specified in the source code. +After a short pause, the process repeats (in an infinite loop), while +the total counter of the bytes received is kept. Thus the application +can be used to test transfers of much larger amounts of traffic over +a longer time. + +You can edit the source code to issue a request to any other site on +the Internet (or on the local network, in which case no NAT/routing +setup is needed). Note that plain http:// downloads are also supported. + +.. warning:: + + If you are doing extensive testing with this sample, please reference + a file on a local server or a special-purpose testing server of your own + on the Internet. Using files on archive.ubuntu.com is not recommended for + large-scale testing. + +Running application on POSIX Host +================================= + +The same application source code can be built for a POSIX system, e.g. +Linux. + +To build for a host POSIX OS: + +.. code-block:: console + + $ make -f Makefile.posix + +To run: + +.. code-block:: console + + $ ./big_http_download + +The behavior of the application is the same as the Zephyr version. diff --git a/samples/net/zstream/big_http_download/prj.conf b/samples/net/zstream/big_http_download/prj.conf new file mode 100644 index 0000000000000..abc6a4a25cb59 --- /dev/null +++ b/samples/net/zstream/big_http_download/prj.conf @@ -0,0 +1,39 @@ +# General config +CONFIG_NEWLIB_LIBC=y +CONFIG_MAIN_STACK_SIZE=6000 +CONFIG_MBEDTLS=y +CONFIG_MBEDTLS_CFG_FILE="config-tls1_2.h" +CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=16384 +CONFIG_MBEDTLS_ENABLE_HEAP=y +CONFIG_MBEDTLS_HEAP_SIZE=60000 +#CONFIG_MBEDTLS_DEBUG=y + +# Networking config +CONFIG_NETWORKING=y +CONFIG_NET_IPV4=y +CONFIG_NET_IPV6=y +CONFIG_NET_TCP=y +CONFIG_NET_SOCKETS=y +CONFIG_NET_SOCKETS_POSIX_NAMES=y + +CONFIG_NET_PKT_TX_COUNT=6 + +CONFIG_DNS_RESOLVER=y +CONFIG_DNS_SERVER_IP_ADDRESSES=y +CONFIG_DNS_SERVER1="192.0.2.2" + +# Network driver config +CONFIG_TEST_RANDOM_GENERATOR=y + +# Network address config +CONFIG_NET_APP_SETTINGS=y +CONFIG_NET_APP_NEED_IPV4=y +CONFIG_NET_APP_MY_IPV4_ADDR="192.0.2.1" +CONFIG_NET_APP_PEER_IPV4_ADDR="192.0.2.2" +CONFIG_NET_APP_MY_IPV4_GW="192.0.2.2" + +# Network debug config +CONFIG_NET_LOG=y +CONFIG_NET_LOG_GLOBAL=y +CONFIG_SYS_LOG_NET_LEVEL=2 +#CONFIG_NET_DEBUG_SOCKETS=y diff --git a/samples/net/zstream/big_http_download/sample.yaml b/samples/net/zstream/big_http_download/sample.yaml new file mode 100644 index 0000000000000..c9744a0168c2b --- /dev/null +++ b/samples/net/zstream/big_http_download/sample.yaml @@ -0,0 +1,11 @@ +sample: + description: Zstream API big HTTP download example + name: big_http_download +tests: + test: + harness: net + min_ram: 110 + min_flash: 192 + # Even with lots of RAM/flash, runs out of it + platform_exclude: minnowboard + tags: net diff --git a/samples/net/zstream/big_http_download/src/big_http_download.c b/samples/net/zstream/big_http_download/src/big_http_download.c new file mode 100644 index 0000000000000..c30c73b4770c3 --- /dev/null +++ b/samples/net/zstream/big_http_download/src/big_http_download.c @@ -0,0 +1,285 @@ +/* + * Copyright (c) 2018 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "mbedtls/md.h" + +#ifndef __ZEPHYR__ + +#include +#include +#include +#include +#include + +#else + +#include +#include +#include +#define sleep(x) k_sleep(x * 1000) + +#endif + +#include +#include +#include + +/* This URL is parsed in-place, so buffer must be non-const. */ +static char download_url[] = + /*"http://archive.ubuntu.com/ubuntu/dists/xenial/main/installer-amd64/current/images/hd-media/vmlinuz";*/ + "https://ftp.gnu.org/gnu/tar/tar-1.13.tar"; +/* Quick testing. */ +/* "http://google.com/foo";*/ + +/* To generate: */ +/* print("".join(["\\x%02x" % x for x in list(binascii.unhexlify("hash"))])) */ +static uint8_t download_hash[32] = + /*"\x33\x7c\x37\xd7\xec\x00\x34\x84\x14\x22\x4b\xaa\x6b\xdb\x2d\x43\xf2\xa3\x4e\xf5\x67\x6b\xaf\xcd\xca\xd9\x16\xf1\x48\xb5\xb3\x17";*/ + "\xbe\x12\xfe\x40\xe1\xb2\x02\xd7\x0c\x45\x5d\x78\x4f\xbe\xc8\xcd\xb3\x38\xfe\x01\x1a\xb9\xe8\x62\x95\x81\x35\x68\x90\x6f\x60\x73"; + +#define SSTRLEN(s) (sizeof(s) - 1) +#define CHECK(r) { if (r == -1) { printf("Error: " #r "\n"); } } + +const char *host; +const char *port; +const char *uri_path = ""; +static char response[1024]; +static char response_hash[32]; +mbedtls_md_context_t hash_ctx; +const mbedtls_md_info_t *hash_info; +unsigned int cur_bytes; + +void dump_addrinfo(const struct addrinfo *ai) +{ + printf("addrinfo @%p: ai_family=%d, ai_socktype=%d, ai_protocol=%d, " + "sa_family=%d, sin_port=%x\n", + ai, ai->ai_family, ai->ai_socktype, ai->ai_protocol, + ai->ai_addr->sa_family, + ((struct sockaddr_in *)ai->ai_addr)->sin_port); +} + +void fatal(const char *msg) +{ + printf("Error: %s\n", msg); + exit(1); +} + +ssize_t sendall(struct zstream *stream, const void *buf, size_t len) +{ + while (len) { + ssize_t out_len = zstream_write(stream, buf, len); + if (out_len < 0) { + return out_len; + } + buf = (const char *)buf + out_len; + len -= out_len; + } + + return 0; +} + +int skip_headers(struct zstream *stream) +{ + int state = 0; + + while (1) { + char c; + int st; + + st = zstream_read(stream, &c, 1); + if (st <= 0) { + return st; + } + + if (state == 0 && c == '\r') { + state++; + } else if (state == 1 && c == '\n') { + state++; + } else if (state == 2 && c == '\r') { + state++; + } else if (state == 3 && c == '\n') { + break; + } else { + state = 0; + } + } + + return 1; +} + +void print_hex(const unsigned char *p, int len) +{ + while (len--) { + printf("%02x", *p++); + } +} + +void download(struct addrinfo *ai, mbedtls_ssl_config *tls_conf) +{ + int sock; + struct zstream_sock stream_sock; + struct zstream_tls stream_tls; + struct zstream *stream; + + cur_bytes = 0; + + sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + CHECK(sock); + printf("sock = %d\n", sock); + CHECK(connect(sock, ai->ai_addr, ai->ai_addrlen)); + + zstream_sock_init(&stream_sock, sock); + stream = (struct zstream *)&stream_sock; + + if (tls_conf) { + zstream_tls_init(&stream_tls, stream, tls_conf, host); + stream = (struct zstream *)&stream_tls; + } + + sendall(stream, "GET /", SSTRLEN("GET /")); + sendall(stream, uri_path, strlen(uri_path)); + sendall(stream, " HTTP/1.0\r\n", SSTRLEN(" HTTP/1.0\r\n")); + sendall(stream, "Host: ", SSTRLEN("Host: ")); + sendall(stream, host, strlen(host)); + sendall(stream, "\r\n\r\n", SSTRLEN("\r\n\r\n")); + zstream_flush(stream); + + if (skip_headers(stream) <= 0) { + printf("EOF or error in response headers\n"); + goto error; + } + + mbedtls_md_starts(&hash_ctx); + + while (1) { + int len = zstream_read(stream, response, sizeof(response) - 1); + + if (len < 0) { + printf("Error reading response\n"); + goto error; + } + + if (len == 0) { + break; + } + + mbedtls_md_update(&hash_ctx, response, len); + + cur_bytes += len; + printf("%u bytes\r", cur_bytes); + + response[len] = 0; + /*printf("%s\n", response);*/ + } + + printf("\n"); + + mbedtls_md_finish(&hash_ctx, response_hash); + + printf("Hash: "); + print_hex(response_hash, mbedtls_md_get_size(hash_info)); + printf("\n"); + + if (memcmp(response_hash, download_hash, + mbedtls_md_get_size(hash_info)) != 0) { + printf("HASH MISMATCH!\n"); + } + +error: + zstream_close(stream); +} + +int main(void) +{ + static struct addrinfo hints; + struct addrinfo *res; + int st; + char *p; + unsigned int total_bytes = 0; + mbedtls_ssl_config *tls_conf = NULL; + + setbuf(stdout, NULL); + + if (strncmp(download_url, "http://", SSTRLEN("http://")) == 0) { + port = "80"; + p = download_url + SSTRLEN("http://"); + } else if (strncmp(download_url, "https://", + SSTRLEN("https://")) == 0) { + if (ztls_get_tls_client_conf(&tls_conf) < 0) { + printf("Unable to initialize TLS\n"); + return 1; + } + mbedtls_ssl_conf_authmode(tls_conf, MBEDTLS_SSL_VERIFY_NONE); + printf("Warning: site certificate is not validated\n"); + port = "443"; + p = download_url + SSTRLEN("https://"); + } else { + fatal("Only http: and https: URLs are supported"); + } + + /* Parse host part */ + host = p; + while (*p && *p != ':' && *p != '/') { + p++; + } + + /* Store optional port part */ + if (*p == ':') { + *p++ = 0; + port = p; + } + + /* Parse path part */ + while (*p && *p != '/') { + p++; + } + + if (*p == '/') { + *p++ = 0; + uri_path = p; + } + + printf("Preparing HTTP GET request for http%s://%s:%s/%s\n", + (tls_conf ? "s" : ""), host, port, uri_path); + + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + st = getaddrinfo(host, port, &hints, &res); + printf("getaddrinfo status: %d\n", st); + + if (st != 0) { + fatal("Unable to resolve address"); + } + + dump_addrinfo(res); + + hash_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); + if (!hash_info) { + fatal("Unable to request hash type from mbedTLS"); + } + + mbedtls_md_init(&hash_ctx); + if (mbedtls_md_setup(&hash_ctx, hash_info, 0) < 0) { + fatal("Can't setup mbedTLS hash engine"); + } + + while (1) { + download(res, tls_conf); + + total_bytes += cur_bytes; + printf("Total downloaded so far: %uMB\n", total_bytes / (1024 * 1024)); + + sleep(3); + } + + mbedtls_md_free(&hash_ctx); + + return 0; +} diff --git a/subsys/net/lib/CMakeLists.txt b/subsys/net/lib/CMakeLists.txt index 5296b3aa08e85..b91a899a50f7f 100644 --- a/subsys/net/lib/CMakeLists.txt +++ b/subsys/net/lib/CMakeLists.txt @@ -5,7 +5,10 @@ add_subdirectory_ifdef(CONFIG_DNS_RESOLVER dns) add_subdirectory_ifdef(CONFIG_MQTT_LIB mqtt) add_subdirectory_ifdef(CONFIG_NET_APP_SETTINGS app) add_subdirectory_ifdef(CONFIG_NET_SOCKETS sockets) +add_subdirectory_ifdef(CONFIG_NET_SOCKETS zstream) add_subdirectory_ifdef(CONFIG_WEBSOCKET websocket) +add_subdirectory_ifdef(CONFIG_MBEDTLS tls_conf) +add_subdirectory_ifdef(CONFIG_NET_ASYNC_SOCKET async_socket) if(CONFIG_HTTP_PARSER_URL OR CONFIG_HTTP_PARSER diff --git a/subsys/net/lib/Kconfig b/subsys/net/lib/Kconfig index 8bd37062d9a6f..3e107bb9e0d4f 100644 --- a/subsys/net/lib/Kconfig +++ b/subsys/net/lib/Kconfig @@ -28,4 +28,6 @@ source "subsys/net/lib/app/Kconfig" source "subsys/net/lib/sockets/Kconfig" +source "subsys/net/lib/async_socket/Kconfig" + endmenu diff --git a/subsys/net/lib/async_socket/CMakeLists.txt b/subsys/net/lib/async_socket/CMakeLists.txt new file mode 100644 index 0000000000000..36b8964133c2c --- /dev/null +++ b/subsys/net/lib/async_socket/CMakeLists.txt @@ -0,0 +1,4 @@ +zephyr_include_directories(.) +zephyr_sources( + async_socket.c + ) diff --git a/subsys/net/lib/async_socket/Kconfig b/subsys/net/lib/async_socket/Kconfig new file mode 100644 index 0000000000000..0ed90a063a600 --- /dev/null +++ b/subsys/net/lib/async_socket/Kconfig @@ -0,0 +1,31 @@ +# Kconfig - Async sockets over BSD Sockets + +# +# Copyright (c) 2018, Texas Instruments Incorporated +# +# SPDX-License-Identifier: Apache-2.0 +# + +menuconfig NET_ASYNC_SOCKET + bool "Async socket API over BSD Sockets" + default n + select NET_SOCKETS + select NET_SOCKETS_POSIX_NAMES + select NEWLIB_LIBC # Needed to find + help + Asynchronous adaptation layer over BSD sockets". + +if NET_ASYNC_SOCKET + +config NET_DEBUG_ASYNC_SOCKET + bool "Debug Async Sockets API" + default n + default y if NET_LOG_GLOBAL + help + Enables logging for async sockets code. + +config NET_ASYNC_SOCKET_PRIO + int "Startup priority for the async socket server" + default 90 + +endif # NET_ASYNC_SOCKET diff --git a/subsys/net/lib/async_socket/async_socket.c b/subsys/net/lib/async_socket/async_socket.c new file mode 100644 index 0000000000000..20f9e3f23781f --- /dev/null +++ b/subsys/net/lib/async_socket/async_socket.c @@ -0,0 +1,389 @@ +/* + * Copyright (c) 2018, Texas Instruments Incorporated + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#if defined(CONFIG_NET_DEBUG_ASYNC_SOCKETS) +#define SYS_LOG_DOMAIN "net/async_sock" +#define NET_LOG_ENABLED 1 +#endif + +/* Zephyr headers */ +#include +#include +#include + +/* Convenience macro to check for invalid socket fd */ +#define INVALID_SOCK (-1) + +/* Add one for signal_socket to unblock poll() on demand: */ +#define MAX_RCV_CALLBACKS (CONFIG_NET_SOCKETS_POLL_MAX + 1) + +#define LOOPBACK_ADDR "127.0.0.1" +#define DISCARD_PORT 9 + +#define SIGNAL_MSG "POLL" +#define SIGNAL_MSG_SIZE 4 + +#define ASYNC_SOCK_TASK_STACKSIZE 1024 +#define ASYNC_SOCK_TASK_PRIORITY K_HIGHEST_THREAD_PRIO + +/*Socket to callback map, for use by async_recv() and async_sock_server() */ +struct rcv_callbacks { + /* Underlying socket id for poll() operation */ + int sock; + + /* Stream for I/O operations */ + struct zstream *stream; + + /* Buffer for receive and its maximum length */ + void *buf; + size_t max_len; + + /* Callback to be triggered when socket receives data */ + async_recv_cb_t cb; + + /* User data to pass back to the callback */ + void *cb_data; +}; +static struct rcv_callbacks rcv_callback[MAX_RCV_CALLBACKS] = { 0, }; +static int num_rcv_cbs; + +/* Special loopback socket used to unblock the poll() API: */ +static int signal_sock; +static struct sockaddr_in loopback_addr; + +/* Dedicated buffer for loopback message: */ +static u8_t server_rcv_buf[SIGNAL_MSG_SIZE]; + +/* Loopback socket's bind address: */ +static struct sockaddr_in bind_addr = { + .sin_family = AF_INET, + .sin_port = htons(DISCARD_PORT), + .sin_addr = { + .s_addr = htonl(INADDR_ANY), + }, +}; + +/* Async socket server thread: */ +static K_THREAD_STACK_DEFINE(async_sock_task_stack, ASYNC_SOCK_TASK_STACKSIZE); +static struct k_thread async_sock_task_data; + +/* Helper function, to restart poll() loop: */ +static int async_server_restart(void) +{ + int rc, status = 0; + + rc = sendto(signal_sock, SIGNAL_MSG, SIGNAL_MSG_SIZE, 0, + (struct sockaddr *)&loopback_addr, + sizeof(loopback_addr)); + if (rc < 0) { + status = -errno; + } + return status; +} + +/* Receive callback helper mapping functions: */ + +static struct rcv_callbacks *get_rcv_callback_slot(int sock) +{ + struct rcv_callbacks *entry, *end; + + end = rcv_callback + ARRAY_SIZE(rcv_callback); + for (entry = &rcv_callback[0]; entry < end; entry++) { + if (entry->sock == sock) { + return entry; + } + } + return NULL; +} + +static void rcv_callbacks_init(void) +{ + struct rcv_callbacks *entry, *end; + + end = rcv_callback + ARRAY_SIZE(rcv_callback); + for (entry = &rcv_callback[0]; entry < end; entry++) { + entry->sock = INVALID_SOCK; + } +} + +static int rcv_callback_register(int sock, struct zstream *stream, + void *buf, size_t max_len, + async_recv_cb_t cb, void *cb_data) +{ + int status = 0; + struct rcv_callbacks *rcv_cb = NULL; + + /* See if this sock already has a registered callback: */ + rcv_cb = get_rcv_callback_slot(sock); + if (!rcv_cb) { + /* If not, see if we have room: */ + if (num_rcv_cbs == MAX_RCV_CALLBACKS) { + /* No more room: */ + NET_ERR("Increase CONFIG_NET_SOCKETS_POLL_MAX"); + } else { + /* Find new slot for this socket: */ + rcv_cb = get_rcv_callback_slot(INVALID_SOCK); + } + } + + if (rcv_cb) { + rcv_cb->sock = sock; + rcv_cb->stream = stream; + rcv_cb->buf = buf; + rcv_cb->max_len = max_len; + rcv_cb->cb = cb; + rcv_cb->cb_data = cb_data; + + num_rcv_cbs++; + + /* Now, signal the signal_sock to restart the poll server, + * so it can recreate the ufds[] list and start poll() + * with the newly registered socket. + */ + status = async_server_restart(); + } else { + status = -ENOMEM; + } + + return status; +} + +static void rcv_callback_deregister(short int sock) +{ + int status = 0; + struct rcv_callbacks *rcv_cb = NULL; + + /* Find slot of this socket: */ + rcv_cb = get_rcv_callback_slot(sock); + if (rcv_cb) { + rcv_cb->sock = INVALID_SOCK; + rcv_cb->buf = NULL; + rcv_cb->max_len = 0; + rcv_cb->cb = NULL; + rcv_cb->cb_data = NULL; + + num_rcv_cbs--; + + /* Now, signal the signal_sock to restart the poll server, + * so it can recreate the ufds[] list and start poll() + * *without* the newly un-registered socket. + */ + status = async_server_restart(); + } +} + +/* Fill the poll() ufds argument from the rcv_callback list: */ +static void rcv_callbacks_to_ufds(struct pollfd *fds) +{ + struct rcv_callbacks *entry, *end; + int fds_index = 0; + + end = rcv_callback + ARRAY_SIZE(rcv_callback); + for (entry = &rcv_callback[0], fds_index = 0; entry < end; entry++) { + if (entry->sock != INVALID_SOCK) { + fds[fds_index].fd = entry->sock; + fds[fds_index].events = POLLIN; + fds_index++; + } + } +} + +/* + * Call poll() in a loop, waiting for registered async sockets + * to get signalled; if so, call their registered receive callbacks. + * + * Currently, this only handles receive. Send, accept and other + * events are not handled. + * + * Problem: how to break out of a blocking poll() to reset + * the list of events? (Without using a finite timeout - + * which is not good from a power perspective). + * In Linux, we could use eventfd() to add an fd to the poll() + * list of fds, to enable breaking out of the poll() when + * a new fd is added/removed. + * But, Zephyr's poll() API only supports sockets for now, and Zephyr + * does not yet support something like eventfd(). + * Calling close() on a bespoke socket placed in the list just + * for this purpose *might* unblock a + * poll()/select() operation with a POLLHUP event, + * but that is not implemented, and not clear in the POSIX standard. + * + * Solution: Reserve a UDP socket to 'signal' by sending a + * small message to the LOOPBACK address, unblocking the poll. + * The loopback is processed in Zephyr IP stack before calling + * the L2 driver, so this should work generally. + */ +void async_sock_server(void *unused1, void *unused2, void *unused3) +{ + ARG_UNUSED(unused1); + ARG_UNUSED(unused2); + ARG_UNUSED(unused3); + + static struct pollfd ufds[MAX_RCV_CALLBACKS]; + static struct sockaddr_storage from; + int nfds; + struct pollfd *entry, *end; + struct rcv_callbacks *cb_entry; + size_t size; + int rcv_len; + size_t from_len; + + /* signal_sock is never closed, and always remains in first position */ + ufds[0].fd = signal_sock; + ufds[0].events = POLLIN; + + /* End of ufds[] array */ + end = ufds + MAX_RCV_CALLBACKS; + + while (1) { + /* Update array of polling structs: */ + rcv_callbacks_to_ufds(&ufds[1]); + + /* Wait until any socket gets signalled: */ + nfds = poll(ufds, (num_rcv_cbs + 1), K_FOREVER); + NET_ASSERT(nfds != 0); /* Timeout should be impossible */ + + if (nfds > 0) { + /* signal_sock signalled via loopback msg: */ + if (ufds[0].revents & POLLIN) { + NET_ASSERT(ufds[0].fd == signal_sock); + + /* Just get and discard data: */ + rcv_len = recvfrom(ufds[0].fd, server_rcv_buf, + SIGNAL_MSG_SIZE, 0, + (struct sockaddr *)&from, + &from_len); + NET_ASSERT(rcv_len == SIGNAL_MSG_SIZE); + if (rcv_len != SIGNAL_MSG_SIZE) { + NET_ERR("Received invalid message"); + } + nfds--; + } + + /* For each socket signalled, recv and fire callback: */ + for (entry = &ufds[1]; (entry < end) && nfds; entry++) { + if (entry->revents & POLLIN) { + cb_entry = + get_rcv_callback_slot(entry->fd); + NET_ASSERT(cb_entry != NULL); + + /* Retrieve the socket data: */ + size = zstream_read(cb_entry->stream, + cb_entry->buf, + cb_entry->max_len); + if (size < 0) { + NET_ERR("Socket errno: %d", + -errno); + } else if (size == 0) { + /* Peer shutdown: */ + async_close(cb_entry->sock, cb_entry->stream); + } else if (cb_entry->cb) { + /* Fire the callback: */ + cb_entry->cb(-1, /* should be ignored */ + cb_entry->buf, + size, + cb_entry->cb_data); + } + nfds--; + } + } + } else { + NET_ERR("poll failed with errno: %d", -errno); + } + } +} + +int async_connect(int sock, const struct sockaddr *addr, socklen_t addrlen, + async_connect_cb_t cb, void *cb_data) +{ + int status; + + status = connect(sock, addr, addrlen); + + if (cb) { + cb(sock, status, cb_data); + } + return(status); +} + +ssize_t async_send(struct zstream *sock, const void *buf, size_t len, + async_send_cb_t cb, void *cb_data, int flags) +{ + ssize_t bytes_sent; + + bytes_sent = zstream_writeall(sock, buf, len, NULL); + if (bytes_sent > 0) { + int res = zstream_flush(sock); + if (res < 0) { + bytes_sent = res; + } + } + + if (cb) { + cb(-1, bytes_sent, cb_data); + } + return bytes_sent; +} + +ssize_t async_recv(int sock, struct zstream *stream, void *buf, size_t max_len, + async_recv_cb_t cb, void *cb_data) +{ + int status; + + /* Store buf, max_len, cb, and cb_data args for this sock id */ + status = rcv_callback_register(sock, stream, buf, max_len, cb, cb_data); + + return status; +} + +int async_close(int sock, struct zstream *stream) +{ + /* Deregister any outstanding receive callbacks: */ + rcv_callback_deregister(sock); + + return zstream_close(stream); +} + + +int async_sock_init(struct device *device) +{ + int rc = 0, retval = 0; + + ARG_UNUSED(device); + + rcv_callbacks_init(); + + loopback_addr.sin_family = AF_INET; + rc = net_addr_pton(AF_INET, LOOPBACK_ADDR, &loopback_addr.sin_addr); + NET_ASSERT(!rc); + loopback_addr.sin_port = htons(DISCARD_PORT); + + /* Create a special socket to enable unblocking the server's poll(): */ + signal_sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (signal_sock == INVALID_SOCK) { + NET_ERR("Unable to create poll server socket: %d", -errno); + retval = -1; + } else { + rc = bind(signal_sock, (struct sockaddr *)&bind_addr, + sizeof(bind_addr)); + if (rc == -1) { + NET_ERR("Cannot bind poll server socket: %d\n", -errno); + retval = -1; + } else { + /* Start the async_socket receive server: */ + (void)k_thread_create(&async_sock_task_data, + async_sock_task_stack, + ASYNC_SOCK_TASK_STACKSIZE, + async_sock_server, + NULL, NULL, NULL, + ASYNC_SOCK_TASK_PRIORITY, + 0, K_NO_WAIT); + } + } + return retval; +} + +SYS_INIT(async_sock_init, APPLICATION, CONFIG_NET_ASYNC_SOCKET_PRIO); diff --git a/subsys/net/lib/mqtt/CMakeLists.txt b/subsys/net/lib/mqtt/CMakeLists.txt index 1a946223e06fd..b728270398934 100644 --- a/subsys/net/lib/mqtt/CMakeLists.txt +++ b/subsys/net/lib/mqtt/CMakeLists.txt @@ -1,6 +1,7 @@ zephyr_library() zephyr_library_sources( + net_utils.c mqtt_pkt.c mqtt.c ) diff --git a/subsys/net/lib/mqtt/Kconfig b/subsys/net/lib/mqtt/Kconfig index a3cfdada2421d..952f3dffa4b9b 100644 --- a/subsys/net/lib/mqtt/Kconfig +++ b/subsys/net/lib/mqtt/Kconfig @@ -9,7 +9,7 @@ config MQTT_LIB bool "MQTT Library Support" default n - select NET_APP_CLIENT + select NET_ASYNC_SOCKET help Enable the Zephyr MQTT Library diff --git a/subsys/net/lib/mqtt/mqtt.c b/subsys/net/lib/mqtt/mqtt.c index 3fe328d851ff2..7d1b0fc7fa107 100644 --- a/subsys/net/lib/mqtt/mqtt.c +++ b/subsys/net/lib/mqtt/mqtt.c @@ -4,23 +4,60 @@ * SPDX-License-Identifier: Apache-2.0 */ -#include +#include "net_utils.h" #include "mqtt_pkt.h" #include -#include -#include +#include +#include +/* + * Issue #5817 workaround: + * Ensuring mqtt.h after socket.h so .connect -> .zsock_connect + * everywhere: + */ +#include #include +#include +#include #include +#define MEM_ALIGN (sizeof(uint32_t)) #define MSG_SIZE CONFIG_MQTT_MSG_MAX_SIZE -#define MQTT_BUF_CTR (1 + CONFIG_MQTT_ADDITIONAL_BUFFER_CTR) +#define MQTT_BUF_CTR (2 + CONFIG_MQTT_ADDITIONAL_BUFFER_CTR) + +#define INVALID_SOCK (-1) + +/* Reset rc to 0 if rc > 0 (meaning, rc bytes sent), indicating success: */ +#define SET_ERRNO_AND_RC(rc) { rc = (rc < 0 ? -errno : 0); } -/* Memory pool internally used to handle messages that may exceed the size of - * system defined network buffer. By using this memory pool, routines don't deal - * with fragmentation, so algorithms are more easy to implement. +/* + * Memory pool for MQTT message buffers. + * The number of buffers should equal the number of mqtt contexts + * created by the application + one. + * - Each mqtt context needs a buffer for sending. + * - One buffer is needed for receiving across all contexts. */ -NET_BUF_POOL_DEFINE(mqtt_msg_pool, MQTT_BUF_CTR, MSG_SIZE, 0, NULL); +K_MEM_SLAB_DEFINE(mqtt_msg_pool, MSG_SIZE, MQTT_BUF_CTR, MEM_ALIGN); + +static void *_mqtt_msg_alloc() +{ + void *buf = NULL; + + if (k_mem_slab_alloc(&mqtt_msg_pool, (void **)&buf, K_NO_WAIT) < 0) { + /* + * We assert, as this is a logic error, due to the application + * overallocating from the mqtt msg pool. + */ + __ASSERT(0, "Increase size of mqtt msg pool"); + } + return buf; +} + +static void _mqtt_msg_free(void *buf) +{ + k_mem_slab_free(&mqtt_msg_pool, (void **)&buf); +} + #define MQTT_PUBLISHER_MIN_MSG_SIZE 2 @@ -30,52 +67,32 @@ NET_BUF_POOL_DEFINE(mqtt_msg_pool, MQTT_BUF_CTR, MSG_SIZE, 0, NULL); int mqtt_tx_connect(struct mqtt_ctx *ctx, struct mqtt_connect_msg *msg) { - struct net_buf *data = NULL; - struct net_pkt *tx = NULL; + void *data = NULL; + u16_t len; int rc; - data = net_buf_alloc(&mqtt_msg_pool, ctx->net_timeout); + data = _mqtt_msg_alloc(); if (data == NULL) { return -ENOMEM; } ctx->clean_session = msg->clean_session ? 1 : 0; - rc = mqtt_pack_connect(data->data, &data->len, MSG_SIZE, msg); + rc = mqtt_pack_connect(data, &len, MSG_SIZE, msg); if (rc != 0) { rc = -EINVAL; - goto exit_connect; - } - - tx = net_app_get_net_pkt(&ctx->net_app_ctx, - AF_UNSPEC, ctx->net_timeout); - if (tx == NULL) { - rc = -ENOMEM; - goto exit_connect; - } - - net_pkt_frag_add(tx, data); - data = NULL; - - rc = net_app_send_pkt(&ctx->net_app_ctx, - tx, NULL, 0, ctx->net_timeout, NULL); - if (rc < 0) { - net_pkt_unref(tx); + } else { + rc = async_send(ctx->stream, data, len, NULL, NULL, 0); + SET_ERRNO_AND_RC(rc); } - tx = NULL; - - return rc; - -exit_connect: - net_pkt_frag_unref(data); + _mqtt_msg_free(data); return rc; } int mqtt_tx_disconnect(struct mqtt_ctx *ctx) { - struct net_pkt *tx = NULL; /* DISCONNECT is a zero length message: 2 bytes required, no payload */ u8_t msg[2]; u16_t len; @@ -86,36 +103,18 @@ int mqtt_tx_disconnect(struct mqtt_ctx *ctx) return -EINVAL; } - tx = net_app_get_net_pkt(&ctx->net_app_ctx, - AF_UNSPEC, ctx->net_timeout); - if (tx == NULL) { - return -ENOMEM; - } - - rc = net_pkt_append_all(tx, len, msg, ctx->net_timeout); - if (rc != true) { - rc = -ENOMEM; - goto exit_disconnect; - } - - rc = net_app_send_pkt(&ctx->net_app_ctx, - tx, NULL, 0, ctx->net_timeout, NULL); + rc = async_send(ctx->stream, msg, len, NULL, NULL, 0); if (rc < 0) { - goto exit_disconnect; - } - - ctx->connected = 0; - tx = NULL; - - if (ctx->disconnect) { - ctx->disconnect(ctx); + rc = -errno; + } else { + rc = 0; /* Because caller expects 0 for success */ + ctx->connected = 0; + /* Call MQTT client's disconnect callback: */ + if (ctx->disconnect) { + ctx->disconnect(ctx); + } } - return rc; - -exit_disconnect: - net_pkt_unref(tx); - return rc; } @@ -135,7 +134,6 @@ static int mqtt_tx_pub_msgs(struct mqtt_ctx *ctx, u16_t id, enum mqtt_packet pkt_type) { - struct net_pkt *tx = NULL; u8_t msg[4]; u16_t len; int rc; @@ -158,34 +156,12 @@ int mqtt_tx_pub_msgs(struct mqtt_ctx *ctx, u16_t id, } if (rc != 0) { - return -EINVAL; - } - - tx = net_app_get_net_pkt(&ctx->net_app_ctx, - AF_UNSPEC, ctx->net_timeout); - if (tx == NULL) { - return -ENOMEM; - } - - rc = net_pkt_append_all(tx, len, msg, ctx->net_timeout); - if (rc != true) { - rc = -ENOMEM; - goto exit_send; - } - - rc = net_app_send_pkt(&ctx->net_app_ctx, - tx, NULL, 0, ctx->net_timeout, NULL); - if (rc < 0) { - goto exit_send; + rc = -EINVAL; + } else { + rc = async_send(ctx->stream, msg, len, NULL, NULL, 0); + SET_ERRNO_AND_RC(rc); } - tx = NULL; - - return rc; - -exit_send: - net_pkt_unref(tx); - return rc; } @@ -211,50 +187,30 @@ int mqtt_tx_pubrel(struct mqtt_ctx *ctx, u16_t id) int mqtt_tx_publish(struct mqtt_ctx *ctx, struct mqtt_publish_msg *msg) { - struct net_buf *data = NULL; - struct net_pkt *tx = NULL; + void *data = NULL; + u16_t len; int rc; - data = net_buf_alloc(&mqtt_msg_pool, ctx->net_timeout); + data = _mqtt_msg_alloc(); if (data == NULL) { return -ENOMEM; } - rc = mqtt_pack_publish(data->data, &data->len, data->size, msg); + rc = mqtt_pack_publish(data, &len, MSG_SIZE, msg); if (rc != 0) { rc = -EINVAL; - goto exit_publish; - } - - tx = net_app_get_net_pkt(&ctx->net_app_ctx, - AF_UNSPEC, ctx->net_timeout); - if (tx == NULL) { - rc = -ENOMEM; - goto exit_publish; - } - - net_pkt_frag_add(tx, data); - data = NULL; - - rc = net_app_send_pkt(&ctx->net_app_ctx, - tx, NULL, 0, ctx->net_timeout, NULL); - if (rc < 0) { - net_pkt_unref(tx); + } else { + rc = async_send(ctx->stream, data, len, NULL, NULL, 0); + SET_ERRNO_AND_RC(rc); } - tx = NULL; - - return rc; - -exit_publish: - net_pkt_frag_unref(data); + _mqtt_msg_free(data); return rc; } int mqtt_tx_pingreq(struct mqtt_ctx *ctx) { - struct net_pkt *tx = NULL; u8_t msg[2]; u16_t len; int rc; @@ -264,30 +220,8 @@ int mqtt_tx_pingreq(struct mqtt_ctx *ctx) return -EINVAL; } - tx = net_app_get_net_pkt(&ctx->net_app_ctx, - AF_UNSPEC, ctx->net_timeout); - if (tx == NULL) { - return -ENOMEM; - } - - rc = net_pkt_append_all(tx, len, msg, ctx->net_timeout); - if (rc != true) { - rc = -ENOMEM; - goto exit_pingreq; - } - - rc = net_app_send_pkt(&ctx->net_app_ctx, - tx, NULL, 0, ctx->net_timeout, NULL); - if (rc < 0) { - goto exit_pingreq; - } - - tx = NULL; - - return rc; - -exit_pingreq: - net_pkt_unref(tx); + rc = async_send(ctx->stream, msg, len, NULL, NULL, 0); + SET_ERRNO_AND_RC(rc); return rc; } @@ -295,44 +229,25 @@ int mqtt_tx_pingreq(struct mqtt_ctx *ctx) int mqtt_tx_subscribe(struct mqtt_ctx *ctx, u16_t pkt_id, u8_t items, const char *topics[], const enum mqtt_qos qos[]) { - struct net_buf *data = NULL; - struct net_pkt *tx = NULL; + void *data = NULL; + u16_t len; int rc; - data = net_buf_alloc(&mqtt_msg_pool, ctx->net_timeout); + data = _mqtt_msg_alloc(); if (data == NULL) { return -ENOMEM; } - rc = mqtt_pack_subscribe(data->data, &data->len, data->size, + rc = mqtt_pack_subscribe(data, &len, MSG_SIZE, pkt_id, items, topics, qos); if (rc != 0) { rc = -EINVAL; - goto exit_subs; - } - - tx = net_app_get_net_pkt(&ctx->net_app_ctx, - AF_UNSPEC, ctx->net_timeout); - if (tx == NULL) { - rc = -ENOMEM; - goto exit_subs; - } - - net_pkt_frag_add(tx, data); - data = NULL; - - rc = net_app_send_pkt(&ctx->net_app_ctx, - tx, NULL, 0, ctx->net_timeout, NULL); - if (rc < 0) { - net_pkt_unref(tx); + } else { + rc = async_send(ctx->stream, data, len, NULL, NULL, 0); + SET_ERRNO_AND_RC(rc); } - tx = NULL; - - return rc; - -exit_subs: - net_pkt_frag_unref(data); + _mqtt_msg_free(data); return rc; } @@ -340,58 +255,38 @@ int mqtt_tx_subscribe(struct mqtt_ctx *ctx, u16_t pkt_id, u8_t items, int mqtt_tx_unsubscribe(struct mqtt_ctx *ctx, u16_t pkt_id, u8_t items, const char *topics[]) { - struct net_buf *data = NULL; - struct net_pkt *tx = NULL; + void *data = NULL; + u16_t len; int rc; - data = net_buf_alloc(&mqtt_msg_pool, ctx->net_timeout); + data = _mqtt_msg_alloc(); if (data == NULL) { return -ENOMEM; } - rc = mqtt_pack_unsubscribe(data->data, &data->len, data->size, pkt_id, + rc = mqtt_pack_unsubscribe(data, &len, MSG_SIZE, pkt_id, items, topics); if (rc != 0) { rc = -EINVAL; - goto exit_unsub; - } - - tx = net_app_get_net_pkt(&ctx->net_app_ctx, - AF_UNSPEC, ctx->net_timeout); - if (tx == NULL) { - rc = -ENOMEM; - goto exit_unsub; - } - - net_pkt_frag_add(tx, data); - data = NULL; - - rc = net_app_send_pkt(&ctx->net_app_ctx, - tx, NULL, 0, ctx->net_timeout, NULL); - if (rc < 0) { - net_pkt_unref(tx); + } else { + rc = async_send(ctx->stream, data, len, NULL, NULL, 0); + SET_ERRNO_AND_RC(rc); } - tx = NULL; - - return rc; - -exit_unsub: - net_pkt_frag_unref(data); + _mqtt_msg_free(data); return rc; } -int mqtt_rx_connack(struct mqtt_ctx *ctx, struct net_buf *rx, int clean_session) +int mqtt_rx_connack(struct mqtt_ctx *ctx, void *rx, size_t len, + int clean_session) { - u16_t len; u8_t connect_rc; u8_t session; u8_t *data; int rc; - data = rx->data; - len = rx->len; + data = (u8_t *)rx; /* CONNACK is 4 bytes len */ rc = mqtt_unpack_connack(data, len, &session, &connect_rc); @@ -448,14 +343,12 @@ int mqtt_rx_connack(struct mqtt_ctx *ctx, struct net_buf *rx, int clean_session) * @retval -EINVAL on error */ static -int mqtt_rx_pub_msgs(struct mqtt_ctx *ctx, struct net_buf *rx, +int mqtt_rx_pub_msgs(struct mqtt_ctx *ctx, void *data, size_t len, enum mqtt_packet type) { int (*unpack)(u8_t *, u16_t, u16_t *) = NULL; int (*response)(struct mqtt_ctx *, u16_t) = NULL; u16_t pkt_id; - u16_t len; - u8_t *data; int rc; switch (type) { @@ -477,9 +370,6 @@ int mqtt_rx_pub_msgs(struct mqtt_ctx *ctx, struct net_buf *rx, return -EINVAL; } - data = rx->data; - len = rx->len; - /* 4 bytes message */ rc = unpack(data, len, &pkt_id); if (rc != 0) { @@ -515,34 +405,34 @@ int mqtt_rx_pub_msgs(struct mqtt_ctx *ctx, struct net_buf *rx, return 0; } -int mqtt_rx_puback(struct mqtt_ctx *ctx, struct net_buf *rx) +int mqtt_rx_puback(struct mqtt_ctx *ctx, void *data, size_t len) { - return mqtt_rx_pub_msgs(ctx, rx, MQTT_PUBACK); + return mqtt_rx_pub_msgs(ctx, data, len, MQTT_PUBACK); } -int mqtt_rx_pubcomp(struct mqtt_ctx *ctx, struct net_buf *rx) +int mqtt_rx_pubcomp(struct mqtt_ctx *ctx, void *data, size_t len) { - return mqtt_rx_pub_msgs(ctx, rx, MQTT_PUBCOMP); + return mqtt_rx_pub_msgs(ctx, data, len, MQTT_PUBCOMP); } -int mqtt_rx_pubrec(struct mqtt_ctx *ctx, struct net_buf *rx) +int mqtt_rx_pubrec(struct mqtt_ctx *ctx, void *data, size_t len) { - return mqtt_rx_pub_msgs(ctx, rx, MQTT_PUBREC); + return mqtt_rx_pub_msgs(ctx, data, len, MQTT_PUBREC); } -int mqtt_rx_pubrel(struct mqtt_ctx *ctx, struct net_buf *rx) +int mqtt_rx_pubrel(struct mqtt_ctx *ctx, void *data, size_t len) { - return mqtt_rx_pub_msgs(ctx, rx, MQTT_PUBREL); + return mqtt_rx_pub_msgs(ctx, data, len, MQTT_PUBREL); } -int mqtt_rx_pingresp(struct mqtt_ctx *ctx, struct net_buf *rx) +int mqtt_rx_pingresp(struct mqtt_ctx *ctx, void *data, size_t len) { int rc; ARG_UNUSED(ctx); /* 2 bytes message */ - rc = mqtt_unpack_pingresp(rx->data, rx->len); + rc = mqtt_unpack_pingresp(data, len); if (rc != 0) { return -EINVAL; @@ -551,18 +441,13 @@ int mqtt_rx_pingresp(struct mqtt_ctx *ctx, struct net_buf *rx) return 0; } -int mqtt_rx_suback(struct mqtt_ctx *ctx, struct net_buf *rx) +int mqtt_rx_suback(struct mqtt_ctx *ctx, void *data, size_t len) { enum mqtt_qos suback_qos[CONFIG_MQTT_SUBSCRIBE_MAX_TOPICS]; u16_t pkt_id; - u16_t len; u8_t items; - u8_t *data; int rc; - data = rx->data; - len = rx->len; - rc = mqtt_unpack_suback(data, len, &pkt_id, &items, CONFIG_MQTT_SUBSCRIBE_MAX_TOPICS, suback_qos); if (rc != 0) { @@ -581,16 +466,11 @@ int mqtt_rx_suback(struct mqtt_ctx *ctx, struct net_buf *rx) return 0; } -int mqtt_rx_unsuback(struct mqtt_ctx *ctx, struct net_buf *rx) +int mqtt_rx_unsuback(struct mqtt_ctx *ctx, void *data, size_t len) { u16_t pkt_id; - u16_t len; - u8_t *data; int rc; - data = rx->data; - len = rx->len; - /* 4 bytes message */ rc = mqtt_unpack_unsuback(data, len, &pkt_id); if (rc != 0) { @@ -609,12 +489,12 @@ int mqtt_rx_unsuback(struct mqtt_ctx *ctx, struct net_buf *rx) return 0; } -int mqtt_rx_publish(struct mqtt_ctx *ctx, struct net_buf *rx) +int mqtt_rx_publish(struct mqtt_ctx *ctx, void *data, size_t len) { struct mqtt_publish_msg msg; int rc; - rc = mqtt_unpack_publish(rx->data, rx->len, &msg); + rc = mqtt_unpack_publish(data, len, &msg); if (rc != 0) { return -EINVAL; } @@ -640,54 +520,6 @@ int mqtt_rx_publish(struct mqtt_ctx *ctx, struct net_buf *rx) return rc; } -/** - * Linearizes an IP fragmented packet - * - * @param [in] ctx MQTT context structure - * @param [in] rx RX IP stack packet - * @param [in] min_size Min message size allowed. This allows us to exit if the - * rx packet is shorter than the expected msg size - * - * @retval Data buffer - * @retval NULL on error - */ -static -struct net_buf *mqtt_linearize_packet(struct mqtt_ctx *ctx, struct net_pkt *rx, - u16_t min_size) -{ - struct net_buf *data = NULL; - u16_t data_len; - u16_t offset; - int rc; - - /* CONFIG_MQTT_MSG_MAX_SIZE is defined via Kconfig. So here it's - * determined if the input packet could fit our data buffer or if - * it has the expected size. - */ - data_len = net_pkt_appdatalen(rx); - if (data_len < min_size || data_len > CONFIG_MQTT_MSG_MAX_SIZE) { - return NULL; - } - - data = net_buf_alloc(&mqtt_msg_pool, ctx->net_timeout); - if (data == NULL) { - return NULL; - } - - offset = net_pkt_get_len(rx) - data_len; - rc = net_frag_linear_copy(data, rx->frags, offset, data_len); - if (rc != 0) { - goto exit_error; - } - - return data; - -exit_error: - net_pkt_frag_unref(data); - - return NULL; -} - /** * Calls the appropriate rx routine for the MQTT message contained in rx * @@ -695,7 +527,8 @@ struct net_buf *mqtt_linearize_packet(struct mqtt_ctx *ctx, struct net_pkt *rx, * (if defined) * * @param ctx MQTT context - * @param rx RX packet + * @param data Received data buffer + * @param data Length of data buffer * * @retval 0 on success * @retval -EINVAL if an unknown message is received @@ -705,47 +538,52 @@ struct net_buf *mqtt_linearize_packet(struct mqtt_ctx *ctx, struct net_pkt *rx, * return codes */ static -int mqtt_parser(struct mqtt_ctx *ctx, struct net_pkt *rx) +int mqtt_parser(struct mqtt_ctx *ctx, void *data, size_t len) { u16_t pkt_type = MQTT_INVALID; - struct net_buf *data = NULL; int rc = -EINVAL; - data = mqtt_linearize_packet(ctx, rx, MQTT_PUBLISHER_MIN_MSG_SIZE); - if (!data) { - return -ENOMEM; + /* + * CONFIG_MQTT_MSG_MAX_SIZE is defined via Kconfig. So here it's + * determined if the received input could fit our data buffer or if + * it has the expected size. + */ + if (len < MQTT_PUBLISHER_MIN_MSG_SIZE || + len > CONFIG_MQTT_MSG_MAX_SIZE) { + return -EMSGSIZE; } - pkt_type = MQTT_PACKET_TYPE(data->data[0]); + pkt_type = MQTT_PACKET_TYPE(((u8_t *)data)[0]); switch (pkt_type) { case MQTT_CONNACK: if (!ctx->connected) { - rc = mqtt_rx_connack(ctx, data, ctx->clean_session); + rc = mqtt_rx_connack(ctx, data, len, + ctx->clean_session); } else { rc = -EINVAL; } break; case MQTT_PUBACK: - rc = mqtt_rx_puback(ctx, data); + rc = mqtt_rx_puback(ctx, data, len); break; case MQTT_PUBREC: - rc = mqtt_rx_pubrec(ctx, data); + rc = mqtt_rx_pubrec(ctx, data, len); break; case MQTT_PUBCOMP: - rc = mqtt_rx_pubcomp(ctx, data); + rc = mqtt_rx_pubcomp(ctx, data, len); break; case MQTT_PINGRESP: - rc = mqtt_rx_pingresp(ctx, data); + rc = mqtt_rx_pingresp(ctx, data, len); break; case MQTT_PUBLISH: - rc = mqtt_rx_publish(ctx, data); + rc = mqtt_rx_publish(ctx, data, len); break; case MQTT_PUBREL: - rc = mqtt_rx_pubrel(ctx, data); + rc = mqtt_rx_pubrel(ctx, data, len); break; case MQTT_SUBACK: - rc = mqtt_rx_suback(ctx, data); + rc = mqtt_rx_suback(ctx, data, len); break; default: rc = -EINVAL; @@ -756,20 +594,18 @@ int mqtt_parser(struct mqtt_ctx *ctx, struct net_pkt *rx) ctx->malformed(ctx, pkt_type); } - net_pkt_frag_unref(data); - return rc; } static -void app_connected(struct net_app_ctx *ctx, int status, void *data) +void async_connected_cb(int sock, int status, void *cb_data) { - struct mqtt_ctx *mqtt = (struct mqtt_ctx *)data; + struct mqtt_ctx *mqtt = (struct mqtt_ctx *)cb_data; - /* net_app_ctx is already referenced to by the mqtt_ctx struct */ - ARG_UNUSED(ctx); + ARG_UNUSED(sock); + ARG_UNUSED(status); - if (!mqtt) { + if (!mqtt || !status) { return; } @@ -779,52 +615,76 @@ void app_connected(struct net_app_ctx *ctx, int status, void *data) } static -void app_recv(struct net_app_ctx *ctx, struct net_pkt *pkt, int status, - void *data) +void async_recv_cb(int sock, void *data, size_t bytes_received, void *cb_data) { - struct mqtt_ctx *mqtt = (struct mqtt_ctx *)data; + struct mqtt_ctx *mqtt = (struct mqtt_ctx *)cb_data; - /* net_app_ctx is already referenced to by the mqtt_ctx struct */ - ARG_UNUSED(ctx); + ARG_UNUSED(sock); - if (status || !pkt) { + if (!data || bytes_received <= 0) { return; } - if (net_pkt_appdatalen(pkt) == 0) { - goto lb_exit; - } - - mqtt->rcv(mqtt, pkt); - -lb_exit: - net_pkt_unref(pkt); + mqtt->rcv(mqtt, data, bytes_received); } int mqtt_connect(struct mqtt_ctx *ctx) { int rc = 0; + struct sockaddr addr; + struct sockaddr peer_addr; + mbedtls_ssl_config *tls_conf; if (!ctx) { return -EFAULT; } - rc = net_app_init_tcp_client(&ctx->net_app_ctx, - NULL, - NULL, - ctx->peer_addr_str, - ctx->peer_port, - ctx->net_init_timeout, - ctx); + /* Duplicate some functionality of net_app_init_tcp_client: + * - Parse peer_addr_str, peer_port + * - Create the socket for this context. + * - TBD: Setup TLS thread + */ + memset(&peer_addr, 0, sizeof(peer_addr)); + memset(&addr, 0, sizeof(addr)); + rc = net_util_init_tcp_client(&addr, &peer_addr, + ctx->peer_addr_str, ctx->peer_port); + if (!rc) { + goto error_connect; + } + + /* Create the socket: */ + ctx->sock = async_socket(addr.sa_family, SOCK_STREAM, IPPROTO_TCP); + if (ctx->sock == INVALID_SOCK) { + rc = -errno; + goto error_connect; + } + + rc = async_connect(ctx->sock, &peer_addr, sizeof(peer_addr), + async_connected_cb, (void *)ctx); if (rc < 0) { goto error_connect; } - rc = net_app_set_cb(&ctx->net_app_ctx, - app_connected, - app_recv, - NULL, - NULL); + zstream_sock_init(&ctx->stream_sock, ctx->sock); + ctx->stream = (struct zstream *)&ctx->stream_sock; + + rc = ztls_get_tls_client_conf(&tls_conf); + if (rc < 0) { + goto error_connect; + } + mbedtls_ssl_conf_authmode(tls_conf, MBEDTLS_SSL_VERIFY_NONE); + rc = zstream_tls_init(&ctx->stream_tls, ctx->stream, tls_conf, NULL); + if (rc < 0) { + goto error_connect; + } + ctx->stream = (struct zstream *)&ctx->stream_tls; + + /* + * Setup receive callback, which will call mqtt_parser() on a received + * socket buffer: + */ + rc = async_recv(ctx->sock, ctx->stream, ctx->rcv_buf, MSG_SIZE, + async_recv_cb, (void *)ctx); if (rc < 0) { goto error_connect; } @@ -846,11 +706,6 @@ int mqtt_connect(struct mqtt_ctx *ctx) } #endif - rc = net_app_connect(&ctx->net_app_ctx, ctx->net_timeout); - if (rc < 0) { - goto error_connect; - } - #if defined(CONFIG_MQTT_LIB_TLS) /* TLS handshake is not finished until app_connected is called */ rc = k_sem_take(&ctx->tls_hs_wait, ctx->tls_hs_timeout); @@ -862,9 +717,7 @@ int mqtt_connect(struct mqtt_ctx *ctx) return rc; error_connect: - /* clean net app context, so mqtt_connect() can be called repeatedly */ - net_app_close(&ctx->net_app_ctx); - net_app_release(&ctx->net_app_ctx); + mqtt_close(ctx); return rc; } @@ -878,6 +731,8 @@ int mqtt_init(struct mqtt_ctx *ctx, enum mqtt_app app_type) ctx->app_type = app_type; ctx->rcv = mqtt_parser; + ctx->sock = INVALID_SOCK; + #if defined(CONFIG_MQTT_LIB_TLS) if (ctx->tls_hs_timeout == 0) { ctx->tls_hs_timeout = TLS_HS_DEFAULT_TIMEOUT; @@ -886,7 +741,9 @@ int mqtt_init(struct mqtt_ctx *ctx, enum mqtt_app app_type) k_sem_init(&ctx->tls_hs_wait, 0, 1); #endif - return 0; + ctx->rcv_buf = _mqtt_msg_alloc(); + + return (ctx->rcv_buf != NULL ? 0 : -ENOMEM); } int mqtt_close(struct mqtt_ctx *ctx) @@ -895,9 +752,13 @@ int mqtt_close(struct mqtt_ctx *ctx) return -EFAULT; } - if (ctx->net_app_ctx.is_init) { - net_app_close(&ctx->net_app_ctx); - net_app_release(&ctx->net_app_ctx); + if (ctx->sock != INVALID_SOCK) { + async_close(ctx->sock, ctx->stream); + ctx->sock = INVALID_SOCK; + } + + if (ctx->rcv_buf) { + _mqtt_msg_free(ctx->rcv_buf); } return 0; diff --git a/subsys/net/lib/mqtt/net_utils.c b/subsys/net/lib/mqtt/net_utils.c new file mode 100644 index 0000000000000..fd51d25dbc802 --- /dev/null +++ b/subsys/net/lib/mqtt/net_utils.c @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2017 Texas Instruments, Inc + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * The following code was lifted from Zephyr's net_app library, to + * support the mqtt port to sockets, and to decouple the mqtt lib from + * net_app's struct net_app_ctx, which may evolve with the new + * net_pkt/net_buf redesign. + * This ideally could move to a generic net/utils module. + */ + +#include +#include + +#include "net_utils.h" + +static int get_port_number(const char *peer_addr_str, + char *buf, + size_t buf_len) +{ + u16_t port = 0; + char *ptr; + int count, i; + + if (peer_addr_str[0] == '[') { +#if defined(CONFIG_NET_IPV6) + /* IPv6 address with port number */ + int end; + + ptr = strstr(peer_addr_str, "]:"); + if (!ptr) { + return -EINVAL; + } + + end = min(INET6_ADDRSTRLEN, ptr - (peer_addr_str + 1)); + memcpy(buf, peer_addr_str + 1, end); + buf[end] = '\0'; + + port = strtol(ptr + 2, NULL, 10); + + return port; +#else + return -EAFNOSUPPORT; +#endif /* CONFIG_NET_IPV6 */ + } + + count = i = 0; + while (peer_addr_str[i]) { + if (peer_addr_str[i] == ':') { + count++; + } + + i++; + } + + if (count == 1) { +#if defined(CONFIG_NET_IPV4) + /* IPv4 address with port number */ + int end; + + ptr = strstr(peer_addr_str, ":"); + if (!ptr) { + return -EINVAL; + } + + end = min(NET_IPV4_ADDR_LEN, ptr - peer_addr_str); + memcpy(buf, peer_addr_str, end); + buf[end] = '\0'; + + port = strtol(ptr + 1, NULL, 10); + + return port; +#else + return -EAFNOSUPPORT; +#endif /* CONFIG_NET_IPV4 */ + } + + return 0; +} + +bool net_util_init_tcp_client(struct sockaddr *addr, + struct sockaddr *peer_addr, + const char *peer_addr_str, + u16_t peer_port) +{ + const char *base_peer_addr = peer_addr_str; + char base_addr_str[INET6_ADDRSTRLEN + 1]; + int addr_ok = false; + u16_t port = 0; + + /* If the peer string contains port number, use that and + * ignore the port number parameter. + */ + port = get_port_number(peer_addr_str, base_addr_str, + sizeof(base_addr_str)); + if (port > 0) { + base_peer_addr = base_addr_str; + peer_port = port; + } else { + strncpy(base_addr_str, peer_addr_str, + sizeof(base_addr_str) - 1); + } + + addr_ok = net_ipaddr_parse(base_peer_addr, + strlen(base_peer_addr), + peer_addr); + + if (addr_ok) { +#if defined(CONFIG_NET_IPV6) + if (peer_addr->sa_family == AF_INET6) { + net_sin6(peer_addr)->sin6_port = htons(peer_port); + } +#endif +#if defined(CONFIG_NET_IPV4) + if (peer_addr->sa_family == AF_INET) { + net_sin(peer_addr)->sin_port = htons(peer_port); + } +#endif + addr->sa_family = peer_addr->sa_family; + } + + return (addr_ok); +} diff --git a/subsys/net/lib/mqtt/net_utils.h b/subsys/net/lib/mqtt/net_utils.h new file mode 100644 index 0000000000000..1cdd10c1b8496 --- /dev/null +++ b/subsys/net/lib/mqtt/net_utils.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2017 Texas Instruments, Inc + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _NET_UTILS_H_ +#define _NET_UTILS_H_ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +bool net_util_init_tcp_client(struct sockaddr *addr, + struct sockaddr *peer_addr, + const char *peer_addr_str, + u16_t peer_port); + +#ifdef __cplusplus +} +#endif + +#endif /* _NET_UTILS_H_ */ diff --git a/subsys/net/lib/tls_conf/CMakeLists.txt b/subsys/net/lib/tls_conf/CMakeLists.txt new file mode 100644 index 0000000000000..562f80aeda254 --- /dev/null +++ b/subsys/net/lib/tls_conf/CMakeLists.txt @@ -0,0 +1,6 @@ +zephyr_library() + +zephyr_library_sources_ifdef(CONFIG_MBEDTLS tls_conf.c) + +zephyr_link_interface_ifdef(CONFIG_MBEDTLS mbedTLS) +zephyr_library_link_libraries_ifdef(CONFIG_MBEDTLS mbedTLS) diff --git a/subsys/net/lib/tls_conf/tls_conf.c b/subsys/net/lib/tls_conf/tls_conf.c new file mode 100644 index 0000000000000..ca208b9bf5b7f --- /dev/null +++ b/subsys/net/lib/tls_conf/tls_conf.c @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2018 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include + +#if !defined(CONFIG_MBEDTLS_CFG_FILE) +#include "mbedtls/config.h" +#else +#include CONFIG_MBEDTLS_CFG_FILE +#endif /* CONFIG_MBEDTLS_CFG_FILE */ +#include "mbedtls/platform.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef __ZEPHYR__ +#include +#include +#include + +#define SYS_LOG_LEVEL 4 +#include + +#else /* __ZEPHYR__ */ + +#define SYS_LOG_ERR(msg, ...) printf(msg "\n", ##__VA_ARGS__) +#define printk printf + +#endif /* __ZEPHYR__ */ + +mbedtls_ssl_config ztls_default_tls_client_conf; +mbedtls_ssl_config ztls_default_tls_server_conf; + +/*static mbedtls_entropy_context ztls_entropy;*/ +static mbedtls_ctr_drbg_context ztls_ctr_drbg; + +#ifdef __ZEPHYR__ + +#ifdef CONFIG_ENTROPY_HAS_DRIVER +static int ztls_entropy_func(void *data, unsigned char *output, size_t len) +{ + int res = entropy_get_entropy(data, output, len); + return res; +} +#else +/* No real entropy driver, use pseudo-random number generator (potentially + * insecure). + */ +static int ztls_entropy_func(void *data, unsigned char *output, size_t len) +{ + size_t i = len / 4; + u32_t v; + + while (i--) { + v = sys_rand32_get(); + UNALIGNED_PUT(v, (u32_t *)output); + output += 4; + } + + i = len & 3; + v = sys_rand32_get(); + while (i--) { + *output++ = v; + v >>= 8; + } + + return 0; +} +#endif + +static int ztls_mbedtls_ctr_drbg_random(void *p_rng, unsigned char *output, + size_t output_len) +{ + static K_MUTEX_DEFINE(mutex); + int res; + + /* Avoid connection lockups due to no entropy, error out instead */ + res = k_mutex_lock(&mutex, K_SECONDS(1)); + if (res < 0) { + return res; + } + + res = mbedtls_ctr_drbg_random(p_rng, output, output_len); + k_mutex_unlock(&mutex); + + return res; +} + +#else /* __ZEPHYR__ */ + +static int null_entropy_func(void *data, unsigned char *output, size_t len) +{ + /* Warning: this is test-only implementation for non-Zephyr + * environments. + */ + (void)data; + (void)output; + (void)len; + return 0; +} + +#define ztls_mbedtls_ctr_drbg_random mbedtls_ctr_drbg_random + +#endif + +#ifdef MBEDTLS_DEBUG_C +static void ztls_mbedtls_debug(void *ctx, int level, const char *file, + int line, const char *str) +{ + (void)ctx; + printk("MBEDTLS%d:%s:%04d: %s\n", level, file, line, str); +} +#endif + +#define ztls_system_is_inited() (ztls_ctr_drbg.f_entropy != NULL) + + +static int ztls_system_init(void) +{ + int ret; + /* TODO: Should use something device-specific, like MAC address */ + static const unsigned char drbg_seed[] = "zephyr"; + struct device *dev = NULL; + +#if defined(__ZEPHYR__) && defined(CONFIG_ENTROPY_HAS_DRIVER) + dev = device_get_binding(CONFIG_ENTROPY_NAME); + + if (!dev) { + SYS_LOG_ERR("can't get entropy device"); + return -ENODEV; + } +#else + printk("*** WARNING: This system lacks entropy driver, " + "TLS communication may be INSECURE! ***\n\n"); +#endif + + /* We don't use mbedTLS entropy pool as of now. */ + /* mbedtls_entropy_init(&p->entropy); */ + + mbedtls_ctr_drbg_init(&ztls_ctr_drbg); +#ifdef __ZEPHYR__ + ret = mbedtls_ctr_drbg_seed(&ztls_ctr_drbg, ztls_entropy_func, dev, + drbg_seed, sizeof(drbg_seed)); +#else + ret = mbedtls_ctr_drbg_seed(&ztls_ctr_drbg, null_entropy_func, NULL, + drbg_seed, sizeof(drbg_seed)); +#endif + if (ret != 0) { + mbedtls_ctr_drbg_free(&ztls_ctr_drbg); + return ret; + } + + return 0; +} + +static int ztls_init_tls_conf(mbedtls_ssl_config *conf, int client_or_serv) +{ + int ret; + + if (!ztls_system_is_inited()) { + ret = ztls_system_init(); + if (ret < 0) { + return ret; + } + } + + mbedtls_ssl_config_init(conf); + + ret = mbedtls_ssl_config_defaults(conf, client_or_serv, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT); + if (ret != 0) { + SYS_LOG_ERR("mbedtls_ssl_config_defaults returned -0x%x", + -ret); + goto cleanup_conf; + } + + mbedtls_ssl_conf_rng(conf, ztls_mbedtls_ctr_drbg_random, + &ztls_ctr_drbg); + + #if 0 + /* These are defaults in mbedTLS */ + mbedtls_ssl_conf_authmode(&ztls_default_tls_client_conf, + MBEDTLS_SSL_VERIFY_REQUIRED); + mbedtls_ssl_conf_authmode(&ztls_default_tls_server_conf, + MBEDTLS_SSL_VERIFY_NONE); + #endif + +#ifdef MBEDTLS_DEBUG_C + mbedtls_ssl_conf_dbg(conf, ztls_mbedtls_debug, NULL); +#ifdef CONFIG_MBEDTLS_DEBUG_LEVEL + /* 3 - info, 4 - debug */ + mbedtls_debug_set_threshold(CONFIG_MBEDTLS_DEBUG_LEVEL); +#endif +#endif + return 0; + +cleanup_conf: + mbedtls_ssl_config_free(conf); + + return ret; + +} + +static int ztls_get_tls_conf_helper(mbedtls_ssl_config **out_conf, + mbedtls_ssl_config *in_conf, + int client_or_serv) +{ + int ret; + + *out_conf = NULL; + ret = ztls_init_tls_conf(in_conf, client_or_serv); + if (ret < 0) { + return ret; + } + + *out_conf = in_conf; + return 0; +} + +int ztls_get_tls_client_conf(mbedtls_ssl_config **out_conf) +{ + return ztls_get_tls_conf_helper(out_conf, + &ztls_default_tls_client_conf, + MBEDTLS_SSL_IS_CLIENT); +} + +int ztls_get_tls_server_conf(mbedtls_ssl_config **out_conf) +{ + return ztls_get_tls_conf_helper(out_conf, + &ztls_default_tls_server_conf, + MBEDTLS_SSL_IS_SERVER); +} + +#ifdef MBEDTLS_X509_CRT_PARSE_C +int ztls_parse_cert_key_pair(struct ztls_cert_key_pair *pair, + const unsigned char *cert, size_t cert_len, + const unsigned char *priv_key, + size_t priv_key_len) +{ + int ret; + + mbedtls_x509_crt_init(&pair->cert); + mbedtls_pk_init(&pair->priv_key); + + ret = mbedtls_x509_crt_parse(&pair->cert, cert, cert_len); + if (ret != 0) { + SYS_LOG_ERR("mbedtls_x509_crt_parse returned -0x%x", -ret); + goto error; + } + + ret = mbedtls_pk_parse_key(&pair->priv_key, priv_key, + priv_key_len, NULL, 0); + if (ret != 0) { + SYS_LOG_ERR("mbedtls_pk_parse_key returned -0x%x", -ret); + goto error; + } + + return 0; + +error: + mbedtls_x509_crt_free(&pair->cert); + mbedtls_pk_free(&pair->priv_key); + + return ret; +} + +int ztls_conf_add_own_cert_key_pair(mbedtls_ssl_config *conf, + struct ztls_cert_key_pair *pair) +{ + int ret = mbedtls_ssl_conf_own_cert(conf, &pair->cert, + &pair->priv_key); + if (ret != 0) { + SYS_LOG_ERR("mbedtls_ssl_conf_own_cert returned -0x%x", -ret); + } + + return ret; +} +#endif /* MBEDTLS_X509_CRT_PARSE_C */ diff --git a/subsys/net/lib/zstream/CMakeLists.txt b/subsys/net/lib/zstream/CMakeLists.txt new file mode 100644 index 0000000000000..b29b58efef08e --- /dev/null +++ b/subsys/net/lib/zstream/CMakeLists.txt @@ -0,0 +1,11 @@ +zephyr_library() + +zephyr_library_sources( + zstream.c + zstream_sock.c + ) + +zephyr_library_sources_ifdef(CONFIG_MBEDTLS zstream_tls.c) + +zephyr_link_interface_ifdef(CONFIG_MBEDTLS mbedTLS) +zephyr_library_link_libraries_ifdef(CONFIG_MBEDTLS mbedTLS) diff --git a/subsys/net/lib/zstream/zstream.c b/subsys/net/lib/zstream/zstream.c new file mode 100644 index 0000000000000..71d4a2b18673c --- /dev/null +++ b/subsys/net/lib/zstream/zstream.c @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include + +#include + +ssize_t zstream_writeall(struct zstream *stream, const void *buf, size_t size, + size_t *written) +{ + const char *p = buf; + size_t cur_sz = 0; + + do { + ssize_t out_sz = zstream_write(stream, p, size); + + if (out_sz == -1) { + if (written) { + *written = cur_sz; + } + + return -1; + } + + cur_sz += out_sz; + size -= out_sz; + p += out_sz; + } while (size); + + if (written) { + *written = cur_sz; + } + + return cur_sz; +} diff --git a/subsys/net/lib/zstream/zstream_sock.c b/subsys/net/lib/zstream/zstream_sock.c new file mode 100644 index 0000000000000..710945a4d446c --- /dev/null +++ b/subsys/net/lib/zstream/zstream_sock.c @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include + +#ifdef __ZEPHYR__ +#include +#else +#include +#include +#endif + +#include + +static ssize_t zstream_sock_read(struct zstream *stream, void *buf, + size_t size) +{ + struct zstream_sock *self = (struct zstream_sock *)stream; + + return recv(self->fd, buf, size, 0); +} + +static ssize_t zstream_sock_write(struct zstream *stream, const void *buf, + size_t size) +{ + struct zstream_sock *self = (struct zstream_sock *)stream; + + return send(self->fd, buf, size, 0); +} + +static int zstream_sock_flush(struct zstream *stream) +{ + return 0; +} + +static int zstream_sock_close(struct zstream *stream) +{ + struct zstream_sock *self = (struct zstream_sock *)stream; + + return close(self->fd); +} + +static const struct zstream_api zstream_sock_api = { + .read = zstream_sock_read, + .write = zstream_sock_write, + .flush = zstream_sock_flush, + .close = zstream_sock_close, +}; + +int zstream_sock_init(struct zstream_sock *self, int fd) +{ + self->api = &zstream_sock_api; + self->fd = fd; + return 0; +} diff --git a/subsys/net/lib/zstream/zstream_tls.c b/subsys/net/lib/zstream/zstream_tls.c new file mode 100644 index 0000000000000..1619dfe9fbf56 --- /dev/null +++ b/subsys/net/lib/zstream/zstream_tls.c @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2018 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include + +#if !defined(CONFIG_MBEDTLS_CFG_FILE) +#include "mbedtls/config.h" +#else +#include CONFIG_MBEDTLS_CFG_FILE +#endif /* CONFIG_MBEDTLS_CFG_FILE */ +#include "mbedtls/platform.h" + +#include +#include +#include +#include +#include +#include + +#ifdef __ZEPHYR__ +#include +#else /* __ZEPHYR__ */ +#include +#include +#define NET_ERR(msg, ...) printf(msg "\n", ##__VA_ARGS__) + +#endif /* __ZEPHYR__ */ + +#include + +static ssize_t zstream_tls_read(struct zstream *stream, void *buf, size_t size) +{ + struct zstream_tls *self = (struct zstream_tls *)stream; + int ret = mbedtls_ssl_read(&self->ssl, buf, size); + + if (ret >= 0) { + return ret; + } + + if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) { + /* End of stream */ + return 0; + } + + if (ret == MBEDTLS_ERR_SSL_CLIENT_RECONNECT) { + /* We don't support TLS reconnects over the same socket, + * treat as EOF. + */ + return 0; + } + + if (ret == MBEDTLS_ERR_SSL_WANT_READ + || ret == MBEDTLS_ERR_SSL_WANT_WRITE) { + errno = EAGAIN; + return -1; + } + + NET_ERR("mbedtls_ssl_read: -%x\n", -ret); + /* TODO: More error code conversion? */ + errno = EINVAL; + return -1; +} + +static ssize_t zstream_tls_write(struct zstream *stream, const void *buf, + size_t size) +{ + struct zstream_tls *self = (struct zstream_tls *)stream; + int ret = mbedtls_ssl_write(&self->ssl, buf, size); + + if (ret >= 0) { + return ret; + } + + if (ret == MBEDTLS_ERR_SSL_WANT_READ + || ret == MBEDTLS_ERR_SSL_WANT_WRITE) { + errno = EAGAIN; + return -1; + } + + /* TODO: More error code conversion? */ + errno = EINVAL; + return -1; +} + +static int zstream_tls_flush(struct zstream *stream) +{ + /* mbedTLS doesn't buffer output data, even for a single + * byte written it will create a TLS record and send at once. + */ + return 0; +} + +int zstream_tls_close(struct zstream *stream) +{ + struct zstream_tls *self = (struct zstream_tls *)stream; + int tls_res = mbedtls_ssl_close_notify(&self->ssl); + /* We need to close underlying stream regardless of TLS close + * notify status, or the stream will be leaked. + */ + int stream_res = zstream_close(self->sock); + + mbedtls_ssl_free(&self->ssl); + + if (tls_res == 0 && stream_res == 0) { + return 0; + } + + if (stream_res == 0) { + /* TODO: Check which mbedTLS errors are possible and improve + * mapping to POSIX codes. + */ + errno = EINVAL; + } + + return -1; +} + +static const struct zstream_api zstream_tls_api = { + .read = zstream_tls_read, + .write = zstream_tls_write, + .flush = zstream_tls_flush, + .close = zstream_tls_close, +}; + +static int zstream_mbedtls_ssl_send(void *ctx, const unsigned char *buf, + size_t len) +{ + struct zstream *sock = ctx; + ssize_t outlen = zstream_write(sock, buf, len); + + if (outlen != -1) { + return outlen; + } + + if (errno == EAGAIN) { + return MBEDTLS_ERR_SSL_WANT_WRITE; + } + + /* Generic error */ + return MBEDTLS_ERR_NET_SEND_FAILED; +} + +static int zstream_mbedtls_ssl_recv(void *ctx, unsigned char *buf, + size_t len) +{ + struct zstream *sock = ctx; + ssize_t outlen = zstream_read(sock, buf, len); + + if (outlen != -1) { + return outlen; + } + + if (errno == EAGAIN) { + return MBEDTLS_ERR_SSL_WANT_READ; + } + + /* Generic error */ + return MBEDTLS_ERR_NET_RECV_FAILED; +} + +int zstream_tls_init(struct zstream_tls *self, struct zstream *sock, + mbedtls_ssl_config *conf, const char *hostname) +{ + int ret; + + self->api = &zstream_tls_api; + self->sock = sock; + + mbedtls_ssl_init(&self->ssl); + + ret = mbedtls_ssl_setup(&self->ssl, conf); + if (ret != 0) { + goto error; + } + + if (hostname) { + /* Set server hostname for SNI */ + ret = mbedtls_ssl_set_hostname(&self->ssl, hostname); + if (ret != 0) { + NET_ERR("mbedtls_ssl_set_hostname: -0x%x", -ret); + goto error; + } + } + + mbedtls_ssl_set_bio(&self->ssl, sock, zstream_mbedtls_ssl_send, + zstream_mbedtls_ssl_recv, NULL); + + while ((ret = mbedtls_ssl_handshake(&self->ssl)) != 0) { + if (ret != MBEDTLS_ERR_SSL_WANT_READ + && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + NET_ERR("mbedtls_ssl_handshake: -%x\n", -ret); + goto error; + } + } + + return 0; + +error: + return -1; +}