diff --git a/configure.ac b/configure.ac index cd34c236..f1008cd3 100644 --- a/configure.ac +++ b/configure.ac @@ -169,6 +169,24 @@ AC_CHECK_LIB([wolfssl],[wolfCrypt_Init],,[AC_MSG_ERROR([libwolfssl is required a fi +# libcurl support +AC_ARG_ENABLE([curl], + [AS_HELP_STRING([--enable-curl],[Enable curl easy socket backend (default: disabled)])], + [ ENABLED_CURL=$enableval ], + [ ENABLED_CURL=no ] + ) + +if test "x$ENABLED_CURL" = "xyes"; then + if test "x$ENABLED_TLS" = "xyes"; then + AC_MSG_ERROR([--enable-tls and --enable-curl are incompatible]) + fi + + AM_CFLAGS="$AM_CFLAGS -DENABLE_MQTT_CURL" + + AC_CHECK_LIB([curl],[curl_easy_init],,[AC_MSG_ERROR([libcurl is required and wasn't found on the system. It can be obtained from https://curl.se/download.html.])]) + +fi + # Non-Blocking support AC_ARG_ENABLE([nonblock], @@ -310,6 +328,7 @@ fi AM_CONDITIONAL([HAVE_LIBWOLFSSL], [test "x$ENABLED_TLS" = "xyes"]) +AM_CONDITIONAL([HAVE_LIBCURL], [test "x$ENABLED_CURL" = "xyes"]) AM_CONDITIONAL([BUILD_EXAMPLES], [test "x$ENABLED_EXAMPLES" = "xyes"]) AM_CONDITIONAL([BUILD_STDINCAP], [test "x$ENABLED_STDINCAP" = "xyes"]) AM_CONDITIONAL([BUILD_SN], [test "x$ENABLED_SN" = "xyes"]) diff --git a/examples/include.am b/examples/include.am index dd0548ea..09060b61 100644 --- a/examples/include.am +++ b/examples/include.am @@ -36,11 +36,20 @@ noinst_HEADERS += examples/mqttclient/mqttclient.h \ if BUILD_SN noinst_HEADERS += examples/sn-client/sn-client.h endif +if HAVE_LIBCURL +noinst_HEADERS += examples/mqttcurl.h +endif # MQTT Client Example +if HAVE_LIBCURL +examples_mqttclient_mqttclient_SOURCES = examples/mqttclient/mqttclient.c \ + examples/mqttcurl.c \ + examples/mqttexample.c +else examples_mqttclient_mqttclient_SOURCES = examples/mqttclient/mqttclient.c \ examples/mqttnet.c \ examples/mqttexample.c +endif examples_mqttclient_mqttclient_LDADD = src/libwolfmqtt.la examples_mqttclient_mqttclient_DEPENDENCIES = src/libwolfmqtt.la examples_mqttclient_mqttclient_CPPFLAGS = -I$(top_srcdir)/examples $(AM_CPPFLAGS) diff --git a/examples/mqttcurl.c b/examples/mqttcurl.c new file mode 100644 index 00000000..aeee4292 --- /dev/null +++ b/examples/mqttcurl.c @@ -0,0 +1,415 @@ +/* mqttcurl.c + * + * Copyright (C) 2006-2023 wolfSSL Inc. + * + * This file is part of wolfMQTT. + * + * wolfMQTT is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfMQTT is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* Include the autoconf generated config.h */ +#ifdef HAVE_CONFIG_H + #include +#endif + +#include "examples/mqttcurl.h" + +#if !defined(ENABLE_MQTT_CURL) + #error "This example requires ENABLE_MQTT_CURL" +#endif + +/* How many times to retry after a timeout. */ +#define MQTT_CURL_NUM_RETRY (2) + +/* Private functions */ + +/* -------------------------------------------------------------------------- */ +/* CURL EASY SOCKET BACKEND EXAMPLE */ +/* -------------------------------------------------------------------------- */ + +static int +wait_on_socket(curl_socket_t sockfd, int for_recv, int timeout_ms) +{ + struct timeval tv; + fd_set infd; + fd_set outfd; + fd_set errfd; + + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (int)(timeout_ms % 1000) * 1000; + + FD_ZERO(&infd); + FD_ZERO(&outfd); + FD_ZERO(&errfd); + + FD_SET(sockfd, &errfd); + + if(for_recv) { + FD_SET(sockfd, &infd); + } + else { + FD_SET(sockfd, &outfd); + } + + return select((int)sockfd + 1, &infd, &outfd, &errfd, &tv); +} + +static int NetConnect(void *context, const char* host, word16 port, + int timeout_ms) +{ + CURLcode res = 0; + CurlContext * ctx = (CurlContext*)context; + int use_tls = 0; + + if (context == NULL || host == NULL || *host == '\0') { + return MQTT_CODE_ERROR_BAD_ARG; + } + + if (ctx->mqttCtx == NULL) { + return MQTT_CODE_ERROR_BAD_ARG; + } + + if (port == MQTT_SECURE_PORT) { use_tls = 1; } + +#if defined(WOLFMQTT_DEBUG_CURL_VERBOSE) + PRINTF("NetConnect: Host %s, Port %u, Timeout %d ms, Use TLS %d", + host, port, timeout_ms, 0); +#endif + + ctx->curl = curl_easy_init(); + + if (ctx->curl == NULL) { + PRINTF("error: curl_easy_init returned NULL"); + return MQTT_CODE_ERROR_MEMORY; + } + + res = curl_easy_setopt(ctx->curl, CURLOPT_VERBOSE, 1L); + if (res != CURLE_OK) { + PRINTF("error: curl_easy_setopt(VERBOSE, 1L) returned: %d, %s", + res, curl_easy_strerror(res)); + return MQTT_CODE_ERROR_CURL; + } + + if (timeout_ms != 0) { + res = curl_easy_setopt(ctx->curl, CURLOPT_CONNECTTIMEOUT_MS, + timeout_ms); + if (res != CURLE_OK) { + PRINTF("error: curl_easy_setopt(CONNECTTIMEOUT_MS, %d) " + "returned %d", timeout_ms, res); + return MQTT_CODE_ERROR_CURL; + } + + res = curl_easy_setopt(ctx->curl, CURLOPT_TIMEOUT_MS, + timeout_ms); + if (res != CURLE_OK) { + PRINTF("error: curl_easy_setopt(TIMEOUT_MS, %d) " + "returned %d", timeout_ms, res); + return MQTT_CODE_ERROR_CURL; + } + } + + res = curl_easy_setopt(ctx->curl, CURLOPT_URL, host); + if (res != CURLE_OK) { + PRINTF("error: curl_easy_setopt(URL, %s) returned: %d", + host, res); + return MQTT_CODE_ERROR_CURL; + } + + res = curl_easy_setopt(ctx->curl, CURLOPT_PORT, port); + if (res != CURLE_OK) { + PRINTF("error: curl_easy_setopt(PORT, %d) returned: %d", + port, res); + return MQTT_CODE_ERROR_CURL; + } + + if (use_tls) { + res = curl_easy_setopt(ctx->curl, CURLOPT_SSLVERSION, + CURL_SSLVERSION_TLSv1_2); + + if (res != CURLE_OK) { + PRINTF("error: curl_easy_setopt(SSLVERSION) returned: %d", + res); + return MQTT_CODE_ERROR_CURL; + } + + /* With CURLOPT_CONNECT_ONLY this means do TLS by default. */ + res = curl_easy_setopt(ctx->curl, CURLOPT_DEFAULT_PROTOCOL, + "https"); + + if (res != CURLE_OK) { + PRINTF("error: curl_easy_setopt(DEFAULT_PROTOCOL) returned: %d", + res); + return MQTT_CODE_ERROR_CURL; + } + + /* Set path to Certificate Authority (CA) file bundle. */ + if (ctx->mqttCtx->ca_file != NULL) { + res = curl_easy_setopt(ctx->curl, CURLOPT_CAINFO, + ctx->mqttCtx->ca_file); + + if (res != CURLE_OK) { + PRINTF("error: curl_easy_setopt(CAINFO) returned: %d", + res); + return MQTT_CODE_ERROR_CURL; + } + } + + /* Set path to dir holding CA files. + * Unused at the moment. */ + /* + if (ctx->mqttCtx->ca_path != NULL) { + res = curl_easy_setopt(ctx->curl, CURLOPT_CAPATH, + ctx->mqttCtx->ca_path); + + if (res != CURLE_OK) { + PRINTF("error: curl_easy_setopt(CAPATH) returned: %d", + res); + return MQTT_CODE_ERROR_CURL; + } + } + */ + + /* Require peer and host verification. */ + res = curl_easy_setopt(ctx->curl, CURLOPT_SSL_VERIFYPEER, 1); + + if (res != CURLE_OK) { + PRINTF("error: curl_easy_setopt(SSL_VERIFYPEER) returned: %d", + res); + return MQTT_CODE_ERROR_CURL; + } + + res = curl_easy_setopt(ctx->curl, CURLOPT_SSL_VERIFYHOST, 2); + + if (res != CURLE_OK) { + PRINTF("error: curl_easy_setopt(SSL_VERIFYHOST) returned: %d", + res); + return MQTT_CODE_ERROR_CURL; + } + } + + res = curl_easy_setopt(ctx->curl, CURLOPT_CONNECT_ONLY, 1); + if (res != CURLE_OK) { + PRINTF("error: curl_easy_setopt(CONNECT_ONLY, 1) returned: %d", + res); + return MQTT_CODE_ERROR_CURL; + } + + res = curl_easy_perform(ctx->curl); + if (res != CURLE_OK) { + PRINTF("error: curl_easy_perform returned: %d, %s", res, + curl_easy_strerror(res)); + return MQTT_CODE_ERROR_CURL; + } + + ctx->stat = SOCK_CONN; + return MQTT_CODE_SUCCESS; +} + +static int NetWrite(void *context, const byte* buf, int buf_len, + int timeout_ms) +{ + CURLcode res = 0; + CurlContext * ctx = (CurlContext*)context; + size_t sent = 0; + curl_socket_t sockfd = 0; + + if (context == NULL || buf == NULL || buf_len == 0) { + return MQTT_CODE_ERROR_BAD_ARG; + } + + /* get the active socket from libcurl */ + res = curl_easy_getinfo(ctx->curl, CURLINFO_ACTIVESOCKET, &sockfd); + if (res != CURLE_OK) { + PRINTF("error: curl_easy_getinfo(CURLINFO_ACTIVESOCKET) returned %d", + res); + return MQTT_CODE_ERROR_CURL; + } + + /* check it makes sense */ + if (sockfd <= 0) { + PRINTF("error: libcurl sockfd: %d", sockfd); + return MQTT_CODE_ERROR_CURL; + } + +#if defined(WOLFMQTT_DEBUG_CURL_VERBOSE) + PRINTF("ctx->curl = %lld, sockfd = %d", (long long) ctx->curl, sockfd); +#endif + + /* A very simple retry with timeout example. This assumes the entire + * payload will be transfered in a single shot without buffering. */ + for (size_t i = 0; i < MQTT_CURL_NUM_RETRY; ++i) { + res = curl_easy_send(ctx->curl, buf, buf_len, &sent); + + if (res == CURLE_OK) { +#if defined(WOLFMQTT_DEBUG_CURL_VERBOSE) + PRINTF("info: curl_easy_send(%d) returned: %d, %s", buf_len, res, + curl_easy_strerror(res)); +#endif + break; + } + + if (res == CURLE_AGAIN) { +#if defined(WOLFMQTT_DEBUG_CURL_VERBOSE) + PRINTF("info: curl_easy_send(%d) returned: %d, %s", buf_len, res, + curl_easy_strerror(res)); +#endif + + if (wait_on_socket(sockfd, 0, timeout_ms) >= 0) { + continue; + } + } + + PRINTF("error: curl_easy_send(%d) returned: %d, %s", buf_len, res, + curl_easy_strerror(res)); + return MQTT_CODE_ERROR_CURL; + } + + if ((int) sent != buf_len) { + PRINTF("error: sent %d bytes, expected %d", (int)sent, buf_len); + return MQTT_CODE_ERROR_CURL; + } + + return buf_len; +} + +static int NetRead(void *context, byte* buf, int buf_len, + int timeout_ms) +{ + CURLcode res = 0; + CurlContext * ctx = (CurlContext*)context; + size_t recvd = 0; + curl_socket_t sockfd = 0; + + if (context == NULL || buf == NULL || buf_len == 0) { + return MQTT_CODE_ERROR_BAD_ARG; + } + + /* get the active socket from libcurl */ + res = curl_easy_getinfo(ctx->curl, CURLINFO_ACTIVESOCKET, &sockfd); + if (res != CURLE_OK) { + PRINTF("error: curl_easy_getinfo(CURLINFO_ACTIVESOCKET) returned %d", + res); + return MQTT_CODE_ERROR_CURL; + } + + /* check it makes sense */ + if (sockfd <= 0) { + PRINTF("error: libcurl sockfd: %d", sockfd); + return MQTT_CODE_ERROR_CURL; + } + +#if defined(WOLFMQTT_DEBUG_CURL_VERBOSE) + PRINTF("ctx->curl = %lld, sockfd = %d", (long long) ctx->curl, sockfd); +#endif + + /* A very simple retry with timeout example. This assumes the entire + * payload will be transfered in a single shot without buffering. */ + for (size_t i = 0; i < MQTT_CURL_NUM_RETRY; ++i) { + res = curl_easy_recv(ctx->curl, buf, buf_len, &recvd); + + if (res == CURLE_OK) { +#if defined(WOLFMQTT_DEBUG_CURL_VERBOSE) + PRINTF("info: curl_easy_recv(%d) returned: %d, %s", buf_len, res, + curl_easy_strerror(res)); +#endif + break; + } + + if (res == CURLE_AGAIN) { +#if defined(WOLFMQTT_DEBUG_CURL_VERBOSE) + PRINTF("info: curl_easy_recv(%d) returned: %d, %s", buf_len, res, + curl_easy_strerror(res)); +#endif + + if (wait_on_socket(sockfd, 1, timeout_ms) >= 0) { + continue; + } + } + + PRINTF("error: curl_easy_recv(%d) returned: %d, %s", buf_len, res, + curl_easy_strerror(res)); + return MQTT_CODE_ERROR_CURL; + } + + if ((int) recvd != buf_len) { + PRINTF("error: recvd %d bytes, expected %d", (int)recvd, buf_len); + return MQTT_CODE_ERROR_CURL; + } + + return buf_len; +} + +static int NetDisconnect(void *context) +{ + CurlContext * ctx = (CurlContext*)context; + + if (ctx == NULL) { + return MQTT_CODE_ERROR_BAD_ARG; + } + + if (ctx->curl != NULL) { +#if defined(WOLFMQTT_DEBUG_CURL_VERBOSE) + PRINTF("info: curl_easy_cleanup"); +#endif + curl_easy_cleanup(ctx->curl); + ctx->curl = NULL; + } + + return 0; +} + +/* Public Functions */ +int MqttClientNet_Init(MqttNet* net, MQTTCtx* mqttCtx) +{ + if (net) { + CurlContext* curlCtx; + + XMEMSET(net, 0, sizeof(MqttNet)); + net->connect = NetConnect; + net->read = NetRead; + net->write = NetWrite; + net->disconnect = NetDisconnect; + + curlCtx = (CurlContext*)WOLFMQTT_MALLOC(sizeof(CurlContext)); + if (curlCtx == NULL) { + return MQTT_CODE_ERROR_MEMORY; + } + net->context = curlCtx; + XMEMSET(curlCtx, 0, sizeof(CurlContext)); + curlCtx->curl = NULL; + curlCtx->fd = SOCKET_INVALID; + curlCtx->stat = SOCK_BEGIN; + curlCtx->mqttCtx = mqttCtx; + } + + return MQTT_CODE_SUCCESS; +} + +int MqttClientNet_DeInit(MqttNet* net) +{ + if (net) { + if (net->context) { + WOLFMQTT_FREE(net->context); + } + XMEMSET(net, 0, sizeof(MqttNet)); + } + return 0; +} + +int MqttClientNet_Wake(MqttNet* net) +{ + (void)net; + return 0; +} diff --git a/examples/mqttcurl.h b/examples/mqttcurl.h new file mode 100644 index 00000000..a97de6ab --- /dev/null +++ b/examples/mqttcurl.h @@ -0,0 +1,64 @@ +/* mqttcurl.h + * + * Copyright (C) 2006-2023 wolfSSL Inc. + * + * This file is part of wolfMQTT. + * + * wolfMQTT is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfMQTT is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifndef WOLFMQTT_EXAMPLE_CURL_H +#define WOLFMQTT_EXAMPLE_CURL_H + +#ifdef __cplusplus + extern "C" { +#endif + +#include "examples/mqttexample.h" +#include "examples/mqttport.h" + +/* Local context for Net callbacks */ +typedef enum { + SOCK_BEGIN = 0, + SOCK_CONN +} NB_Stat; + +/* Structure for Network Security */ +#ifdef ENABLE_MQTT_CURL +typedef struct _CurlContext { + CURL * curl; + SOCKET_T fd; + NB_Stat stat; + int sockRcRead; + int sockRcWrite; + int timeout_ms; + MQTTCtx* mqttCtx; +} CurlContext; +#endif + +/* Functions used to handle the MqttNet structure creation / destruction */ +int MqttClientNet_Init(MqttNet* net, MQTTCtx* mqttCtx); +int MqttClientNet_DeInit(MqttNet* net); +#ifdef WOLFMQTT_SN +int SN_ClientNet_Init(MqttNet* net, MQTTCtx* mqttCtx); +#endif + +int MqttClientNet_Wake(MqttNet* net); + +#ifdef __cplusplus + } /* extern "C" */ +#endif + +#endif /* WOLFMQTT_EXAMPLE_CURL_H */ diff --git a/examples/mqttexample.c b/examples/mqttexample.c index 07d6bf37..3ff69b25 100644 --- a/examples/mqttexample.c +++ b/examples/mqttexample.c @@ -235,6 +235,10 @@ void mqtt_show_usage(MQTTCtx* mqttCtx) #ifdef HAVE_PQC PRINTF("-Q Use Key Share with post-quantum algorithm"); #endif +#elif defined (ENABLE_MQTT_CURL) + PRINTF("-p Port to connect on, default: Normal %d, TLS %d", + MQTT_DEFAULT_PORT, MQTT_SECURE_PORT); + PRINTF("-A Load CA (validate peer)"); #else PRINTF("-p Port to connect on, default: %d", MQTT_DEFAULT_PORT); @@ -286,6 +290,9 @@ void mqtt_init_ctx(MQTTCtx* mqttCtx) #endif #ifdef WOLFMQTT_DEFAULT_TLS mqttCtx->use_tls = WOLFMQTT_DEFAULT_TLS; +#endif +#ifdef ENABLE_MQTT_CURL + mqttCtx->ca_file = NULL; #endif mqttCtx->app_name = "mqttclient"; mqttCtx->message = DEFAULT_MESSAGE; @@ -295,6 +302,11 @@ int mqtt_parse_args(MQTTCtx* mqttCtx, int argc, char** argv) { int rc; + #ifdef ENABLE_MQTT_CURL + #define MQTT_CURL_ARGS "A:" + #else + #define MQTT_CURL_ARGS "" + #endif #ifdef ENABLE_MQTT_TLS #define MQTT_TLS_ARGS "c:A:K:S;Q:" #else @@ -307,7 +319,7 @@ int mqtt_parse_args(MQTTCtx* mqttCtx, int argc, char** argv) #endif while ((rc = mygetopt(argc, argv, "?h:p:q:sk:i:lu:w:m:n:C:Tf:rtd" \ - MQTT_TLS_ARGS MQTT_V5_ARGS)) != -1) { + MQTT_CURL_ARGS MQTT_TLS_ARGS MQTT_V5_ARGS)) != -1) { switch ((char)rc) { case '?' : mqtt_show_usage(mqttCtx); @@ -387,6 +399,11 @@ int mqtt_parse_args(MQTTCtx* mqttCtx, int argc, char** argv) mqttCtx->debug_on = 1; break; + #if defined (ENABLE_MQTT_CURL) + case 'A': + mqttCtx->ca_file = myoptarg; + break; + #endif /* ENABLE_MQTT_CURL */ #ifdef ENABLE_MQTT_TLS case 'A': mTlsCaFile = myoptarg; @@ -412,7 +429,7 @@ int mqtt_parse_args(MQTTCtx* mqttCtx, int argc, char** argv) PRINTF("To use '-Q', build wolfSSL with --with-liboqs"); #endif break; - #endif + #endif /* ENABLE_MQTT_TLS */ #ifdef WOLFMQTT_V5 case 'P': diff --git a/examples/mqttexample.h b/examples/mqttexample.h index 315f7e18..f18ce3e8 100644 --- a/examples/mqttexample.h +++ b/examples/mqttexample.h @@ -148,6 +148,9 @@ typedef struct _MQTTCtx { const char* message; const char* pub_file; const char* client_id; +#if defined (ENABLE_MQTT_CURL) + const char* ca_file; +#endif byte *tx_buf, *rx_buf; int return_code; int use_tls; diff --git a/src/include.am b/src/include.am index 08f9cd16..5647eed8 100644 --- a/src/include.am +++ b/src/include.am @@ -4,9 +4,16 @@ lib_LTLIBRARIES+= src/libwolfmqtt.la + +if HAVE_LIBCURL +src_libwolfmqtt_la_SOURCES = src/mqtt_client.c \ + src/mqtt_packet.c \ + src/mqtt_curl.c +else src_libwolfmqtt_la_SOURCES = src/mqtt_client.c \ src/mqtt_packet.c \ src/mqtt_socket.c +endif if BUILD_SN src_libwolfmqtt_la_SOURCES += src/mqtt_sn_client.c \ diff --git a/src/mqtt_client.c b/src/mqtt_client.c index 4f1aa7fd..57ee9462 100644 --- a/src/mqtt_client.c +++ b/src/mqtt_client.c @@ -2944,6 +2944,10 @@ const char* MqttClient_ReturnCodeToString(int return_code) return "Error (System resource failed)"; case MQTT_CODE_ERROR_NOT_FOUND: return "Error (Not found)"; +#if defined(ENABLE_MQTT_CURL) + case MQTT_CODE_ERROR_CURL: + return "Error (libcurl)"; +#endif #ifdef WOLFMQTT_V5 /* MQTT v5 Reason code strings */ diff --git a/src/mqtt_curl.c b/src/mqtt_curl.c new file mode 100644 index 00000000..b47b1c08 --- /dev/null +++ b/src/mqtt_curl.c @@ -0,0 +1,239 @@ +/* mqtt_curl.c + * + * Copyright (C) 2006-2023 wolfSSL Inc. + * + * This file is part of wolfMQTT. + * + * wolfMQTT is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfMQTT is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* Include the autoconf generated config.h */ +#ifdef HAVE_CONFIG_H + #include +#endif + +#ifdef WOLFMQTT_NONBLOCK + /* need EWOULDBLOCK and EAGAIN */ + #if defined(MICROCHIP_MPLAB_HARMONY) && \ + ((__XC32_VERSION < 4000) || (__XC32_VERSION == 243739000)) + /* xc32 versions >= v4.0 no longer have sys/errno.h */ + #include + #endif + #include +#endif + +#include "wolfmqtt/mqtt_client.h" +#include "wolfmqtt/mqtt_curl.h" + +/* Options */ +#ifdef WOLFMQTT_NO_STDIO + #undef WOLFMQTT_DEBUG_SOCKET +#endif + +/* #define WOLFMQTT_TEST_NONBLOCK */ +#ifdef WOLFMQTT_TEST_NONBLOCK + #define WOLFMQTT_TEST_NONBLOCK_TIMES 1 +#endif + +/* Public Functions */ + +int MqttSocket_Init(MqttClient *client, MqttNet *net) +{ + int rc = MQTT_CODE_ERROR_BAD_ARG; + if (client) { + curl_global_init(CURL_GLOBAL_DEFAULT); + + client->net = net; + MqttClient_Flags(client, (MQTT_CLIENT_FLAG_IS_CONNECTED | + MQTT_CLIENT_FLAG_IS_TLS), 0);; + + /* Validate callbacks are not null! */ + if (net && net->connect && net->read && net->write && net->disconnect) { + rc = MQTT_CODE_SUCCESS; + } + } + return rc; +} + +int MqttSocket_Write(MqttClient *client, const byte* buf, int buf_len, + int timeout_ms) +{ + int rc = 0; + + /* Validate arguments */ + if (client == NULL || client->net == NULL || client->net->write == NULL || + buf == NULL || buf_len <= 0) { + return MQTT_CODE_ERROR_BAD_ARG; + } + + /* check for buffer position overflow */ + if (client->write.pos >= buf_len) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + } + + do { + rc = client->net->write(client->net->context, &buf[client->write.pos], + buf_len - client->write.pos, timeout_ms); + if (rc <= 0) { + break; + } + client->write.pos += rc; + client->write.total += rc; + } while (client->write.pos < buf_len); + + /* handle return code */ + if (rc > 0) { + /* return length write and reset position */ + rc = client->write.pos; + client->write.pos = 0; + } + +#ifdef WOLFMQTT_DEBUG_SOCKET + if (rc != 0 && rc != MQTT_CODE_CONTINUE) { /* hide in non-blocking case */ + PRINTF("MqttSocket_Write: Len=%d, Rc=%d", buf_len, rc); + } +#endif + + return rc; +} + +int MqttSocket_Read(MqttClient *client, byte* buf, int buf_len, int timeout_ms) +{ + int rc = 0; + + /* Validate arguments */ + if (client == NULL || client->net == NULL || client->net->read == NULL || + buf == NULL || buf_len <= 0) { + return MQTT_CODE_ERROR_BAD_ARG; + } + + /* check for buffer position overflow */ + if (client->read.pos >= buf_len) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + } + + do { + rc = client->net->read(client->net->context, &buf[client->read.pos], + buf_len - client->read.pos, timeout_ms); + if (rc <= 0) { + break; + } + client->read.pos += rc; + client->read.total += rc; + } while (client->read.pos < buf_len); + + /* handle return code */ + if (rc > 0) { + /* return length read and reset position */ + rc = client->read.pos; + client->read.pos = 0; + } + +#ifdef WOLFMQTT_DEBUG_SOCKET + if (rc != 0 && rc != MQTT_CODE_CONTINUE) { /* hide in non-blocking case */ + PRINTF("MqttSocket_Read: Len=%d, Rc=%d", buf_len, rc); + } +#endif + + return rc; +} + +#ifdef WOLFMQTT_SN +int MqttSocket_Peek(MqttClient *client, byte* buf, int buf_len, int timeout_ms) +{ + int rc; + + /* Validate arguments */ + if (client == NULL || client->net == NULL || client->net->peek == NULL || + buf == NULL || buf_len <= 0) { + return MQTT_CODE_ERROR_BAD_ARG; + } + + /* check for buffer position overflow */ + if (client->read.pos >= buf_len) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + } + + rc = client->net->peek(client->net->context, buf, buf_len, timeout_ms); + if (rc > 0) { + #ifdef WOLFMQTT_DEBUG_SOCKET + PRINTF("MqttSocket_Peek: Len=%d, Rc=%d", buf_len, rc); + #endif + + /* return length read and reset position */ + client->read.pos = 0; + } + + return rc; +} +#endif /* WOLFMQTT_SN */ + +int MqttSocket_Connect(MqttClient *client, const char* host, word16 port, + int timeout_ms, int use_tls, MqttTlsCb cb) +{ + int rc = MQTT_CODE_SUCCESS; + + /* Validate arguments */ + if (client == NULL || client->net == NULL || + client->net->connect == NULL) { + return MQTT_CODE_ERROR_BAD_ARG; + } + + if ((MqttClient_Flags(client, 0, 0) & MQTT_CLIENT_FLAG_IS_CONNECTED) == 0) { + /* Validate port */ + if (port == 0) { + port = (use_tls) ? MQTT_SECURE_PORT : MQTT_DEFAULT_PORT; + } + + /* Connect to host */ + rc = client->net->connect(client->net->context, host, port, timeout_ms); + if (rc != MQTT_CODE_SUCCESS) { + return rc; + } + MqttClient_Flags(client, 0, MQTT_CLIENT_FLAG_IS_CONNECTED); + } + + (void)cb; + +#ifdef WOLFMQTT_DEBUG_SOCKET + PRINTF("MqttSocket_Connect: Rc=%d", rc); +#endif + + return rc; +} + +int MqttSocket_Disconnect(MqttClient *client) +{ + int rc = MQTT_CODE_SUCCESS; + if (client) { + /* Make sure socket is closed */ + if (client->net && client->net->disconnect) { + rc = client->net->disconnect(client->net->context); + } + MqttClient_Flags(client, MQTT_CLIENT_FLAG_IS_CONNECTED, 0); + + curl_global_cleanup(); + } +#ifdef WOLFMQTT_DEBUG_SOCKET + PRINTF("MqttSocket_Disconnect: Rc=%d", rc); +#endif + + /* Check for error */ + if (rc < 0) { + rc = MQTT_CODE_ERROR_NETWORK; + } + + return rc; +} diff --git a/wolfmqtt/include.am b/wolfmqtt/include.am index 77dfcc9d..d48ec4a3 100644 --- a/wolfmqtt/include.am +++ b/wolfmqtt/include.am @@ -11,6 +11,9 @@ nobase_include_HEADERS+= \ wolfmqtt/visibility.h \ wolfmqtt/options.h \ wolfmqtt/vs_settings.h +if HAVE_LIBCURL +nobase_include_HEADERS+= wolfmqtt/mqtt_curl.h +endif if BUILD_SN nobase_include_HEADERS+= wolfmqtt/mqtt_sn_client.h \ diff --git a/wolfmqtt/mqtt_client.h b/wolfmqtt/mqtt_client.h index ddb70afe..c65f3adb 100644 --- a/wolfmqtt/mqtt_client.h +++ b/wolfmqtt/mqtt_client.h @@ -40,7 +40,11 @@ #endif #include "wolfmqtt/mqtt_types.h" #include "wolfmqtt/mqtt_packet.h" -#include "wolfmqtt/mqtt_socket.h" +#ifdef ENABLE_MQTT_CURL + #include "wolfmqtt/mqtt_curl.h" +#else + #include "wolfmqtt/mqtt_socket.h" +#endif #ifdef WOLFMQTT_SN #include "wolfmqtt/mqtt_sn_packet.h" diff --git a/wolfmqtt/mqtt_curl.h b/wolfmqtt/mqtt_curl.h new file mode 100644 index 00000000..d51f5d3d --- /dev/null +++ b/wolfmqtt/mqtt_curl.h @@ -0,0 +1,87 @@ +/* mqtt_curl.h + * + * Copyright (C) 2006-2023 wolfSSL Inc. + * + * This file is part of wolfMQTT. + * + * wolfMQTT is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfMQTT is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifndef WOLFMQTT_CURL_H +#define WOLFMQTT_CURL_H + +#include + +#ifdef __cplusplus + extern "C" { +#endif + +#include "wolfmqtt/mqtt_types.h" + +/* Default Port Numbers */ +#define MQTT_DEFAULT_PORT 1883 +#define MQTT_SECURE_PORT 8883 + + +struct _MqttClient; + +/* Function callbacks */ +typedef int (*MqttTlsCb)(struct _MqttClient* client); + +typedef int (*MqttNetConnectCb)(void *context, + const char* host, word16 port, int timeout_ms); +typedef int (*MqttNetWriteCb)(void *context, + const byte* buf, int buf_len, int timeout_ms); +typedef int (*MqttNetReadCb)(void *context, + byte* buf, int buf_len, int timeout_ms); +#ifdef WOLFMQTT_SN +typedef int (*MqttNetPeekCb)(void *context, + byte* buf, int buf_len, int timeout_ms); +#endif +typedef int (*MqttNetDisconnectCb)(void *context); + +/* Structure for Network callbacks */ +typedef struct _MqttNet { + void *context; + MqttNetConnectCb connect; + MqttNetReadCb read; + MqttNetWriteCb write; + MqttNetDisconnectCb disconnect; +#ifdef WOLFMQTT_SN + MqttNetPeekCb peek; + void *multi_ctx; +#endif +} MqttNet; + +/* MQTT SOCKET APPLICATION INTERFACE */ +WOLFMQTT_LOCAL int MqttSocket_Init(struct _MqttClient *client, MqttNet* net); +WOLFMQTT_LOCAL int MqttSocket_Write(struct _MqttClient *client, const byte* buf, + int buf_len, int timeout_ms); +WOLFMQTT_LOCAL int MqttSocket_Read(struct _MqttClient *client, byte* buf, + int buf_len, int timeout_ms); +#ifdef WOLFMQTT_SN +WOLFMQTT_LOCAL int MqttSocket_Peek(struct _MqttClient *client, byte* buf, + int buf_len, int timeout_ms); +#endif /* WOLFMQTT_SN */ +WOLFMQTT_LOCAL int MqttSocket_Connect(struct _MqttClient *client, + const char* host, word16 port, int timeout_ms, int use_tls, + MqttTlsCb cb); +WOLFMQTT_LOCAL int MqttSocket_Disconnect(struct _MqttClient *client); + +#ifdef __cplusplus + } /* extern "C" */ +#endif + +#endif /* WOLFMQTT_CURL_H */ diff --git a/wolfmqtt/mqtt_packet.h b/wolfmqtt/mqtt_packet.h index efa2d46b..239c374a 100644 --- a/wolfmqtt/mqtt_packet.h +++ b/wolfmqtt/mqtt_packet.h @@ -32,7 +32,11 @@ #endif #include "wolfmqtt/mqtt_types.h" -#include "wolfmqtt/mqtt_socket.h" +#ifdef ENABLE_MQTT_CURL + #include "wolfmqtt/mqtt_curl.h" +#else + #include "wolfmqtt/mqtt_socket.h" +#endif /* Size of a data length elements in protocol */ diff --git a/wolfmqtt/mqtt_types.h b/wolfmqtt/mqtt_types.h index cc47234d..ae32e70d 100644 --- a/wolfmqtt/mqtt_types.h +++ b/wolfmqtt/mqtt_types.h @@ -194,6 +194,10 @@ enum MqttPacketResponseCodes { MQTT_CODE_ERROR_CALLBACK = -13, MQTT_CODE_ERROR_SYSTEM = -14, MQTT_CODE_ERROR_NOT_FOUND = -15, +#if defined(ENABLE_MQTT_CURL) + MQTT_CODE_ERROR_CURL = -16, /* An error in libcurl that is not clearly + * a network, memory, TLS, or system error. */ +#endif MQTT_CODE_CONTINUE = -101, MQTT_CODE_STDIN_WAKE = -102,