diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml new file mode 100644 index 000000000..5bd1d200f --- /dev/null +++ b/.github/workflows/build_linux.yml @@ -0,0 +1,41 @@ +name: "Builds for Linux" +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-18.04 + steps: + - name: Check out code + uses: actions/checkout@v2 + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install doxygen + - name: Build + run: | + rm -rf build.paho + mkdir build.paho + cd build.paho + echo "pwd $PWD" + cmake -DPAHO_BUILD_STATIC=FALSE -DPAHO_BUILD_SHARED=TRUE -DCMAKE_BUILD_TYPE=Debug -DPAHO_WITH_SSL=TRUE -DOPENSSL_ROOT_DIR= -DPAHO_BUILD_DOCUMENTATION=FALSE -DPAHO_BUILD_SAMPLES=TRUE -DPAHO_HIGH_PERFORMANCE=TRUE .. + cmake --build . + - name: Start test broker + run: | + git clone https://github.com/eclipse/paho.mqtt.testing.git + cd paho.mqtt.testing/interoperability + python3 startbroker.py -c localhost_testing.conf & + - name: Start test proxy + run: | + python3 test/mqttsas.py & + - name: run tests + run: | + cd build.paho + ctest -VV --timeout 600 + - name: clean up + run: | + killall python3 || true + sleep 3 # allow broker time to terminate and report + - name: package + run: | + cd build.paho + cpack --verbose diff --git a/.github/workflows/build_macos.yml b/.github/workflows/build_macos.yml new file mode 100644 index 000000000..6d69b479c --- /dev/null +++ b/.github/workflows/build_macos.yml @@ -0,0 +1,41 @@ +name: "Builds for MacOS" +on: [push, pull_request] + +jobs: + build: + runs-on: macos-latest + steps: + - name: Check out code + uses: actions/checkout@v2 + - name: Install dependencies + run: | + brew update + brew install doxygen + - name: Build + run: | + rm -rf build.paho + mkdir build.paho + cd build.paho + echo "pwd $PWD" + cmake -DPAHO_BUILD_STATIC=FALSE -DPAHO_BUILD_SHARED=TRUE -DCMAKE_BUILD_TYPE=Debug -DPAHO_WITH_SSL=TRUE -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl@1.1 -DPAHO_BUILD_DOCUMENTATION=FALSE -DPAHO_BUILD_SAMPLES=TRUE -DPAHO_HIGH_PERFORMANCE=TRUE .. + cmake --build . + - name: Start test broker + run: | + git clone https://github.com/eclipse/paho.mqtt.testing.git + cd paho.mqtt.testing/interoperability + python3 startbroker.py -c localhost_testing.conf & + - name: Start test proxy + run: | + python3 test/mqttsas.py & + - name: run tests + run: | + cd build.paho + ctest -VV --timeout 600 + - name: clean up + run: | + killall python3 || true + sleep 3 # allow broker time to terminate and report + - name: package + run: | + cd build.paho + cpack --verbose diff --git a/.github/workflows/covsync.yml b/.github/workflows/covsync.yml index 02578c57a..72e411381 100644 --- a/.github/workflows/covsync.yml +++ b/.github/workflows/covsync.yml @@ -14,4 +14,5 @@ jobs: fetch-depth: 0 - run: | git checkout -b coverity-develop origin/develop + git pull origin coverity-develop git push origin coverity-develop diff --git a/.travis.yml b/.travis.yml index befd03c5e..39ee01379 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ jobs: env: OPENSSL_ROOT_DIR= PAHO_BUILD_STATIC=TRUE PAHO_BUILD_SHARED=TRUE PAHO_HIGH_PERFORMANCE=TRUE - os: osx compiler: clang - env: OPENSSL_ROOT_DIR=/usr/local/opt/openssl PAHO_BUILD_STATIC=FALSE PAHO_BUILD_SHARED=TRUE PAHO_HIGH_PERFORMANC=FALSE + env: OPENSSL_ROOT_DIR=/usr/local/opt/openssl PAHO_BUILD_STATIC=FALSE PAHO_BUILD_SHARED=TRUE PAHO_HIGH_PERFORMANCE=FALSE - os: linux compiler: gcc dist: trusty diff --git a/CMakeLists.txt b/CMakeLists.txt index 7bfee10e4..cf0fd68bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,12 +36,8 @@ INCLUDE(GNUInstallDirs) STRING(TIMESTAMP BUILD_TIMESTAMP UTC) MESSAGE(STATUS "Timestamp is ${BUILD_TIMESTAMP}") -IF (PAHO_HIGH_PERFORMANCE) - ADD_DEFINITIONS(-DHIGH_PERFORMANCE=1) -ENDIF() - IF(WIN32) - ADD_DEFINITIONS(-D_CRT_SECURE_NO_DEPRECATE -DWIN32_LEAN_AND_MEAN -MD) + ADD_DEFINITIONS(-D_CRT_SECURE_NO_DEPRECATE -DWIN32_LEAN_AND_MEAN) ELSEIF(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") ADD_DEFINITIONS(-DOSX) ENDIF() @@ -55,6 +51,16 @@ SET(PAHO_BUILD_SAMPLES FALSE CACHE BOOL "Build sample programs") SET(PAHO_BUILD_DEB_PACKAGE FALSE CACHE BOOL "Build debian package") SET(PAHO_ENABLE_TESTING TRUE CACHE BOOL "Build tests and run") SET(PAHO_ENABLE_CPACK TRUE CACHE BOOL "Enable CPack") +SET(PAHO_HIGH_PERFORMANCE FALSE CACHE BOOL "Disable tracing and heap tracking") +SET(PAHO_USE_SELECT FALSE CACHE BOOL "Revert to select system call instead of poll") + +IF (PAHO_HIGH_PERFORMANCE) + ADD_DEFINITIONS(-DHIGH_PERFORMANCE=1) +ENDIF() + +IF (PAHO_USE_SELECT) + ADD_DEFINITIONS(-DUSE_SELECT=1) +ENDIF() IF (NOT PAHO_BUILD_SHARED AND NOT PAHO_BUILD_STATIC) MESSAGE(FATAL_ERROR "You must set either PAHO_BUILD_SHARED, PAHO_BUILD_STATIC, or both") diff --git a/Makefile b/Makefile index e1a7eab6a..2c0fc2b72 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ #******************************************************************************* -# Copyright (c) 2009, 2020 IBM Corp. +# Copyright (c) 2009, 2021 IBM Corp. # # All rights reserved. This program and the accompanying materials # are made available under the terms of the Eclipse Public License v2.0 @@ -21,7 +21,7 @@ # Note: on OS X you should install XCode and the associated command-line tools SHELL = /bin/sh -.PHONY: clean, mkdir, install, uninstall, html +.PHONY: clean mkdir install install-strip uninstall html strip-options MAJOR_VERSION := $(shell cat version.major) MINOR_VERSION := $(shell cat version.minor) @@ -155,18 +155,18 @@ PAHO_CS_PUB_TARGET = ${blddir}/samples/${PAHO_CS_PUB_NAME} PAHO_CS_SUB_TARGET = ${blddir}/samples/${PAHO_CS_SUB_NAME} #CCFLAGS_SO = -g -fPIC $(CFLAGS) -Os -Wall -fvisibility=hidden -I$(blddir_work) -#FLAGS_EXE = $(LDFLAGS) -I ${srcdir} -lpthread -L ${blddir} -#FLAGS_EXES = $(LDFLAGS) -I ${srcdir} ${START_GROUP} -lpthread -lssl -lcrypto ${END_GROUP} -L ${blddir} +#FLAGS_EXE = $(LDFLAGS) -I ${srcdir} -pthread -L ${blddir} +#FLAGS_EXES = $(LDFLAGS) -I ${srcdir} ${START_GROUP} -pthread -lssl -lcrypto ${END_GROUP} -L ${blddir} CCFLAGS_SO = -g -fPIC $(CFLAGS) -D_GNU_SOURCE -Os -Wall -fvisibility=hidden -I$(blddir_work) -DPAHO_MQTT_EXPORTS=1 -FLAGS_EXE = $(LDFLAGS) -I ${srcdir} ${START_GROUP} -lpthread ${GAI_LIB} ${END_GROUP} -L ${blddir} -FLAGS_EXES = $(LDFLAGS) -I ${srcdir} ${START_GROUP} -lpthread ${GAI_LIB} -lssl -lcrypto ${END_GROUP} -L ${blddir} +FLAGS_EXE = $(LDFLAGS) -I ${srcdir} ${START_GROUP} -pthread ${GAI_LIB} ${END_GROUP} -L ${blddir} +FLAGS_EXES = $(LDFLAGS) -I ${srcdir} ${START_GROUP} -pthread ${GAI_LIB} -lssl -lcrypto ${END_GROUP} -L ${blddir} LDCONFIG ?= /sbin/ldconfig -LDFLAGS_C = $(LDFLAGS) -shared -Wl,-init,$(MQTTCLIENT_INIT) $(START_GROUP) -lpthread $(GAI_LIB) $(END_GROUP) -LDFLAGS_CS = $(LDFLAGS) -shared $(START_GROUP) -lpthread $(GAI_LIB) $(EXTRA_LIB) -lssl -lcrypto $(END_GROUP) -Wl,-init,$(MQTTCLIENT_INIT) -LDFLAGS_A = $(LDFLAGS) -shared -Wl,-init,$(MQTTASYNC_INIT) $(START_GROUP) -lpthread $(GAI_LIB) $(END_GROUP) -LDFLAGS_AS = $(LDFLAGS) -shared $(START_GROUP) -lpthread $(GAI_LIB) $(EXTRA_LIB) -lssl -lcrypto $(END_GROUP) -Wl,-init,$(MQTTASYNC_INIT) +LDFLAGS_C = $(LDFLAGS) -shared -Wl,-init,$(MQTTCLIENT_INIT) $(START_GROUP) -pthread $(GAI_LIB) $(END_GROUP) +LDFLAGS_CS = $(LDFLAGS) -shared $(START_GROUP) -pthread $(GAI_LIB) $(EXTRA_LIB) -lssl -lcrypto $(END_GROUP) -Wl,-init,$(MQTTCLIENT_INIT) +LDFLAGS_A = $(LDFLAGS) -shared -Wl,-init,$(MQTTASYNC_INIT) $(START_GROUP) -pthread $(GAI_LIB) $(END_GROUP) +LDFLAGS_AS = $(LDFLAGS) -shared $(START_GROUP) -pthread $(GAI_LIB) $(EXTRA_LIB) -lssl -lcrypto $(END_GROUP) -Wl,-init,$(MQTTASYNC_INIT) SED_COMMAND = sed \ -e "s/@CLIENT_VERSION@/${release.version}/g" \ diff --git a/README.md b/README.md index b00780f81..7031ab627 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,7 @@ PAHO_WITH_SSL | FALSE | Flag that defines whether to build ssl-enabled binaries OPENSSL_ROOT_DIR | "" (system default) | Directory containing your OpenSSL installation (i.e. `/usr/local` when headers are in `/usr/local/include` and libraries are in `/usr/local/lib`) PAHO_BUILD_DOCUMENTATION | FALSE | Create and install the HTML based API documentation (requires Doxygen) PAHO_BUILD_SAMPLES | FALSE | Build sample programs +PAHO_ENABLE_TESTING | TRUE | Build test and run MQTT_TEST_BROKER | tcp://localhost:1883 | MQTT connection URL for a broker to use during test execution MQTT_TEST_PROXY | tcp://localhost:1883 | Hostname of the test proxy to use MQTT_SSL_HOSTNAME | localhost | Hostname of a test SSL MQTT broker to use diff --git a/appveyor.yml b/appveyor.yml index 6ab1abb46..36d1425aa 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -52,7 +52,7 @@ build_script: nmake - ctest -T test -VV + ctest -T test -VV cd .. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0bc719482..4c064090c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -47,6 +47,7 @@ SET(common_src Base64.c SHA1.c WebSocket.c + Proxy.c ) IF (NOT PAHO_HIGH_PERFORMANCE) diff --git a/src/Clients.c b/src/Clients.c index 585257d2c..9e70e48b8 100644 --- a/src/Clients.c +++ b/src/Clients.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2013 IBM Corp. + * Copyright (c) 2009, 2022 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -51,5 +51,5 @@ int clientSocketCompare(void* a, void* b) { Clients* client = (Clients*)a; /*printf("comparing %d with %d\n", (char*)a, (char*)b); */ - return client->net.socket == *(int*)b; + return client->net.socket == *(SOCKET*)b; } diff --git a/src/Clients.h b/src/Clients.h index b0cacf082..130a9622b 100644 --- a/src/Clients.h +++ b/src/Clients.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2021 IBM Corp. and Ian Craggs + * Copyright (c) 2009, 2022 IBM Corp. and Ian Craggs * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -24,15 +24,16 @@ #include #include "MQTTTime.h" -#if defined(OPENSSL) #if defined(_WIN32) || defined(_WIN64) #include #endif +#if defined(OPENSSL) #include #endif #include "MQTTClient.h" #include "LinkedList.h" #include "MQTTClientPersistence.h" +#include "Socket.h" /** * Stored publication data to minimize copying @@ -77,7 +78,7 @@ typedef struct typedef struct { - int socket; + SOCKET socket; START_TIME_TYPE lastSent; START_TIME_TYPE lastReceived; START_TIME_TYPE lastPing; @@ -131,12 +132,15 @@ typedef struct networkHandles net; /**< network info for this client */ int msgID; /**< the MQTT message id */ int keepAliveInterval; /**< the MQTT keep alive interval */ - int retryInterval; + int retryInterval; /**< the MQTT retry interval for QoS > 0 */ int maxInflightMessages; /**< the max number of inflight outbound messages we allow */ willMessages* will; /**< the MQTT will message, if any */ List* inboundMsgs; /**< inbound in flight messages */ List* outboundMsgs; /**< outbound in flight messages */ + int connect_count; /**< the number of outbound messages on reconnect - to ensure we send them all */ + int connect_sent; /**< the current number of outbound messages on reconnect that we've sent */ List* messageQueue; /**< inbound complete but undelivered messages */ + List* outboundQueue; /**< outbound queued messages */ unsigned int qentry_seqno; void* phandle; /**< the persistence handle */ MQTTClient_persistence* persistence; /**< a persistence implementation */ @@ -147,8 +151,8 @@ typedef struct void* context; /**< calling context - used when calling disconnect_internal */ int MQTTVersion; /**< the version of MQTT being used, 3, 4 or 5 */ int sessionExpiry; /**< MQTT 5 session expiry */ - char* httpProxy; /**< HTTP proxy for websockets */ - char* httpsProxy; /**< HTTPS proxy for websockets */ + char* httpProxy; /**< HTTP proxy */ + char* httpsProxy; /**< HTTPS proxy */ #if defined(OPENSSL) MQTTClient_SSLOptions *sslopts; /**< the SSL/TLS connect options */ SSL_SESSION* session; /**< SSL session pointer for fast handhake */ diff --git a/src/Keysight_ws_add b/src/Keysight_ws_add new file mode 100644 index 000000000..f6103546a --- /dev/null +++ b/src/Keysight_ws_add @@ -0,0 +1,22 @@ +#if WINVER <= _WIN32_WINNT_WIN8 +#define HTON(x) hton((uint64_t) (x), sizeof(x)) +uint64_t hton(uint64_t x, size_t n) +{ + uint64_t y = 0; + size_t i = 0; + + for (i=0; i < n; ++i) + { + y = (y << 8) | (x & 0xff); + x = (x >> 8); + } + return y; +} +#define htons(x) (uint16_t) HTON(x) +#define htonl(x) (uint32_t) HTON(x) +#define htonll(x) (uint64_t) HTON(x) + +#define ntohs(x) htons(x) +#define ntohl(x) htonl(x) +#define ntohll(x) htonll(x) +#endif diff --git a/src/Log.c b/src/Log.c index 6ef09248d..92054db08 100644 --- a/src/Log.c +++ b/src/Log.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2020 IBM Corp. + * Copyright (c) 2009, 2022 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -86,7 +86,7 @@ typedef struct #endif int sametime_count; int number; - int thread_id; + thread_id_type thread_id; int depth; char name[MAX_FUNCTION_NAME_LENGTH + 1]; int line; @@ -453,7 +453,7 @@ void Log(enum LOG_LEVELS log_level, int msgno, const char *format, ...) * @param aFormat the printf format string to be used if the message id does not exist * @param ... the printf inserts */ -void Log_stackTrace(enum LOG_LEVELS log_level, int msgno, int thread_id, int current_depth, const char* name, int line, int* rc) +void Log_stackTrace(enum LOG_LEVELS log_level, int msgno, thread_id_type thread_id, int current_depth, const char* name, int line, int* rc) { traceEntry *cur_entry = NULL; diff --git a/src/Log.h b/src/Log.h index 102e31dfd..ae9bb9ef4 100644 --- a/src/Log.h +++ b/src/Log.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2013 IBM Corp. + * Copyright (c) 2009, 2022 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -18,6 +18,14 @@ #if !defined(LOG_H) #define LOG_H +#if defined(_WIN32) || defined(_WIN64) + #include + #define thread_id_type DWORD +#else + #include + #define thread_id_type pthread_t +#endif + /*BE map LOG_LEVELS { @@ -76,7 +84,7 @@ int Log_initialize(Log_nameValue*); void Log_terminate(void); void Log(enum LOG_LEVELS, int, const char *, ...); -void Log_stackTrace(enum LOG_LEVELS, int, int, int, const char*, int, int*); +void Log_stackTrace(enum LOG_LEVELS, int, thread_id_type, int, const char*, int, int*); typedef void Log_traceCallback(enum LOG_LEVELS level, const char *message); void Log_setTraceCallback(Log_traceCallback* callback); diff --git a/src/MQTTAsync.c b/src/MQTTAsync.c index 6f6caa6d9..b49f15805 100644 --- a/src/MQTTAsync.c +++ b/src/MQTTAsync.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2021 IBM Corp., Ian Craggs and others + * Copyright (c) 2009, 2022 IBM Corp., Ian Craggs and others * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -340,6 +340,12 @@ int MQTTAsync_createWithOptions(MQTTAsync* handle, const char* serverURI, const } } + if (options && options->maxBufferedMessages <= 0) + { + rc = MQTTASYNC_MAX_BUFFERED; + goto exit; + } + if (options && (strncmp(options->struct_id, "MQCO", 4) != 0 || options->struct_version < 0 || options->struct_version > 2)) { @@ -356,6 +362,7 @@ int MQTTAsync_createWithOptions(MQTTAsync* handle, const char* serverURI, const bstate->clients = ListInitialize(); Socket_outInitialize(); Socket_setWriteCompleteCallback(MQTTAsync_writeComplete); + Socket_setWriteAvailableCallback(MQTTProtocol_writeAvailable); MQTTAsync_handles = ListInitialize(); MQTTAsync_commands = ListInitialize(); #if defined(OPENSSL) @@ -408,9 +415,10 @@ int MQTTAsync_createWithOptions(MQTTAsync* handle, const char* serverURI, const m->c->outboundMsgs = ListInitialize(); m->c->inboundMsgs = ListInitialize(); m->c->messageQueue = ListInitialize(); + m->c->outboundQueue = ListInitialize(); m->c->clientID = MQTTStrdup(clientId); if (m->c->context == NULL || m->c->outboundMsgs == NULL || m->c->inboundMsgs == NULL || - m->c->messageQueue == NULL || m->c->clientID == NULL) + m->c->messageQueue == NULL || m->c->outboundQueue == NULL || m->c->clientID == NULL) { rc = PAHO_MEMORY_ERROR; goto exit; @@ -484,7 +492,7 @@ void MQTTAsync_destroy(MQTTAsync* handle) if (m->c) { - int saved_socket = m->c->net.socket; + SOCKET saved_socket = m->c->net.socket; char* saved_clientid = MQTTStrdup(m->c->clientID); #if !defined(NO_PERSISTENCE) MQTTPersistence_close(m->c); @@ -627,8 +635,6 @@ int MQTTAsync_connect(MQTTAsync handle, const MQTTAsync_connectOptions* options) m->connect.context = options->context; m->connectTimeout = options->connectTimeout; - MQTTAsync_tostop = 0; - /* don't lock async mutex if we are being called from a callback */ thread_id = Thread_getid(); if (thread_id != sendThread_id && thread_id != receiveThread_id) @@ -636,6 +642,7 @@ int MQTTAsync_connect(MQTTAsync handle, const MQTTAsync_connectOptions* options) MQTTAsync_lock_mutex(mqttasync_mutex); locked = 1; } + MQTTAsync_tostop = 0; if (sendThread_state != STARTING && sendThread_state != RUNNING) { sendThread_state = STARTING; @@ -1425,6 +1432,7 @@ int MQTTAsync_getPendingTokens(MQTTAsync handle, MQTTAsync_token **tokens) FUNC_ENTRY; MQTTAsync_lock_mutex(mqttasync_mutex); + MQTTAsync_lock_mutex(mqttcommand_mutex); *tokens = NULL; if (m == NULL) @@ -1438,7 +1446,7 @@ int MQTTAsync_getPendingTokens(MQTTAsync handle, MQTTAsync_token **tokens) { MQTTAsync_queuedCommand* cmd = (MQTTAsync_queuedCommand*)(current->content); - if (cmd->client == m) + if (cmd->client == m && cmd->command.type == PUBLISH) count++; } if (m->c) @@ -1459,7 +1467,7 @@ int MQTTAsync_getPendingTokens(MQTTAsync handle, MQTTAsync_token **tokens) { MQTTAsync_queuedCommand* cmd = (MQTTAsync_queuedCommand*)(current->content); - if (cmd->client == m) + if (cmd->client == m && cmd->command.type == PUBLISH) (*tokens)[count++] = cmd->command.token; } @@ -1476,6 +1484,7 @@ int MQTTAsync_getPendingTokens(MQTTAsync handle, MQTTAsync_token **tokens) (*tokens)[count] = -1; /* indicate end of list */ exit: + MQTTAsync_unlock_mutex(mqttcommand_mutex); MQTTAsync_unlock_mutex(mqttasync_mutex); FUNC_EXIT_RC(rc); return rc; @@ -1777,6 +1786,8 @@ const char* MQTTAsync_strerror(int code) return "Zero length will topic on connect"; case MQTTASYNC_COMMAND_IGNORED: return "Connect or disconnect command ignored"; + case MQTTASYNC_MAX_BUFFERED: + return "maxBufferedMessages in the connect options must be >= 0"; } chars = snprintf(buf, sizeof(buf), "Unknown error code %d", code); diff --git a/src/MQTTAsync.h b/src/MQTTAsync.h index c81531303..41571db4f 100644 --- a/src/MQTTAsync.h +++ b/src/MQTTAsync.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2021 IBM Corp., Ian Craggs and others + * Copyright (c) 2009, 2022 IBM Corp., Ian Craggs and others * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -29,7 +29,7 @@ * @cond MQTTAsync_main * @mainpage Asynchronous MQTT client library for C * - * © Copyright 2009, 2021 IBM Corp., Ian Craggs and others + * © Copyright 2009, 2022 IBM Corp., Ian Craggs and others * * @brief An Asynchronous MQTT client library for C. * @@ -193,6 +193,11 @@ * for the previous connect or disconnect command to be complete. */ #define MQTTASYNC_COMMAND_IGNORED -18 + /* + * Return code: maxBufferedMessages in the connect options must be >= 0 + */ + #define MQTTASYNC_MAX_BUFFERED -19 + /** * Default MQTT version to connect with. Use 3.1.1 then fall back to 3.1 */ @@ -421,7 +426,7 @@ typedef void MQTTAsync_connected(void* context, char* cause); /** * This is a callback function, which will be called when the client - * library receives a disconnect packet. + * library receives a disconnect packet from the server. This applies to MQTT V5 and above only. * * Note: Neither MQTTAsync_create() nor MQTTAsync_destroy() should be * called within this callback. @@ -950,7 +955,9 @@ typedef struct int struct_version; /** Whether to allow messages to be sent when the client library is not connected. */ int sendWhileDisconnected; - /** The maximum number of messages allowed to be buffered while not connected. */ + /** The maximum number of messages allowed to be buffered. This is intended to be used to + * limit the number of messages queued while the client is not connected. It also applies + * when the client is connected, however, so has to be greater than 0. */ int maxBufferedMessages; /** Whether the MQTT version is 3.1, 3.1.1, or 5. To use V5, this must be set. * MQTT V5 has to be chosen here, because during the create call the message persistence @@ -1345,11 +1352,11 @@ typedef struct */ const MQTTAsync_nameValue* httpHeaders; /** - * HTTP proxy for websockets + * HTTP proxy */ const char* httpProxy; /** - * HTTPS proxy for websockets + * HTTPS proxy */ const char* httpsProxy; } MQTTAsync_connectOptions; @@ -1696,7 +1703,8 @@ LIBMQTT_API void MQTTAsync_setTraceLevel(enum MQTTASYNC_TRACE_LEVELS level); /** * This is a callback function prototype which must be implemented if you want - * to receive trace information. + * to receive trace information. Do not invoke any other Paho API calls in this + * callback function - unpredictable behavior may result. * @param level the trace level of the message returned * @param message the trace message. This is a pointer to a static buffer which * will be overwritten on each call. You must copy the data if you want to keep @@ -1888,7 +1896,7 @@ LIBMQTT_API const char* MQTTAsync_strerror(int code); #include #endif -#define ADDRESS "tcp://mqtt.eclipse.org:1883" +#define ADDRESS "tcp://mqtt.eclipseprojects.io:1883" #define CLIENTID "ExampleClientPub" #define TOPIC "MQTT Examples" #define PAYLOAD "Hello World!" @@ -2059,7 +2067,7 @@ int main(int argc, char* argv[]) #include #endif -#define ADDRESS "tcp://mqtt.eclipse.org:1883" +#define ADDRESS "tcp://mqtt.eclipseprojects.io:1883" #define CLIENTID "ExampleClientSub" #define TOPIC "MQTT Examples" #define PAYLOAD "Hello World!" diff --git a/src/MQTTAsyncUtils.c b/src/MQTTAsyncUtils.c index 8c4554ec6..a090a6a6a 100644 --- a/src/MQTTAsyncUtils.c +++ b/src/MQTTAsyncUtils.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2021 IBM Corp., Ian Craggs and others + * Copyright (c) 2009, 2022 IBM Corp., Ian Craggs and others * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -12,7 +12,7 @@ * * Contributors: * Ian Craggs - initial implementation and documentation - * + * Sven Gambel - add generic proxy support *******************************************************************************/ #include @@ -35,6 +35,7 @@ #include "Heap.h" #include "OsWrapper.h" #include "WebSocket.h" +#include "Proxy.h" static int clientSockCompare(void* a, void* b); static int MQTTAsync_checkConn(MQTTAsync_command* command, MQTTAsyncs* client); @@ -59,7 +60,7 @@ static int MQTTAsync_deliverMessage(MQTTAsyncs* m, char* topicName, size_t topic static int MQTTAsync_disconnect_internal(MQTTAsync handle, int timeout); static int cmdMessageIDCompare(void* a, void* b); static void MQTTAsync_retry(void); -static MQTTPacket* MQTTAsync_cycle(int* sock, unsigned long timeout, int* rc); +static MQTTPacket* MQTTAsync_cycle(SOCKET* sock, unsigned long timeout, int* rc); static int MQTTAsync_connecting(MQTTAsyncs* m); extern MQTTProtocol state; /* defined in MQTTAsync.c */ @@ -198,7 +199,9 @@ void MQTTAsync_terminate(void) { FUNC_ENTRY; MQTTAsync_stop(); - if (global_initialized) + + /* don't destroy global data if a new client was created while waiting for background threads to terminate */ + if (global_initialized && bstate->clients->count == 0) { ListElement* elem = NULL; ListFree(bstate->clients); @@ -254,13 +257,15 @@ static int MQTTAsync_persistCommand(MQTTAsync_queuedCommand* qcmd) int chars = 0; /* number of chars from snprintf */ int props_allocated = 0; int process = 1; + int multiplier = 2; /* default value 2 for MQTTVERSION < 5 */ FUNC_ENTRY; switch (command->type) { case SUBSCRIBE: + multiplier = (aclient->c->MQTTVersion >= MQTTVERSION_5) ? 3 : 2; nbufs = ((aclient->c->MQTTVersion >= MQTTVERSION_5) ? 4 : 3) + - (command->details.sub.count * 2); + (command->details.sub.count * multiplier); if (((lens = (int*)malloc(nbufs * sizeof(int))) == NULL) || ((bufs = malloc(nbufs * sizeof(char *))) == NULL)) @@ -282,12 +287,9 @@ static int MQTTAsync_persistCommand(MQTTAsync_queuedCommand* qcmd) bufs[bufindex] = command->details.sub.topics[i]; lens[bufindex++] = (int)strlen(command->details.sub.topics[i]) + 1; - if (aclient->c->MQTTVersion < MQTTVERSION_5) - { - bufs[bufindex] = &command->details.sub.qoss[i]; - lens[bufindex++] = sizeof(command->details.sub.qoss[i]); - } - else + bufs[bufindex] = &command->details.sub.qoss[i]; + lens[bufindex++] = sizeof(command->details.sub.qoss[i]); + if (aclient->c->MQTTVersion >= MQTTVERSION_5) { if (command->details.sub.count == 1) { @@ -456,7 +458,7 @@ static MQTTAsync_queuedCommand* MQTTAsync_restoreCommand(char* buffer, int bufle switch (command->type) { case SUBSCRIBE: - if (qcommand->not_restored == 0) + if (qcommand->not_restored == 1) break; if (&ptr[sizeof(int)] > endpos) goto error_exit; @@ -467,16 +469,17 @@ static MQTTAsync_queuedCommand* MQTTAsync_restoreCommand(char* buffer, int bufle { if ((command->details.sub.topics = (char **)malloc(sizeof(char *) * command->details.sub.count)) == NULL) goto error_exit; - if (MQTTVersion < MQTTVERSION_5) - { - if ((command->details.sub.qoss = (int *)malloc(sizeof(int) * command->details.sub.count)) == NULL) - goto error_exit; - } - else if (command->details.sub.count > 1) + if ((command->details.sub.qoss = (int *)malloc(sizeof(int) * command->details.sub.count)) == NULL) + goto error_exit; + + if ((MQTTVersion >= MQTTVERSION_5)) { - command->details.sub.optlist = (MQTTSubscribe_options*)malloc(sizeof(MQTTSubscribe_options) * command->details.sub.count); - if (command->details.sub.optlist == NULL) - goto error_exit; + if (command->details.sub.count > 1) + { + command->details.sub.optlist = (MQTTSubscribe_options*)malloc(sizeof(MQTTSubscribe_options) * command->details.sub.count); + if (command->details.sub.optlist == NULL) + goto error_exit; + } } } @@ -488,18 +491,15 @@ static MQTTAsync_queuedCommand* MQTTAsync_restoreCommand(char* buffer, int bufle if ((command->details.sub.topics[i] = malloc(data_size)) == NULL) goto error_exit; - strcpy(command->details.sub.topics[i], ptr); ptr += data_size; - if (MQTTVersion < MQTTVERSION_5) - { - if (&ptr[sizeof(int)] > endpos) - goto error_exit; - command->details.sub.qoss[i] = *(int*)ptr; - ptr += sizeof(int); - } - else + if (&ptr[sizeof(int)] > endpos) + goto error_exit; + command->details.sub.qoss[i] = *(int*)ptr; + ptr += sizeof(int); + + if (MQTTVersion >= MQTTVERSION_5) { if (&ptr[sizeof(MQTTSubscribe_options)] > endpos) goto error_exit; @@ -518,7 +518,7 @@ static MQTTAsync_queuedCommand* MQTTAsync_restoreCommand(char* buffer, int bufle break; case UNSUBSCRIBE: - if (qcommand->not_restored == 0) + if (qcommand->not_restored == 1) break; if (&ptr[sizeof(int)] > endpos) @@ -662,14 +662,14 @@ int MQTTAsync_restoreCommands(MQTTAsyncs* client) cmd->seqno = atoi(strchr(msgkeys[i], '-')+1); /* key format is tag'-'seqno */ /* we can just append the commands to the list as they've already been sorted */ ListAppend(MQTTAsync_commands, cmd, sizeof(MQTTAsync_queuedCommand)); - if (buffer) - free(buffer); client->command_seqno = max(client->command_seqno, cmd->seqno); commands_restored++; if (cmd->command.type == PUBLISH) client->noBufferedMessages++; } } + if (buffer) + free(buffer); if (msgkeys[i]) free(msgkeys[i]); i++; @@ -889,11 +889,12 @@ int MQTTAsync_addCommand(MQTTAsync_queuedCommand* command, int command_size) { ListDetach(MQTTAsync_commands, first_publish); - MQTTAsync_freeCommand(first_publish); #if !defined(NO_PERSISTENCE) if (command->client->c->persistence) MQTTAsync_unpersistCommand(first_publish); #endif + + MQTTAsync_freeCommand(first_publish); } } else @@ -976,12 +977,12 @@ void MQTTAsync_checkDisconnect(MQTTAsync handle, MQTTAsync_command* command) /** * Call Socket_noPendingWrites(int socket) with protection by socket_mutex, see https://github.com/eclipse/paho.mqtt.c/issues/385 */ -static int MQTTAsync_Socket_noPendingWrites(int socket) +static int MQTTAsync_Socket_noPendingWrites(SOCKET socket) { int rc; - Thread_lock_mutex(socket_mutex); + MQTTAsync_lock_mutex(socket_mutex); rc = Socket_noPendingWrites(socket); - Thread_unlock_mutex(socket_mutex); + MQTTAsync_unlock_mutex(socket_mutex); return rc; } @@ -1059,13 +1060,14 @@ static void MQTTAsync_freeCommand(MQTTAsync_queuedCommand *command) } -void MQTTAsync_writeComplete(int socket, int rc) +void MQTTAsync_writeComplete(SOCKET socket, int rc) { ListElement* found = NULL; FUNC_ENTRY; - /* a partial write is now complete for a socket - this will be on a publish*/ + /* a partial write is now complete for a socket - this will be on a publish*/ + MQTTAsync_lock_mutex(mqttasync_mutex); MQTTProtocol_checkPendingWrites(); /* find the client using this socket */ @@ -1166,6 +1168,7 @@ void MQTTAsync_writeComplete(int socket, int rc) m->pending_write = NULL; } /* if pending_write */ } + MQTTAsync_unlock_mutex(mqttasync_mutex); FUNC_EXIT; } @@ -1243,11 +1246,11 @@ static int MQTTAsync_processCommand(void) free(command->key); command->key = NULL; command = MQTTAsync_restoreCommand(buffer, buflen, MQTTVersion, command); - if (buffer) - free(buffer); } else Log(LOG_ERROR, -1, "Error restoring command: rc %d from pget\n", rc); + if (buffer) + free(buffer); } MQTTAsync_unpersistCommand(command); } @@ -1438,7 +1441,10 @@ static int MQTTAsync_processCommand(void) else { if (rc != SOCKET_ERROR) - command->command.details.pub.destinationName = NULL; /* this will be freed by the protocol code */ + { + command->command.details.pub.payload = NULL; /* this will be freed by the protocol code */ + command->command.details.pub.destinationName = NULL; /* this will be freed by the protocol code */ + } command->client->pending_write = &command->command; } } @@ -1750,6 +1756,8 @@ static void MQTTAsync_checkTimeouts(void) thread_return_type WINAPI MQTTAsync_sendThread(void* n) { + int timeout = 10; /* first time in we have a small timeout. Gets things started more quickly */ + FUNC_ENTRY; MQTTAsync_lock_mutex(mqttasync_mutex); sendThread_state = RUNNING; @@ -1758,20 +1766,27 @@ thread_return_type WINAPI MQTTAsync_sendThread(void* n) while (!MQTTAsync_tostop) { int rc; + int command_count = 0; - while (MQTTAsync_commands->count > 0) + MQTTAsync_lock_mutex(mqttcommand_mutex); + command_count = MQTTAsync_commands->count; + MQTTAsync_unlock_mutex(mqttcommand_mutex); + while (command_count > 0) { if (MQTTAsync_processCommand() == 0) break; /* no commands were processed, so go into a wait */ + MQTTAsync_lock_mutex(mqttcommand_mutex); + command_count = MQTTAsync_commands->count; + MQTTAsync_unlock_mutex(mqttcommand_mutex); } #if !defined(_WIN32) && !defined(_WIN64) - if ((rc = Thread_wait_cond(send_cond, 1)) != 0 && rc != ETIMEDOUT) + if ((rc = Thread_wait_cond(send_cond, timeout)) != 0 && rc != ETIMEDOUT) Log(LOG_ERROR, -1, "Error %d waiting for condition variable", rc); #else - if ((rc = Thread_wait_sem(send_sem, 1000)) != 0 && rc != ETIMEDOUT) + if ((rc = Thread_wait_sem(send_sem, timeout)) != 0 && rc != ETIMEDOUT) Log(LOG_ERROR, -1, "Error %d waiting for semaphore", rc); #endif - + timeout = 1000; /* 1 second for follow on waits */ MQTTAsync_checkTimeouts(); } sendThread_state = STOPPING; @@ -1969,7 +1984,7 @@ thread_return_type WINAPI MQTTAsync_receiveThread(void* n) while (!MQTTAsync_tostop) { int rc = SOCKET_ERROR; - int sock = -1; + SOCKET sock = -1; MQTTAsyncs* m = NULL; MQTTPacket* pack = NULL; @@ -2252,13 +2267,17 @@ thread_return_type WINAPI MQTTAsync_receiveThread(void* n) else if (pack->header.bits.type == DISCONNECT) { Ack* disc = (Ack*)pack; + int discrc = 0; + discrc = disc->rc; if (m->disconnected) { Log(TRACE_MIN, -1, "Calling disconnected for client %s", m->c->clientID); (*(m->disconnected))(m->disconnected_context, &disc->properties, disc->rc); } - MQTTPacket_freeAck(disc); + rc = MQTTProtocol_handleDisconnects(pack, m->c->net.socket); + m->c->connected = 0; /* don't send disconnect packet back */ + nextOrClose(m, discrc, "Received disconnect"); } } } @@ -2309,7 +2328,7 @@ static void MQTTAsync_stop(void) { int count = 0; MQTTAsync_tostop = 1; - while ((sendThread_state != STOPPED || receiveThread_state != STOPPED) && ++count < 100) + while ((sendThread_state != STOPPED || receiveThread_state != STOPPED) && MQTTAsync_tostop != 0 && ++count < 100) { MQTTAsync_unlock_mutex(mqttasync_mutex); Log(TRACE_MIN, -1, "sleeping"); @@ -2337,7 +2356,7 @@ static void MQTTAsync_closeOnly(Clients* client, enum MQTTReasonCodes reasonCode MQTTProtocol_checkPendingWrites(); if (client->connected && Socket_noPendingWrites(client->net.socket)) MQTTPacket_send_disconnect(client, reasonCode, props); - Thread_lock_mutex(socket_mutex); + MQTTAsync_lock_mutex(socket_mutex); WebSocket_close(&client->net, WebSocket_CLOSE_NORMAL, NULL); #if defined(OPENSSL) SSL_SESSION_free(client->session); /* is a no-op if session is NULL */ @@ -2349,7 +2368,7 @@ static void MQTTAsync_closeOnly(Clients* client, enum MQTTReasonCodes reasonCode #if defined(OPENSSL) client->net.ssl = NULL; #endif - Thread_unlock_mutex(socket_mutex); + MQTTAsync_unlock_mutex(socket_mutex); } client->connected = 0; client->connect_state = NOT_IN_PROGRESS; @@ -2708,7 +2727,7 @@ static int MQTTAsync_connecting(MQTTAsyncs* m) else if (strncmp(URI_WSS, serverURI, strlen(URI_WSS)) == 0) { serverURI += strlen(URI_WSS); - default_port = WS_DEFAULT_PORT; + default_port = WSS_DEFAULT_PORT; } #endif } @@ -2733,9 +2752,9 @@ static int MQTTAsync_connecting(MQTTAsyncs* m) size_t hostname_len; int setSocketForSSLrc = 0; - if (m->websocket && m->c->net.https_proxy) { + if (m->c->net.https_proxy) { m->c->connect_state = PROXY_CONNECT_IN_PROGRESS; - if ((rc = WebSocket_proxy_connect( &m->c->net, 1, serverURI)) == SOCKET_ERROR ) + if ((rc = Proxy_connect( &m->c->net, 1, serverURI)) == SOCKET_ERROR ) goto exit; } @@ -2768,7 +2787,7 @@ static int MQTTAsync_connecting(MQTTAsyncs* m) if ( m->websocket ) { m->c->connect_state = WEBSOCKET_IN_PROGRESS; - if ((rc = WebSocket_connect(&m->c->net, serverURI)) == SOCKET_ERROR ) + if ((rc = WebSocket_connect(&m->c->net, m->ssl, serverURI)) == SOCKET_ERROR ) goto exit; } else @@ -2795,16 +2814,16 @@ static int MQTTAsync_connecting(MQTTAsyncs* m) else { #endif + if (m->c->net.http_proxy) { + m->c->connect_state = PROXY_CONNECT_IN_PROGRESS; + if ((rc = Proxy_connect( &m->c->net, 0, serverURI)) == SOCKET_ERROR ) + goto exit; + } + if ( m->websocket ) { - if (m->c->net.http_proxy) { - m->c->connect_state = PROXY_CONNECT_IN_PROGRESS; - if ((rc = WebSocket_proxy_connect( &m->c->net, 0, serverURI)) == SOCKET_ERROR ) - goto exit; - } - m->c->connect_state = WEBSOCKET_IN_PROGRESS; - if ((rc = WebSocket_connect(&m->c->net, serverURI)) == SOCKET_ERROR ) + if ((rc = WebSocket_connect(&m->c->net, 0, serverURI)) == SOCKET_ERROR ) goto exit; } else @@ -2835,7 +2854,7 @@ static int MQTTAsync_connecting(MQTTAsyncs* m) if ( m->websocket ) { m->c->connect_state = WEBSOCKET_IN_PROGRESS; - if ((rc = WebSocket_connect(&m->c->net, serverURI)) == SOCKET_ERROR ) + if ((rc = WebSocket_connect(&m->c->net, 1, serverURI)) == SOCKET_ERROR ) goto exit; } else @@ -2868,27 +2887,25 @@ static int MQTTAsync_connecting(MQTTAsyncs* m) } -static MQTTPacket* MQTTAsync_cycle(int* sock, unsigned long timeout, int* rc) +static MQTTPacket* MQTTAsync_cycle(SOCKET* sock, unsigned long timeout, int* rc) { - struct timeval tp = {0L, 0L}; MQTTPacket* pack = NULL; + int rc1 = 0; FUNC_ENTRY; - if (timeout > 0L) - { - tp.tv_sec = timeout / 1000; - tp.tv_usec = (timeout % 1000) * 1000; /* this field is microseconds! */ - } - - int rc1 = 0; #if defined(OPENSSL) if ((*sock = SSLSocket_getPendingRead()) == -1) { #endif + int should_stop = 0; + /* 0 from getReadySocket indicates no work to do, rc -1 == error */ - *sock = Socket_getReadySocket(0, &tp,socket_mutex, &rc1); + *sock = Socket_getReadySocket(0, (int)timeout, socket_mutex, &rc1); *rc = rc1; - if (!MQTTAsync_tostop && *sock == 0 && (tp.tv_sec > 0L || tp.tv_usec > 0L)) + MQTTAsync_lock_mutex(mqttasync_mutex); + should_stop = MQTTAsync_tostop; + MQTTAsync_unlock_mutex(mqttasync_mutex); + if (!should_stop && *sock == 0 && (timeout > 0L)) MQTTAsync_sleep(100L); #if defined(OPENSSL) } diff --git a/src/MQTTAsyncUtils.h b/src/MQTTAsyncUtils.h index 42a3145b4..f65980e3b 100644 --- a/src/MQTTAsyncUtils.h +++ b/src/MQTTAsyncUtils.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2020 IBM Corp. and others + * Copyright (c) 2009, 2022 IBM Corp. and others * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -169,7 +169,7 @@ void MQTTAsync_closeSession(Clients* client, enum MQTTReasonCodes reasonCode, MQ int MQTTAsync_disconnect1(MQTTAsync handle, const MQTTAsync_disconnectOptions* options, int internal); int MQTTAsync_assignMsgId(MQTTAsyncs* m); int MQTTAsync_getNoBufferedMessages(MQTTAsyncs* m); -void MQTTAsync_writeComplete(int socket, int rc); +void MQTTAsync_writeComplete(SOCKET socket, int rc); void setRetryLoopInterval(int keepalive); #if defined(_WIN32) || defined(_WIN64) diff --git a/src/MQTTClient.c b/src/MQTTClient.c index b32a35ed3..7cba42b92 100644 --- a/src/MQTTClient.c +++ b/src/MQTTClient.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2021 IBM Corp., Ian Craggs and others + * Copyright (c) 2009, 2022 IBM Corp., Ian Craggs and others * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -35,6 +35,7 @@ * Ian Craggs - check for NULL SSL options #334 * Ian Craggs - allocate username/password buffers #431 * Ian Craggs - MQTT 5.0 support + * Sven Gambel - add generic proxy support *******************************************************************************/ /** @@ -80,6 +81,7 @@ #include "VersionInfo.h" #include "WebSocket.h" +#include "Proxy.h" const char *client_timestamp_eye = "MQTTClientV3_Timestamp " BUILD_TIMESTAMP; const char *client_version_eye = "MQTTClientV3_Version " CLIENT_VERSION; @@ -359,11 +361,11 @@ static MQTTResponse MQTTClient_connectURI(MQTTClient handle, MQTTClient_connectO static int MQTTClient_disconnect1(MQTTClient handle, int timeout, int internal, int stop, enum MQTTReasonCodes, MQTTProperties*); static int MQTTClient_disconnect_internal(MQTTClient handle, int timeout); static void MQTTClient_retry(void); -static MQTTPacket* MQTTClient_cycle(int* sock, ELAPSED_TIME_TYPE timeout, int* rc); -static MQTTPacket* MQTTClient_waitfor(MQTTClient handle, int packet_type, int* rc, ELAPSED_TIME_TYPE timeout); +static MQTTPacket* MQTTClient_cycle(SOCKET* sock, ELAPSED_TIME_TYPE timeout, int* rc); +static MQTTPacket* MQTTClient_waitfor(MQTTClient handle, int packet_type, int* rc, int64_t timeout); /*static int pubCompare(void* a, void* b); */ static void MQTTProtocol_checkPendingWrites(void); -static void MQTTClient_writeComplete(int socket, int rc); +static void MQTTClient_writeComplete(SOCKET socket, int rc); int MQTTClient_createWithOptions(MQTTClient* handle, const char* serverURI, const char* clientId, @@ -428,6 +430,7 @@ int MQTTClient_createWithOptions(MQTTClient* handle, const char* serverURI, cons bstate->clients = ListInitialize(); Socket_outInitialize(); Socket_setWriteCompleteCallback(MQTTClient_writeComplete); + Socket_setWriteAvailableCallback(MQTTProtocol_writeAvailable); handles = ListInitialize(); #if defined(OPENSSL) SSLSocket_initialize(); @@ -486,6 +489,7 @@ int MQTTClient_createWithOptions(MQTTClient* handle, const char* serverURI, cons m->c->outboundMsgs = ListInitialize(); m->c->inboundMsgs = ListInitialize(); m->c->messageQueue = ListInitialize(); + m->c->outboundQueue = ListInitialize(); m->c->clientID = MQTTStrdup(clientId); m->connect_sem = Thread_create_sem(&rc); m->connack_sem = Thread_create_sem(&rc); @@ -572,7 +576,7 @@ void MQTTClient_destroy(MQTTClient* handle) if (m->c) { - int saved_socket = m->c->net.socket; + SOCKET saved_socket = m->c->net.socket; char* saved_clientid = MQTTStrdup(m->c->clientID); #if !defined(NO_PERSISTENCE) MQTTPersistence_close(m->c); @@ -803,7 +807,7 @@ static thread_return_type WINAPI MQTTClient_run(void* n) while (!tostop) { int rc = SOCKET_ERROR; - int sock = -1; + SOCKET sock = -1; MQTTClients* m = NULL; MQTTPacket* pack = NULL; @@ -1231,9 +1235,9 @@ static MQTTResponse MQTTClient_connectURIVersion(MQTTClient handle, MQTTClient_c const char *topic; int setSocketForSSLrc = 0; - if (m->websocket && m->c->net.https_proxy) { + if (m->c->net.https_proxy) { m->c->connect_state = PROXY_CONNECT_IN_PROGRESS; - if ((rc = WebSocket_proxy_connect( &m->c->net, 1, serverURI)) == SOCKET_ERROR ) + if ((rc = Proxy_connect( &m->c->net, 1, serverURI)) == SOCKET_ERROR ) goto exit; } @@ -1263,7 +1267,7 @@ static MQTTResponse MQTTClient_connectURIVersion(MQTTClient handle, MQTTClient_c if (m->websocket) { m->c->connect_state = WEBSOCKET_IN_PROGRESS; - rc = WebSocket_connect(&m->c->net, serverURI); + rc = WebSocket_connect(&m->c->net, 1, serverURI); if ( rc == SOCKET_ERROR ) goto exit; } @@ -1288,28 +1292,31 @@ static MQTTResponse MQTTClient_connectURIVersion(MQTTClient handle, MQTTClient_c } } #endif - else if (m->websocket) + else { if (m->c->net.http_proxy) { m->c->connect_state = PROXY_CONNECT_IN_PROGRESS; - if ((rc = WebSocket_proxy_connect( &m->c->net, 0, serverURI)) == SOCKET_ERROR ) + if ((rc = Proxy_connect( &m->c->net, 0, serverURI)) == SOCKET_ERROR ) goto exit; } - m->c->connect_state = WEBSOCKET_IN_PROGRESS; - if ( WebSocket_connect(&m->c->net, serverURI) == SOCKET_ERROR ) + if (m->websocket) { - rc = SOCKET_ERROR; - goto exit; + m->c->connect_state = WEBSOCKET_IN_PROGRESS; + if ( WebSocket_connect(&m->c->net, 0, serverURI) == SOCKET_ERROR ) + { + rc = SOCKET_ERROR; + goto exit; + } } - } - else - { - m->c->connect_state = WAIT_FOR_CONNACK; /* TCP connect completed, in which case send the MQTT connect packet */ - if (MQTTPacket_send_connect(m->c, MQTTVersion, connectProperties, willProperties) == SOCKET_ERROR) + else { - rc = SOCKET_ERROR; - goto exit; + m->c->connect_state = WAIT_FOR_CONNACK; /* TCP connect completed, in which case send the MQTT connect packet */ + if (MQTTPacket_send_connect(m->c, MQTTVersion, connectProperties, willProperties) == SOCKET_ERROR) + { + rc = SOCKET_ERROR; + goto exit; + } } } } @@ -1332,7 +1339,7 @@ static MQTTResponse MQTTClient_connectURIVersion(MQTTClient handle, MQTTClient_c { /* wait for websocket connect */ m->c->connect_state = WEBSOCKET_IN_PROGRESS; - rc = WebSocket_connect( &m->c->net, serverURI ); + rc = WebSocket_connect( &m->c->net, 1, serverURI); if ( rc != 1 ) { rc = SOCKET_ERROR; @@ -2477,27 +2484,24 @@ static void MQTTClient_retry(void) } -static MQTTPacket* MQTTClient_cycle(int* sock, ELAPSED_TIME_TYPE timeout, int* rc) +static MQTTPacket* MQTTClient_cycle(SOCKET* sock, ELAPSED_TIME_TYPE timeout, int* rc) { - struct timeval tp = {0L, 0L}; static Ack ack; MQTTPacket* pack = NULL; + int rc1 = 0; + START_TIME_TYPE start; FUNC_ENTRY; - if (timeout > 0L) - { - tp.tv_sec = (long)(timeout / 1000); - tp.tv_usec = (long)((timeout % 1000) * 1000); /* this field is microseconds! */ - } - - int rc1 = 0; #if defined(OPENSSL) if ((*sock = SSLSocket_getPendingRead()) == -1) { /* 0 from getReadySocket indicates no work to do, rc -1 == error */ #endif - *sock = Socket_getReadySocket(0, &tp, socket_mutex, rc); + start = MQTTTime_start_clock(); + *sock = Socket_getReadySocket(0, (int)timeout, socket_mutex, rc); *rc = rc1; + if (*sock == 0 && timeout >= 100L && MQTTTime_elapsed(start) < (int64_t)10) + MQTTTime_sleep(100L); #if defined(OPENSSL) } #endif @@ -2576,7 +2580,7 @@ static MQTTPacket* MQTTClient_cycle(int* sock, ELAPSED_TIME_TYPE timeout, int* r } -static MQTTPacket* MQTTClient_waitfor(MQTTClient handle, int packet_type, int* rc, ELAPSED_TIME_TYPE timeout) +static MQTTPacket* MQTTClient_waitfor(MQTTClient handle, int packet_type, int* rc, int64_t timeout) { MQTTPacket* pack = NULL; MQTTClients* m = handle; @@ -2611,7 +2615,7 @@ static MQTTPacket* MQTTClient_waitfor(MQTTClient handle, int packet_type, int* r *rc = TCPSOCKET_COMPLETE; while (1) { - int sock = -1; + SOCKET sock = -1; pack = MQTTClient_cycle(&sock, 100L, rc); if (sock == m->c->net.socket) { @@ -2671,7 +2675,7 @@ static MQTTPacket* MQTTClient_waitfor(MQTTClient handle, int packet_type, int* r } } } - if (MQTTTime_elapsed(start) > (ELAPSED_TIME_TYPE)timeout) + if (MQTTTime_elapsed(start) > (uint64_t)timeout) { pack = NULL; break; @@ -2716,7 +2720,7 @@ int MQTTClient_receive(MQTTClient handle, char** topicName, int* topicLen, MQTTC elapsed = MQTTTime_elapsed(start); do { - int sock = 0; + SOCKET sock = 0; MQTTClient_cycle(&sock, (timeout > elapsed) ? timeout - elapsed : 0L, &rc); if (rc == SOCKET_ERROR) @@ -2758,7 +2762,7 @@ void MQTTClient_yield(void) elapsed = MQTTTime_elapsed(start); do { - int sock = -1; + SOCKET sock = -1; MQTTClient_cycle(&sock, (timeout > elapsed) ? timeout - elapsed : 0L, &rc); Thread_lock_mutex(mqttclient_mutex); if (rc == SOCKET_ERROR && ListFindItem(handles, &sock, clientSockCompare)) @@ -3005,7 +3009,7 @@ static void MQTTProtocol_checkPendingWrites(void) } -static void MQTTClient_writeComplete(int socket, int rc) +static void MQTTClient_writeComplete(SOCKET socket, int rc) { ListElement* found = NULL; diff --git a/src/MQTTClient.h b/src/MQTTClient.h index d29fe4e3b..6fd31f6ea 100644 --- a/src/MQTTClient.h +++ b/src/MQTTClient.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2021 IBM Corp., Ian Craggs and others + * Copyright (c) 2009, 2022 IBM Corp., Ian Craggs and others * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -41,7 +41,7 @@ * @endcond * @cond MQTTClient_main * @mainpage MQTT Client library for C - * © Copyright 2009, 2021 IBM Corp., Ian Craggs and others + * © Copyright 2009, 2022 IBM Corp., Ian Craggs and others * * @brief An MQTT client library in C. * @@ -951,11 +951,11 @@ typedef struct */ const MQTTClient_nameValue* httpHeaders; /** - * HTTP proxy for websockets + * HTTP proxy */ const char* httpProxy; /** - * HTTPS proxy for websockets + * HTTPS proxy */ const char* httpsProxy; } MQTTClient_connectOptions; @@ -1407,7 +1407,8 @@ LIBMQTT_API void MQTTClient_setTraceLevel(enum MQTTCLIENT_TRACE_LEVELS level); /** * This is a callback function prototype which must be implemented if you want - * to receive trace information. + * to receive trace information. Do not invoke any other Paho API calls in this + * callback function - unpredictable behavior may result. * @param level the trace level of the message returned * @param message the trace message. This is a pointer to a static buffer which * will be overwritten on each call. You must copy the data if you want to keep @@ -1581,7 +1582,7 @@ LIBMQTT_API const char* MQTTClient_strerror(int code); #include #include "MQTTClient.h" -#define ADDRESS "tcp://mqtt.eclipse.org:1883" +#define ADDRESS "tcp://mqtt.eclipseprojects.io:1883" #define CLIENTID "ExampleClientPub" #define TOPIC "MQTT Examples" #define PAYLOAD "Hello World!" @@ -1648,7 +1649,7 @@ int main(int argc, char* argv[]) #include #endif -#define ADDRESS "tcp://mqtt.eclipse.org:1883" +#define ADDRESS "tcp://mqtt.eclipseprojects.io:1883" #define CLIENTID "ExampleClientPub" #define TOPIC "MQTT Examples" #define PAYLOAD "Hello World!" @@ -1757,7 +1758,7 @@ int main(int argc, char* argv[]) #include #include "MQTTClient.h" -#define ADDRESS "tcp://mqtt.eclipse.org:1883" +#define ADDRESS "tcp://mqtt.eclipseprojects.io:1883" #define CLIENTID "ExampleClientSub" #define TOPIC "MQTT Examples" #define PAYLOAD "Hello World!" diff --git a/src/MQTTPacket.c b/src/MQTTPacket.c index 296bf31b7..6c9a6c5fe 100644 --- a/src/MQTTPacket.c +++ b/src/MQTTPacket.c @@ -816,10 +816,11 @@ void* MQTTPacket_ack(int MQTTVersion, unsigned char aHeader, char* data, size_t pack->rc = MQTTREASONCODE_SUCCESS; pack->properties = props; - if (datalen > 2) + /* disconnect has no msgid */ + if (datalen > 2 || (pack->header.bits.type == DISCONNECT && datalen > 0)) pack->rc = readChar(&curdata); /* reason code */ - if (datalen > 3) + if (datalen > 3 || (pack->header.bits.type == DISCONNECT && datalen > 1)) { if (MQTTProperties_read(&pack->properties, &curdata, enddata) != 1) { diff --git a/src/MQTTPersistence.c b/src/MQTTPersistence.c index c5634ded4..7e6e11e8d 100644 --- a/src/MQTTPersistence.c +++ b/src/MQTTPersistence.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2020 IBM Corp. + * Copyright (c) 2009, 2022 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -431,7 +431,7 @@ void MQTTPersistence_insertInOrder(List* list, void* content, size_t size) * @param the MQTT version being used (>= MQTTVERSION_5 means properties included) * @return 0 if success, #MQTTCLIENT_PERSISTENCE_ERROR otherwise. */ -int MQTTPersistence_putPacket(int socket, char* buf0, size_t buf0len, int count, +int MQTTPersistence_putPacket(SOCKET socket, char* buf0, size_t buf0len, int count, char** buffers, size_t* buflens, int htype, int msgId, int scr, int MQTTVersion) { int rc = 0; @@ -886,10 +886,11 @@ int MQTTPersistence_restoreMessageQueue(Clients* c) { qe->seqno = atoi(strchr(msgkeys[i], '-')+1); /* key format is tag'-'seqno */ MQTTPersistence_insertInSeqOrder(c->messageQueue, qe, sizeof(MQTTPersistence_qEntry)); - free(buffer); c->qentry_seqno = max(c->qentry_seqno, qe->seqno); entries_restored++; } + if (buffer) + free(buffer); } if (msgkeys[i]) { diff --git a/src/MQTTPersistence.h b/src/MQTTPersistence.h index e48111618..3c62a79c5 100644 --- a/src/MQTTPersistence.h +++ b/src/MQTTPersistence.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2020 IBM Corp. + * Copyright (c) 2009, 2022 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -63,7 +63,7 @@ int MQTTPersistence_clear(Clients* c); int MQTTPersistence_restorePackets(Clients* c); void* MQTTPersistence_restorePacket(int MQTTVersion, char* buffer, size_t buflen); void MQTTPersistence_insertInOrder(List* list, void* content, size_t size); -int MQTTPersistence_putPacket(int socket, char* buf0, size_t buf0len, int count, +int MQTTPersistence_putPacket(SOCKET socket, char* buf0, size_t buf0len, int count, char** buffers, size_t* buflens, int htype, int msgId, int scr, int MQTTVersion); int MQTTPersistence_remove(Clients* c, char* type, int qos, int msgId); void MQTTPersistence_wrapMsgID(Clients *c); diff --git a/src/MQTTProtocol.h b/src/MQTTProtocol.h index 52bcd159d..3bb816d82 100644 --- a/src/MQTTProtocol.h +++ b/src/MQTTProtocol.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2014 IBM Corp. + * Copyright (c) 2009, 2022 IBM Corp., Ian Craggs * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -27,7 +27,7 @@ typedef struct { - int socket; + SOCKET socket; Publications* p; } pending_write; diff --git a/src/MQTTProtocolClient.c b/src/MQTTProtocolClient.c index 78f4d7f21..aaf43f75a 100644 --- a/src/MQTTProtocolClient.c +++ b/src/MQTTProtocolClient.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2021 IBM Corp. and Ian Craggs + * Copyright (c) 2009, 2022 IBM Corp. and Ian Craggs * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -36,6 +36,7 @@ #if !defined(NO_PERSISTENCE) #include "MQTTPersistence.h" #endif +#include "Socket.h" #include "SocketBuffer.h" #include "StackTrace.h" #include "Heap.h" @@ -55,6 +56,13 @@ static int MQTTProtocol_startPublishCommon( int retained); static void MQTTProtocol_retries(START_TIME_TYPE now, Clients* client, int regardless); +static int MQTTProtocol_queueAck(Clients* client, int ackType, int msgId); + +typedef struct { + int messageId; + int ackType; +} AckRequest; + /** * List callback function for comparing Message structures by message id @@ -306,12 +314,13 @@ void MQTTProtocol_removePublication(Publications* p) * @param sock the socket on which the packet was received * @return completion code */ -int MQTTProtocol_handlePublishes(void* pack, int sock) +int MQTTProtocol_handlePublishes(void* pack, SOCKET sock) { Publish* publish = (Publish*)pack; Clients* client = NULL; char* clientid = NULL; int rc = TCPSOCKET_COMPLETE; + int socketHasPendingWrites = 0; FUNC_ENTRY; client = (Clients*)(ListFindItem(bstate->clients, &sock, clientSocketCompare)->content); @@ -320,15 +329,21 @@ int MQTTProtocol_handlePublishes(void* pack, int sock) publish->header.bits.retain, publish->payloadlen, min(20, publish->payloadlen), publish->payload); if (publish->header.bits.qos == 0) + { Protocol_processPublication(publish, client, 1); - else if (!Socket_noPendingWrites(sock)) - rc = SOCKET_ERROR; /* queue acks? */ - else if (publish->header.bits.qos == 1) + goto exit; + } + + socketHasPendingWrites = !Socket_noPendingWrites(sock); + + if (publish->header.bits.qos == 1) { - /* send puback before processing the publications because a lot of return publications could fill up the socket buffer */ - rc = MQTTPacket_send_puback(publish->MQTTVersion, publish->msgId, &client->net, client->clientID); - /* if we get a socket error from sending the puback, should we ignore the publication? */ - Protocol_processPublication(publish, client, 1); + Protocol_processPublication(publish, client, 1); + + if (socketHasPendingWrites) + rc = MQTTProtocol_queueAck(client, PUBACK, publish->msgId); + else + rc = MQTTPacket_send_puback(publish->MQTTVersion, publish->msgId, &client->net, client->clientID); } else if (publish->header.bits.qos == 2) { @@ -364,7 +379,7 @@ int MQTTProtocol_handlePublishes(void* pack, int sock) already_received = 1; } else ListAppend(client->inboundMsgs, m, sizeof(Messages) + len); - rc = MQTTPacket_send_pubrec(publish->MQTTVersion, publish->msgId, &client->net, client->clientID); + if (m->MQTTVersion >= MQTTVERSION_5 && already_received == 0) { Publish publish1; @@ -394,6 +409,10 @@ int MQTTProtocol_handlePublishes(void* pack, int sock) } memcpy(m->publish->payload, temp, m->publish->payloadlen); } + if (socketHasPendingWrites) + rc = MQTTProtocol_queueAck(client, PUBREC, publish->msgId); + else + rc = MQTTPacket_send_pubrec(publish->MQTTVersion, publish->msgId, &client->net, client->clientID); publish->topic = NULL; } exit: @@ -408,7 +427,7 @@ int MQTTProtocol_handlePublishes(void* pack, int sock) * @param sock the socket on which the packet was received * @return completion code */ -int MQTTProtocol_handlePubacks(void* pack, int sock) +int MQTTProtocol_handlePubacks(void* pack, SOCKET sock) { Puback* puback = (Puback*)pack; Clients* client = NULL; @@ -454,11 +473,12 @@ int MQTTProtocol_handlePubacks(void* pack, int sock) * @param sock the socket on which the packet was received * @return completion code */ -int MQTTProtocol_handlePubrecs(void* pack, int sock) +int MQTTProtocol_handlePubrecs(void* pack, SOCKET sock) { Pubrec* pubrec = (Pubrec*)pack; Clients* client = NULL; int rc = TCPSOCKET_COMPLETE; + int send_pubrel = 1; /* boolean to send PUBREL or not */ FUNC_ENTRY; client = (Clients*)(ListFindItem(bstate->clients, &sock, clientSocketCompare)->content); @@ -500,15 +520,22 @@ int MQTTProtocol_handlePubrecs(void* pack, int sock) MQTTProperties_free(&m->properties); ListRemove(client->outboundMsgs, m); (++state.msgs_sent); + send_pubrel = 0; /* in MQTT v5, stop the exchange if there is an error reported */ } else { - rc = MQTTPacket_send_pubrel(pubrec->MQTTVersion, pubrec->msgId, 0, &client->net, client->clientID); m->nextMessageType = PUBCOMP; m->lastTouch = MQTTTime_now(); } } } + if (!send_pubrel) + ; /* only don't send ack on MQTT v5 PUBREC error, otherwise send ack under all circumstances because MQTT state can get out of step */ + else if (!Socket_noPendingWrites(sock)) + rc = MQTTProtocol_queueAck(client, PUBREL, pubrec->msgId); + else + rc = MQTTPacket_send_pubrel(pubrec->MQTTVersion, pubrec->msgId, 0, &client->net, client->clientID); + if (pubrec->MQTTVersion >= MQTTVERSION_5) MQTTProperties_free(&pubrec->properties); free(pack); @@ -523,7 +550,7 @@ int MQTTProtocol_handlePubrecs(void* pack, int sock) * @param sock the socket on which the packet was received * @return completion code */ -int MQTTProtocol_handlePubrels(void* pack, int sock) +int MQTTProtocol_handlePubrels(void* pack, SOCKET sock) { Pubrel* pubrel = (Pubrel*)pack; Clients* client = NULL; @@ -538,11 +565,6 @@ int MQTTProtocol_handlePubrels(void* pack, int sock) { if (pubrel->header.bits.dup == 0) Log(TRACE_MIN, 3, NULL, "PUBREL", client->clientID, pubrel->msgId); - else if (!Socket_noPendingWrites(sock)) - rc = SOCKET_ERROR; /* queue acks? */ - else - /* Apparently this is "normal" behaviour, so we don't need to issue a warning */ - rc = MQTTPacket_send_pubcomp(pubrel->MQTTVersion, pubrel->msgId, &client->net, client->clientID); } else { @@ -551,15 +573,12 @@ int MQTTProtocol_handlePubrels(void* pack, int sock) Log(TRACE_MIN, 4, NULL, "PUBREL", client->clientID, pubrel->msgId, m->qos); else if (m->nextMessageType != PUBREL) Log(TRACE_MIN, 5, NULL, "PUBREL", client->clientID, pubrel->msgId); - else if (!Socket_noPendingWrites(sock)) - rc = SOCKET_ERROR; /* queue acks? */ else { Publish publish; memset(&publish, '\0', sizeof(publish)); - /* send pubcomp before processing the publications because a lot of return publications could fill up the socket buffer */ - rc = MQTTPacket_send_pubcomp(pubrel->MQTTVersion, pubrel->msgId, &client->net, client->clientID); + publish.header.bits.qos = m->qos; publish.header.bits.retain = m->retain; publish.msgId = m->msgid; @@ -576,9 +595,9 @@ int MQTTProtocol_handlePubrels(void* pack, int sock) else Protocol_processPublication(&publish, client, 0); /* only for 3.1.1 and lower */ #if !defined(NO_PERSISTENCE) - rc += MQTTPersistence_remove(client, - (m->MQTTVersion >= MQTTVERSION_5) ? PERSISTENCE_V5_PUBLISH_RECEIVED : PERSISTENCE_PUBLISH_RECEIVED, - m->qos, pubrel->msgId); + rc += MQTTPersistence_remove(client, + (m->MQTTVersion >= MQTTVERSION_5) ? PERSISTENCE_V5_PUBLISH_RECEIVED : PERSISTENCE_PUBLISH_RECEIVED, + m->qos, pubrel->msgId); #endif if (m->MQTTVersion >= MQTTVERSION_5) MQTTProperties_free(&m->properties); @@ -588,6 +607,12 @@ int MQTTProtocol_handlePubrels(void* pack, int sock) ++(state.msgs_received); } } + /* Send ack under all circumstances because MQTT state can get out of step - this standard also says to do this */ + if (!Socket_noPendingWrites(sock)) + rc = MQTTProtocol_queueAck(client, PUBCOMP, pubrel->msgId); + else + rc = MQTTPacket_send_pubcomp(pubrel->MQTTVersion, pubrel->msgId, &client->net, client->clientID); + if (pubrel->MQTTVersion >= MQTTVERSION_5) MQTTProperties_free(&pubrel->properties); free(pack); @@ -602,7 +627,7 @@ int MQTTProtocol_handlePubrels(void* pack, int sock) * @param sock the socket on which the packet was received * @return completion code */ -int MQTTProtocol_handlePubcomps(void* pack, int sock) +int MQTTProtocol_handlePubcomps(void* pack, SOCKET sock) { Pubcomp* pubcomp = (Pubcomp*)pack; Clients* client = NULL; @@ -729,9 +754,15 @@ static void MQTTProtocol_retries(START_TIME_TYPE now, Clients* client, int regar FUNC_ENTRY; - if (!regardless && client->retryInterval <= 0) /* 0 or -ive retryInterval turns off retry except on reconnect */ + if (!regardless && client->retryInterval <= 0 && /* 0 or -ive retryInterval turns off retry except on reconnect */ + client->connect_sent == client->connect_count) goto exit; + if (regardless) + client->connect_count = client->outboundMsgs->count; /* remember the number of messages to retry on connect */ + else if (client->connect_sent < client->connect_count) /* continue a connect retry which didn't complete first time around */ + regardless = 1; + while (client && ListNextElement(client->outboundMsgs, &outcurrent) && client->connected && client->good && /* client is connected and has no errors */ Socket_noPendingWrites(client->net.socket)) /* there aren't any previous packets still stacked up on the socket */ @@ -739,6 +770,8 @@ static void MQTTProtocol_retries(START_TIME_TYPE now, Clients* client, int regar Messages* m = (Messages*)(outcurrent->content); if (regardless || MQTTTime_difftime(now, m->lastTouch) > (DIFF_TIME_TYPE)(max(client->retryInterval, 10) * 1000)) { + if (regardless) + ++client->connect_sent; if (m->qos == 1 || (m->qos == 2 && m->nextMessageType == PUBREC)) { Publish publish; @@ -783,7 +816,6 @@ static void MQTTProtocol_retries(START_TIME_TYPE now, Clients* client, int regar else m->lastTouch = MQTTTime_now(); } - /* break; why not do all retries at once? */ } } exit: @@ -791,6 +823,35 @@ static void MQTTProtocol_retries(START_TIME_TYPE now, Clients* client, int regar } +/** + * Queue an ack message. This is used when the socket is full (e.g. SSL_ERROR_WANT_WRITE). + * To be completed/cleared when the socket is no longer full + * @param client the client that received the published message + * @param ackType the type of ack to send + * @param msgId the msg id of the message we are acknowledging + * @return the completion code + */ +int MQTTProtocol_queueAck(Clients* client, int ackType, int msgId) +{ + int rc = 0; + AckRequest* ackReq = NULL; + + FUNC_ENTRY; + ackReq = malloc(sizeof(AckRequest)); + if (!ackReq) + rc = PAHO_MEMORY_ERROR; + else + { + ackReq->messageId = msgId; + ackReq->ackType = ackType; + ListAppend(client->outboundQueue, ackReq, sizeof(AckRequest)); + } + + FUNC_EXIT_RC(rc); + return rc; +} + + /** * MQTT retry protocol and socket pending writes processing. * @param now current time @@ -835,6 +896,7 @@ void MQTTProtocol_freeClient(Clients* client) MQTTProtocol_freeMessageList(client->outboundMsgs); MQTTProtocol_freeMessageList(client->inboundMsgs); ListFree(client->messageQueue); + ListFree(client->outboundQueue); free(client->clientID); client->clientID = NULL; if (client->will) @@ -917,6 +979,51 @@ void MQTTProtocol_freeMessageList(List* msgList) } +/** + * Callback that is invoked when the socket is available for writing. + * This is the last attempt made to acknowledge a message. Failures that + * occur here are ignored. + * @param socket the socket that is available for writing + */ +void MQTTProtocol_writeAvailable(SOCKET socket) +{ + Clients* client = NULL; + ListElement* current = NULL; + int rc = 0; + + FUNC_ENTRY; + + client = (Clients*)(ListFindItem(bstate->clients, &socket, clientSocketCompare)->content); + + current = NULL; + while (ListNextElement(client->outboundQueue, ¤t) && rc == 0) + { + AckRequest* ackReq = (AckRequest*)(current->content); + + switch (ackReq->ackType) + { + case PUBACK: + rc = MQTTPacket_send_puback(client->MQTTVersion, ackReq->messageId, &client->net, client->clientID); + break; + case PUBREC: + rc = MQTTPacket_send_pubrec(client->MQTTVersion, ackReq->messageId, &client->net, client->clientID); + break; + case PUBREL: + rc = MQTTPacket_send_pubrel(client->MQTTVersion, ackReq->messageId, 0, &client->net, client->clientID); + break; + case PUBCOMP: + rc = MQTTPacket_send_pubcomp(client->MQTTVersion, ackReq->messageId, &client->net, client->clientID); + break; + default: + Log(LOG_ERROR, -1, "unknown ACK type %d, dropping msg", ackReq->ackType); + break; + } + } + + ListEmpty(client->outboundQueue); + FUNC_EXIT_RC(rc); +} + /** * Copy no more than dest_size -1 characters from the string pointed to by src to the array pointed to by dest. * The destination string will always be null-terminated. diff --git a/src/MQTTProtocolClient.h b/src/MQTTProtocolClient.h index 2d077afa6..b9b22a4eb 100644 --- a/src/MQTTProtocolClient.h +++ b/src/MQTTProtocolClient.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2020 IBM Corp. + * Copyright (c) 2009, 2022 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -39,11 +39,11 @@ int MQTTProtocol_assignMsgId(Clients* client); void MQTTProtocol_removePublication(Publications* p); void Protocol_processPublication(Publish* publish, Clients* client, int allocatePayload); -int MQTTProtocol_handlePublishes(void* pack, int sock); -int MQTTProtocol_handlePubacks(void* pack, int sock); -int MQTTProtocol_handlePubrecs(void* pack, int sock); -int MQTTProtocol_handlePubrels(void* pack, int sock); -int MQTTProtocol_handlePubcomps(void* pack, int sock); +int MQTTProtocol_handlePublishes(void* pack, SOCKET sock); +int MQTTProtocol_handlePubacks(void* pack, SOCKET sock); +int MQTTProtocol_handlePubrecs(void* pack, SOCKET sock); +int MQTTProtocol_handlePubrels(void* pack, SOCKET sock); +int MQTTProtocol_handlePubcomps(void* pack, SOCKET sock); void MQTTProtocol_closeSession(Clients* c, int sendwill); void MQTTProtocol_keepalive(START_TIME_TYPE); @@ -55,6 +55,8 @@ void MQTTProtocol_freeMessageList(List* msgList); char* MQTTStrncpy(char *dest, const char* src, size_t num); char* MQTTStrdup(const char* src); +void MQTTProtocol_writeAvailable(SOCKET socket); + //#define MQTTStrdup(src) MQTTStrncpy(malloc(strlen(src)+1), src, strlen(src)+1) #endif diff --git a/src/MQTTProtocolOut.c b/src/MQTTProtocolOut.c index 06c277dfb..c588819ed 100644 --- a/src/MQTTProtocolOut.c +++ b/src/MQTTProtocolOut.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2020 IBM Corp. + * Copyright (c) 2009, 2022 IBM Corp., Ian Craggs and others * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -21,6 +21,7 @@ * Ian Craggs - fix for issue #164 * Ian Craggs - fix for issue #179 * Ian Craggs - MQTT 5.0 support + * Sven Gambel - add generic proxy support *******************************************************************************/ /** @@ -38,6 +39,7 @@ #include "StackTrace.h" #include "Heap.h" #include "WebSocket.h" +#include "Proxy.h" #include "Base64.h" extern ClientStates* bstate; @@ -53,11 +55,14 @@ extern ClientStates* bstate; */ size_t MQTTProtocol_addressPort(const char* uri, int* port, const char **topic, int default_port) { - char* colon_pos = strrchr(uri, ':'); /* reverse find to allow for ':' in IPv6 addresses */ char* buf = (char*)uri; + char* colon_pos; size_t len; + char* topic_pos; FUNC_ENTRY; + colon_pos = strrchr(uri, ':'); /* reverse find to allow for ':' in IPv6 addresses */ + if (uri[0] == '[') { /* ip v6 */ if (colon_pos < strrchr(uri, ']')) @@ -75,13 +80,17 @@ size_t MQTTProtocol_addressPort(const char* uri, int* port, const char **topic, *port = default_port; } - /* try and find topic portion */ - if ( topic ) + /* find any topic portion */ + topic_pos = (char*)uri; + if (colon_pos) + topic_pos = colon_pos; + topic_pos = strchr(topic_pos, '/'); + if (topic_pos) { - const char* addr_start = uri; - if ( colon_pos ) - addr_start = colon_pos; - *topic = strchr( addr_start, '/' ); + if (topic) + *topic = topic_pos; + if (!colon_pos) + len = topic_pos - uri; } if (buf[len - 1] == ']') @@ -247,11 +256,11 @@ int MQTTProtocol_connect(const char* ip_address, Clients* aClient, int websocket Log(TRACE_PROTOCOL, -1, "Setting https proxy auth to %s", aClient->net.https_proxy_auth); } - if (!ssl && websocket && aClient->net.http_proxy) { + if (!ssl && aClient->net.http_proxy) { #else - if (websocket && aClient->net.http_proxy) { + if (aClient->net.http_proxy) { #endif - addr_len = MQTTProtocol_addressPort(aClient->net.http_proxy, &port, NULL, WS_DEFAULT_PORT); + addr_len = MQTTProtocol_addressPort(aClient->net.http_proxy, &port, NULL, PROXY_DEFAULT_PORT); #if defined(__GNUC__) && defined(__linux__) if (timeout < 0) rc = -1; @@ -262,8 +271,8 @@ int MQTTProtocol_connect(const char* ip_address, Clients* aClient, int websocket #endif } #if defined(OPENSSL) - else if (ssl && websocket && aClient->net.https_proxy) { - addr_len = MQTTProtocol_addressPort(aClient->net.https_proxy, &port, NULL, WS_DEFAULT_PORT); + else if (ssl && aClient->net.https_proxy) { + addr_len = MQTTProtocol_addressPort(aClient->net.https_proxy, &port, NULL, PROXY_DEFAULT_PORT); #if defined(__GNUC__) && defined(__linux__) if (timeout < 0) rc = -1; @@ -276,9 +285,11 @@ int MQTTProtocol_connect(const char* ip_address, Clients* aClient, int websocket #endif else { #if defined(OPENSSL) - addr_len = MQTTProtocol_addressPort(ip_address, &port, NULL, ssl ? SECURE_MQTT_DEFAULT_PORT : MQTT_DEFAULT_PORT); + addr_len = MQTTProtocol_addressPort(ip_address, &port, NULL, ssl ? + (websocket ? WSS_DEFAULT_PORT : SECURE_MQTT_DEFAULT_PORT) : + (websocket ? WS_DEFAULT_PORT : MQTT_DEFAULT_PORT) ); #else - addr_len = MQTTProtocol_addressPort(ip_address, &port, NULL, MQTT_DEFAULT_PORT); + addr_len = MQTTProtocol_addressPort(ip_address, &port, NULL, websocket ? WS_DEFAULT_PORT : MQTT_DEFAULT_PORT); #endif #if defined(__GNUC__) && defined(__linux__) if (timeout < 0) @@ -296,9 +307,9 @@ int MQTTProtocol_connect(const char* ip_address, Clients* aClient, int websocket #if defined(OPENSSL) if (ssl) { - if (websocket && aClient->net.https_proxy) { + if (aClient->net.https_proxy) { aClient->connect_state = PROXY_CONNECT_IN_PROGRESS; - rc = WebSocket_proxy_connect( &aClient->net, 1, ip_address); + rc = Proxy_connect( &aClient->net, 1, ip_address); } if (rc == 0 && SSLSocket_setSocketForSSL(&aClient->net, aClient->sslopts, ip_address, addr_len) == 1) { @@ -313,16 +324,19 @@ int MQTTProtocol_connect(const char* ip_address, Clients* aClient, int websocket else rc = SOCKET_ERROR; } - else if (websocket && aClient->net.http_proxy) { + else if (aClient->net.http_proxy) { #else - if (websocket && aClient->net.http_proxy) { + if (aClient->net.http_proxy) { #endif aClient->connect_state = PROXY_CONNECT_IN_PROGRESS; - rc = WebSocket_proxy_connect( &aClient->net, 0, ip_address); + rc = Proxy_connect( &aClient->net, 0, ip_address); } if ( websocket ) { - rc = WebSocket_connect( &aClient->net, ip_address ); +#if defined(OPENSSL) + rc = WebSocket_connect(&aClient->net, ssl, ip_address); +#endif + rc = WebSocket_connect(&aClient->net, 0, ip_address); if ( rc == TCPSOCKET_INTERRUPTED ) aClient->connect_state = WEBSOCKET_IN_PROGRESS; /* Websocket connect called - wait for completion */ } @@ -348,7 +362,7 @@ int MQTTProtocol_connect(const char* ip_address, Clients* aClient, int websocket * @param sock the socket on which the packet was received * @return completion code */ -int MQTTProtocol_handlePingresps(void* pack, int sock) +int MQTTProtocol_handlePingresps(void* pack, SOCKET sock) { Clients* client = NULL; int rc = TCPSOCKET_COMPLETE; @@ -389,7 +403,7 @@ int MQTTProtocol_subscribe(Clients* client, List* topics, List* qoss, int msgID, * @param sock the socket on which the packet was received * @return completion code */ -int MQTTProtocol_handleSubacks(void* pack, int sock) +int MQTTProtocol_handleSubacks(void* pack, SOCKET sock) { Suback* suback = (Suback*)pack; Clients* client = NULL; @@ -427,7 +441,7 @@ int MQTTProtocol_unsubscribe(Clients* client, List* topics, int msgID, MQTTPrope * @param sock the socket on which the packet was received * @return completion code */ -int MQTTProtocol_handleUnsubacks(void* pack, int sock) +int MQTTProtocol_handleUnsubacks(void* pack, SOCKET sock) { Unsuback* unsuback = (Unsuback*)pack; Clients* client = NULL; @@ -441,3 +455,24 @@ int MQTTProtocol_handleUnsubacks(void* pack, int sock) return rc; } + +/** + * Process an incoming disconnect packet for a socket + * @param pack pointer to the disconnect packet + * @param sock the socket on which the packet was received + * @return completion code + */ +int MQTTProtocol_handleDisconnects(void* pack, SOCKET sock) +{ + Ack* disconnect = (Ack*)pack; + Clients* client = NULL; + int rc = TCPSOCKET_COMPLETE; + + FUNC_ENTRY; + client = (Clients*)(ListFindItem(bstate->clients, &sock, clientSocketCompare)->content); + Log(LOG_PROTOCOL, 30, NULL, sock, client->clientID, disconnect->rc); + MQTTPacket_freeAck(disconnect); + FUNC_EXIT_RC(rc); + return rc; +} + diff --git a/src/MQTTProtocolOut.h b/src/MQTTProtocolOut.h index 6a5015574..adc95abfd 100644 --- a/src/MQTTProtocolOut.h +++ b/src/MQTTProtocolOut.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2020 IBM Corp. + * Copyright (c) 2009, 2022 IBM Corp., Ian Craggs, and others * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -16,6 +16,7 @@ * Ian Craggs - MQTT 3.1.1 support * Ian Craggs - SNI support * Ian Craggs - MQTT 5.0 support + * Sven Gambel - add generic proxy support *******************************************************************************/ #if !defined(MQTTPROTOCOLOUT_H) @@ -32,6 +33,8 @@ #define MQTT_DEFAULT_PORT 1883 #define SECURE_MQTT_DEFAULT_PORT 8883 #define WS_DEFAULT_PORT 80 +#define WSS_DEFAULT_PORT 443 +#define PROXY_DEFAULT_PORT 8080 size_t MQTTProtocol_addressPort(const char* uri, int* port, const char **topic, int default_port); void MQTTProtocol_reconnect(const char* ip_address, Clients* client); @@ -52,11 +55,12 @@ int MQTTProtocol_connect(const char* ip_address, Clients* acClients, int websock MQTTProperties* connectProperties, MQTTProperties* willProperties); #endif #endif -int MQTTProtocol_handlePingresps(void* pack, int sock); +int MQTTProtocol_handlePingresps(void* pack, SOCKET sock); int MQTTProtocol_subscribe(Clients* client, List* topics, List* qoss, int msgID, MQTTSubscribe_options* opts, MQTTProperties* props); -int MQTTProtocol_handleSubacks(void* pack, int sock); +int MQTTProtocol_handleSubacks(void* pack, SOCKET sock); int MQTTProtocol_unsubscribe(Clients* client, List* topics, int msgID, MQTTProperties* props); -int MQTTProtocol_handleUnsubacks(void* pack, int sock); +int MQTTProtocol_handleUnsubacks(void* pack, SOCKET sock); +int MQTTProtocol_handleDisconnects(void* pack, SOCKET sock); #endif diff --git a/src/Messages.c b/src/Messages.c index 299612d90..1b47fadf9 100644 --- a/src/Messages.c +++ b/src/Messages.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2020 IBM Corp. + * Copyright (c) 2009, 2021 IBM Corp., Ian Craggs * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -66,6 +66,7 @@ static const char *protocol_message_list[] = "%d %s -> PUBLISH qos: 0 retained: %d rc: %d payload len(%d): %.*s", /* 27 */ "%d %s -> DISCONNECT (%d)", /* 28 */ "Socket error for client identifier %s, socket %d, peer address %s; ending connection", /* 29 */ + "%d %s <- DISCONNECT (%d)", /* 30 */ }; static const char *trace_message_list[] = diff --git a/src/Proxy.c b/src/Proxy.c new file mode 100644 index 000000000..728cb8fcd --- /dev/null +++ b/src/Proxy.c @@ -0,0 +1,154 @@ +/******************************************************************************* + * Copyright (c) 2009, 2021 Diehl Metering. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Keith Holman - initial implementation and documentation + * Sven Gambel - move WebSocket proxy support to generic proxy support + *******************************************************************************/ + +#include +#include +// for timeout process in Proxy_connect() +#include +#if defined(_WIN32) || defined(_WIN64) + #include + #if defined(_MSC_VER) && _MSC_VER < 1900 + #define snprintf _snprintf + #endif +#else + #include +#endif + +#include "Log.h" +#include "MQTTProtocolOut.h" +#include "StackTrace.h" +#include "Heap.h" + +#if defined(OPENSSL) +#include "SSLSocket.h" +#include +#endif /* defined(OPENSSL) */ +#include "Socket.h" + +/** + * Notify the IP address and port of the endpoint to proxy, and wait connection to endpoint. + * + * @param[in] net network connection to proxy. + * @param[in] ssl enable ssl. + * @param[in] hostname hostname of endpoint. + * + * @retval SOCKET_ERROR failed to network connection + * @retval 0 connection to endpoint + * + */ +int Proxy_connect(networkHandles *net, int ssl, const char *hostname) +{ + int port, i, rc = 0, buf_len=0; + char *buf = NULL; + size_t hostname_len, actual_len = 0; + time_t current, timeout; + PacketBuffers nulbufs = {0, NULL, NULL, NULL, {0, 0, 0, 0}}; + + FUNC_ENTRY; + hostname_len = MQTTProtocol_addressPort(hostname, &port, NULL, PROXY_DEFAULT_PORT); + for ( i = 0; i < 2; ++i ) { +#if defined(OPENSSL) + if(ssl) { + if (net->https_proxy_auth) { + buf_len = snprintf( buf, (size_t)buf_len, "CONNECT %.*s:%d HTTP/1.1\r\n" + "Host: %.*s\r\n" + "Proxy-authorization: Basic %s\r\n" + "\r\n", + (int)hostname_len, hostname, port, + (int)hostname_len, hostname, net->https_proxy_auth); + } + else { + buf_len = snprintf( buf, (size_t)buf_len, "CONNECT %.*s:%d HTTP/1.1\r\n" + "Host: %.*s\r\n" + "\r\n", + (int)hostname_len, hostname, port, + (int)hostname_len, hostname); + } + } + else { +#endif + if (net->http_proxy_auth) { + buf_len = snprintf( buf, (size_t)buf_len, "CONNECT %.*s:%d HTTP/1.1\r\n" + "Host: %.*s\r\n" + "Proxy-authorization: Basic %s\r\n" + "\r\n", + (int)hostname_len, hostname, port, + (int)hostname_len, hostname, net->http_proxy_auth); + } + else { + buf_len = snprintf( buf, (size_t)buf_len, "CONNECT %.*s:%d HTTP/1.1\r\n" + "Host: %.*s\r\n" + "\r\n", + (int)hostname_len, hostname, port, + (int)hostname_len, hostname); + } +#if defined(OPENSSL) + } +#endif + if ( i==0 && buf_len > 0 ) { + ++buf_len; + if ((buf = malloc( buf_len )) == NULL) + { + rc = PAHO_MEMORY_ERROR; + goto exit; + } + + } + } + Log(TRACE_PROTOCOL, -1, "Proxy_connect: \"%s\"", buf); + + Socket_putdatas(net->socket, buf, buf_len, nulbufs); + free(buf); + buf = NULL; + + time(&timeout); + timeout += (time_t)10; + + while(1) { + buf = Socket_getdata(net->socket, (size_t)12, &actual_len, &rc); + if(actual_len) { + if ( (strncmp( buf, "HTTP/1.0 200", 12 ) != 0) && (strncmp( buf, "HTTP/1.1 200", 12 ) != 0) ) + rc = SOCKET_ERROR; + break; + } + else { + time(¤t); + if(current > timeout) { + rc = SOCKET_ERROR; + break; + } +#if defined(_WIN32) || defined(_WIN64) + Sleep(250); +#else + usleep(250000); +#endif + } + } + + /* flush the SocketBuffer */ + actual_len = 1; + while (actual_len) + { + int rc1; + + buf = Socket_getdata(net->socket, (size_t)1, &actual_len, &rc1); + } + +exit: + FUNC_EXIT_RC(rc); + return rc; +} diff --git a/src/Proxy.h b/src/Proxy.h new file mode 100644 index 000000000..67cf5df16 --- /dev/null +++ b/src/Proxy.h @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright (c) 2009, 2021 Diehl Metering. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Sven Gambel - move WebSocket proxy support to generic proxy support + *******************************************************************************/ + +#if !defined(PROXY_H) +#define PROXY_H + +#include "Clients.h" + +/* Notify the IP address and port of the endpoint to proxy, and wait connection to endpoint */ +int Proxy_connect(networkHandles *net, int ssl, const char *hostname ); + +#endif /* PROXY_H */ diff --git a/src/SSLSocket.c b/src/SSLSocket.c index a303d370a..fd80c7276 100644 --- a/src/SSLSocket.c +++ b/src/SSLSocket.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2020 IBM Corp. + * Copyright (c) 2009, 2022 IBM Corp., Ian Craggs * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -46,7 +46,7 @@ extern Sockets mod_s; -static int SSLSocket_error(char* aString, SSL* ssl, int sock, int rc, int (*cb)(const char *str, size_t len, void *u), void* u); +static int SSLSocket_error(char* aString, SSL* ssl, SOCKET sock, int rc, int (*cb)(const char *str, size_t len, void *u), void* u); char* SSL_get_verify_result_string(int rc); void SSL_CTX_info_callback(const SSL* ssl, int where, int ret); char* SSLSocket_get_version_string(int version); @@ -69,7 +69,7 @@ extern unsigned long SSLThread_id(void); extern void SSLLocks_callback(int mode, int n, const char *file, int line); int SSLSocket_createContext(networkHandles* net, MQTTClient_SSLOptions* opts); void SSLSocket_destroyContext(networkHandles* net); -void SSLSocket_addPendingRead(int sock); +void SSLSocket_addPendingRead(SOCKET sock); /* 1 ~ we are responsible for initializing openssl; 0 ~ openssl init is done externally */ static int handle_openssl_init = 1; @@ -94,7 +94,7 @@ static int tls_ex_index_ssl_opts; * @param u context to be passed as second argument to ERR_print_errors_cb * @return the specific TCP error code */ -static int SSLSocket_error(char* aString, SSL* ssl, int sock, int rc, int (*cb)(const char *str, size_t len, void *u), void* u) +static int SSLSocket_error(char* aString, SSL* ssl, SOCKET sock, int rc, int (*cb)(const char *str, size_t len, void *u), void* u) { int error; @@ -593,6 +593,10 @@ int SSLSocket_createContext(networkHandles* net, MQTTClient_SSLOptions* opts) } } +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) + SSL_CTX_set_security_level(net->ctx, 1); +#endif + if (opts->keyStore) { if ((rc = SSL_CTX_use_certificate_chain_file(net->ctx, opts->keyStore)) != 1) @@ -728,7 +732,7 @@ int SSLSocket_setSocketForSSL(networkHandles* net, MQTTClient_SSLOptions* opts, break; Log(TRACE_PROTOCOL, 1, "SSL cipher available: %d:%s", i, cipher); } - if ((rc = SSL_set_fd(net->ssl, net->socket)) != 1) { + if ((rc = (int)SSL_set_fd(net->ssl, (int)net->socket)) != 1) { if (opts->struct_version >= 3) SSLSocket_error("SSL_set_fd", net->ssl, net->socket, rc, opts->ssl_error_cb, opts->ssl_error_context); else @@ -757,7 +761,7 @@ int SSLSocket_setSocketForSSL(networkHandles* net, MQTTClient_SSLOptions* opts, /* * Return value: 1 - success, TCPSOCKET_INTERRUPTED - try again, anything else is failure */ -int SSLSocket_connect(SSL* ssl, int sock, const char* hostname, int verify, int (*cb)(const char *str, size_t len, void *u), void* u) +int SSLSocket_connect(SSL* ssl, SOCKET sock, const char* hostname, int verify, int (*cb)(const char *str, size_t len, void *u), void* u) { int rc = 0; @@ -831,7 +835,7 @@ int SSLSocket_connect(SSL* ssl, int sock, const char* hostname, int verify, int * @param c the character read, returned * @return completion code */ -int SSLSocket_getch(SSL* ssl, int socket, char* c) +int SSLSocket_getch(SSL* ssl, SOCKET socket, char* c) { int rc = SOCKET_ERROR; @@ -871,7 +875,7 @@ int SSLSocket_getch(SSL* ssl, int socket, char* c) * @param actual_len the actual number of bytes read * @return completion code */ -char *SSLSocket_getdata(SSL* ssl, int socket, size_t bytes, size_t* actual_len, int* rc) +char *SSLSocket_getdata(SSL* ssl, SOCKET socket, size_t bytes, size_t* actual_len, int* rc) { char* buf; @@ -884,23 +888,26 @@ char *SSLSocket_getdata(SSL* ssl, int socket, size_t bytes, size_t* actual_len, buf = SocketBuffer_getQueuedData(socket, bytes, actual_len); - ERR_clear_error(); - if ((*rc = SSL_read(ssl, buf + (*actual_len), (int)(bytes - (*actual_len)))) < 0) + if (*actual_len != bytes) { - *rc = SSLSocket_error("SSL_read - getdata", ssl, socket, *rc, NULL, NULL); - if (*rc != SSL_ERROR_WANT_READ && *rc != SSL_ERROR_WANT_WRITE) + ERR_clear_error(); + if ((*rc = SSL_read(ssl, buf + (*actual_len), (int)(bytes - (*actual_len)))) < 0) + { + *rc = SSLSocket_error("SSL_read - getdata", ssl, socket, *rc, NULL, NULL); + if (*rc != SSL_ERROR_WANT_READ && *rc != SSL_ERROR_WANT_WRITE) + { + buf = NULL; + goto exit; + } + } + else if (*rc == 0) /* rc 0 means the other end closed the socket */ { buf = NULL; goto exit; } + else + *actual_len += *rc; } - else if (*rc == 0) /* rc 0 means the other end closed the socket */ - { - buf = NULL; - goto exit; - } - else - *actual_len += *rc; if (*actual_len == bytes) { @@ -956,7 +963,7 @@ int SSLSocket_close(networkHandles* net) /* No SSL_writev() provided by OpenSSL. Boo. */ -int SSLSocket_putdatas(SSL* ssl, int socket, char* buf0, size_t buf0len, PacketBuffers bufs) +int SSLSocket_putdatas(SSL* ssl, SOCKET socket, char* buf0, size_t buf0len, PacketBuffers bufs) { int rc = 0; int i; @@ -996,7 +1003,7 @@ int SSLSocket_putdatas(SSL* ssl, int socket, char* buf0, size_t buf0len, PacketB if (sslerror == SSL_ERROR_WANT_WRITE) { - int* sockmem = (int*)malloc(sizeof(int)); + SOCKET* sockmem = (SOCKET*)malloc(sizeof(SOCKET)); int free = 1; if (!sockmem) @@ -1010,7 +1017,9 @@ int SSLSocket_putdatas(SSL* ssl, int socket, char* buf0, size_t buf0len, PacketB SocketBuffer_pendingWrite(socket, ssl, 1, &iovec, &free, iovec.iov_len, 0); *sockmem = socket; ListAppend(mod_s.write_pending, sockmem, sizeof(int)); +#if defined(USE_SELECT) FD_SET(socket, &(mod_s.pending_wset)); +#endif rc = TCPSOCKET_INTERRUPTED; } else @@ -1039,12 +1048,12 @@ int SSLSocket_putdatas(SSL* ssl, int socket, char* buf0, size_t buf0len, PacketB } -void SSLSocket_addPendingRead(int sock) +void SSLSocket_addPendingRead(SOCKET sock) { FUNC_ENTRY; if (ListFindItem(&pending_reads, &sock, intcompare) == NULL) /* make sure we don't add the same socket twice */ { - int* psock = (int*)malloc(sizeof(sock)); + SOCKET* psock = (SOCKET*)malloc(sizeof(SOCKET)); if (psock) { *psock = sock; @@ -1058,9 +1067,9 @@ void SSLSocket_addPendingRead(int sock) } -int SSLSocket_getPendingRead(void) +SOCKET SSLSocket_getPendingRead(void) { - int sock = -1; + SOCKET sock = -1; if (pending_reads.count > 0) { @@ -1093,4 +1102,15 @@ int SSLSocket_continueWrite(pending_writes* pw) FUNC_EXIT_RC(rc); return rc; } + + +int SSLSocket_abortWrite(pending_writes* pw) +{ + int rc = 0; + + FUNC_ENTRY; + free(pw->iovecs[0].iov_base); + FUNC_EXIT_RC(rc); + return rc; +} #endif diff --git a/src/SSLSocket.h b/src/SSLSocket.h index 86273c8e2..3fcf0c1d1 100644 --- a/src/SSLSocket.h +++ b/src/SSLSocket.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2020 IBM Corp. + * Copyright (c) 2009, 2022 IBM Corp., Ian Craggs * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -39,14 +39,15 @@ int SSLSocket_initialize(void); void SSLSocket_terminate(void); int SSLSocket_setSocketForSSL(networkHandles* net, MQTTClient_SSLOptions* opts, const char* hostname, size_t hostname_len); -int SSLSocket_getch(SSL* ssl, int socket, char* c); -char *SSLSocket_getdata(SSL* ssl, int socket, size_t bytes, size_t* actual_len, int* rc); +int SSLSocket_getch(SSL* ssl, SOCKET socket, char* c); +char *SSLSocket_getdata(SSL* ssl, SOCKET socket, size_t bytes, size_t* actual_len, int* rc); int SSLSocket_close(networkHandles* net); -int SSLSocket_putdatas(SSL* ssl, int socket, char* buf0, size_t buf0len, PacketBuffers bufs); -int SSLSocket_connect(SSL* ssl, int sock, const char* hostname, int verify, int (*cb)(const char *str, size_t len, void *u), void* u); +int SSLSocket_putdatas(SSL* ssl, SOCKET socket, char* buf0, size_t buf0len, PacketBuffers bufs); +int SSLSocket_connect(SSL* ssl, SOCKET sock, const char* hostname, int verify, int (*cb)(const char *str, size_t len, void *u), void* u); -int SSLSocket_getPendingRead(void); +SOCKET SSLSocket_getPendingRead(void); int SSLSocket_continueWrite(pending_writes* pw); +int SSLSocket_abortWrite(pending_writes* pw); #endif diff --git a/src/Socket.c b/src/Socket.c index 6566883de..bcff03d49 100644 --- a/src/Socket.c +++ b/src/Socket.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2020 IBM Corp. and others + * Copyright (c) 2009, 2022 IBM Corp., Ian Craggs and others * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -44,16 +44,21 @@ #include "Heap.h" -int Socket_setnonblocking(int sock); -int Socket_error(char* aString, int sock); -int Socket_addSocket(int newSd); +#if defined(USE_SELECT) int isReady(int socket, fd_set* read_set, fd_set* write_set); -int Socket_writev(int socket, iobuf* iovecs, int count, unsigned long* bytes); -int Socket_close_only(int socket); -int Socket_continueWrite(int socket); -int Socket_continueWrites(fd_set* pwset, int* socket); -char* Socket_getaddrname(struct sockaddr* sa, int sock); -int Socket_abortWrite(int socket); +int Socket_continueWrites(fd_set* pwset, int* socket, mutex_type mutex); +#else +int isReady(int index); +int Socket_continueWrites(SOCKET* socket, mutex_type mutex); +#endif +int Socket_setnonblocking(SOCKET sock); +int Socket_error(char* aString, SOCKET sock); +int Socket_addSocket(SOCKET newSd); +int Socket_writev(SOCKET socket, iobuf* iovecs, int count, unsigned long* bytes); +int Socket_close_only(SOCKET socket); +int Socket_continueWrite(SOCKET socket); +char* Socket_getaddrname(struct sockaddr* sa, SOCKET sock); +int Socket_abortWrite(SOCKET socket); #if defined(_WIN32) || defined(_WIN64) #define iov_len len @@ -65,14 +70,16 @@ int Socket_abortWrite(int socket); * Structure to hold all socket data for this module */ Sockets mod_s; +#if defined(USE_SELECT) static fd_set wset; +#endif /** * Set a socket non-blocking, OS independently * @param sock the socket to set non-blocking * @return TCP call error code */ -int Socket_setnonblocking(int sock) +int Socket_setnonblocking(SOCKET sock) { int rc; #if defined(_WIN32) || defined(_WIN64) @@ -99,7 +106,7 @@ int Socket_setnonblocking(int sock) * @param sock the socket on which the error occurred * @return the specific TCP error code */ -int Socket_error(char* aString, int sock) +int Socket_error(char* aString, SOCKET sock) { int err; @@ -134,14 +141,24 @@ void Socket_outInitialize(void) #endif SocketBuffer_initialize(); - mod_s.clientsds = ListInitialize(); mod_s.connect_pending = ListInitialize(); mod_s.write_pending = ListInitialize(); + +#if defined(USE_SELECT) + mod_s.clientsds = ListInitialize(); mod_s.cur_clientsds = NULL; FD_ZERO(&(mod_s.rset)); /* Initialize the descriptor set */ FD_ZERO(&(mod_s.pending_wset)); mod_s.maxfdp1 = 0; memcpy((void*)&(mod_s.rset_saved), (void*)&(mod_s.rset), sizeof(mod_s.rset_saved)); +#else + mod_s.nfds = 0; + mod_s.fds = NULL; + + mod_s.saved.cur_fd = -1; + mod_s.saved.fds = NULL; + mod_s.saved.nfds = 0; +#endif FUNC_EXIT; } @@ -154,7 +171,14 @@ void Socket_outTerminate(void) FUNC_ENTRY; ListFree(mod_s.connect_pending); ListFree(mod_s.write_pending); +#if defined(USE_SELECT) ListFree(mod_s.clientsds); +#else + if (mod_s.fds) + free(mod_s.fds); + if (mod_s.saved.fds) + free(mod_s.saved.fds); +#endif SocketBuffer_terminate(); #if defined(_WIN32) || defined(_WIN64) WSACleanup(); @@ -163,6 +187,7 @@ void Socket_outTerminate(void) } +#if defined(USE_SELECT) /** * Add a socket to the list of socket to check with select * @param newSd the new socket to add @@ -209,8 +234,67 @@ int Socket_addSocket(int newSd) FUNC_EXIT_RC(rc); return rc; } +#else +static int cmpfds(const void *p1, const void *p2) +{ + SOCKET key1 = ((struct pollfd*)p1)->fd; + SOCKET key2 = ((struct pollfd*)p2)->fd; + + return (key1 == key2) ? 0 : ((key1 < key2) ? -1 : 1); +} + + +static int cmpsockfds(const void *p1, const void *p2) +{ + int key1 = *(int*)p1; + SOCKET key2 = ((struct pollfd*)p2)->fd; + + return (key1 == key2) ? 0 : ((key1 < key2) ? -1 : 1); +} +/** + * Add a socket to the list of socket to check with select + * @param newSd the new socket to add + */ +int Socket_addSocket(SOCKET newSd) +{ + int rc = 0; + + FUNC_ENTRY; + mod_s.nfds++; + if (mod_s.fds) + mod_s.fds = realloc(mod_s.fds, mod_s.nfds * sizeof(mod_s.fds[0])); + else + mod_s.fds = malloc(mod_s.nfds * sizeof(mod_s.fds[0])); + if (!mod_s.fds) + { + rc = PAHO_MEMORY_ERROR; + goto exit; + } + + mod_s.fds[mod_s.nfds - 1].fd = newSd; +#if defined(_WIN32) || defined(_WIN64) + mod_s.fds[mod_s.nfds - 1].events = POLLIN | POLLOUT; +#else + mod_s.fds[mod_s.nfds - 1].events = POLLIN | POLLOUT | POLLNVAL; +#endif + + /* sort the poll fds array by socket number */ + qsort(mod_s.fds, (size_t)mod_s.nfds, sizeof(mod_s.fds[0]), cmpfds); + + rc = Socket_setnonblocking(newSd); + if (rc == SOCKET_ERROR) + Log(LOG_ERROR, -1, "addSocket: setnonblocking"); + +exit: + FUNC_EXIT_RC(rc); + return rc; +} +#endif + + +#if defined(USE_SELECT) /** * Don't accept work from a client unless it is accepting work back, i.e. its socket is writeable * this seems like a reasonable form of flow control, and practically, seems to work. @@ -231,33 +315,59 @@ int isReady(int socket, fd_set* read_set, fd_set* write_set) FUNC_EXIT_RC(rc); return rc; } +#else +/** + * Don't accept work from a client unless it is accepting work back, i.e. its socket is writeable + * this seems like a reasonable form of flow control, and practically, seems to work. + * @param index the socket index to check + * @return boolean - is the socket ready to go? + */ +int isReady(int index) +{ + int rc = 1; + SOCKET* socket = &mod_s.saved.fds[index].fd; + + FUNC_ENTRY; + + if ((mod_s.saved.fds[index].revents & POLLHUP) || (mod_s.saved.fds[index].revents & POLLNVAL)) + ; /* signal work to be done if there is an error on the socket */ + else if (ListFindItem(mod_s.connect_pending, socket, intcompare) && + (mod_s.saved.fds[index].revents & POLLOUT)) + ListRemoveItem(mod_s.connect_pending, socket, intcompare); + else + rc = (mod_s.saved.fds[index].revents & POLLIN) && + (mod_s.saved.fds[index].revents & POLLOUT) && + Socket_noPendingWrites(*socket); + FUNC_EXIT_RC(rc); + return rc; +} +#endif +#if defined(USE_SELECT) /** * Returns the next socket ready for communications as indicated by select * @param more_work flag to indicate more work is waiting, and thus a timeout value of 0 should * be used for the select - * @param tp the timeout to be used for the select, unless overridden + * @param timeout the timeout to be used for the select, unless overridden * @param rc a value other than 0 indicates an error of the returned socket * @return the socket next ready, or 0 if none is ready */ -int Socket_getReadySocket(int more_work, struct timeval *tp, mutex_type mutex, int* rc) +int Socket_getReadySocket(int more_work, int timeout, mutex_type mutex, int* rc) { int sock = 0; *rc = 0; - static struct timeval zero = {0L, 0L}; /* 0 seconds */ - static struct timeval one = {1L, 0L}; /* 1 second */ - struct timeval timeout = one; + int timeout_ms = 1000; FUNC_ENTRY; Thread_lock_mutex(mutex); if (mod_s.clientsds->count == 0) goto exit; - + if (more_work) - timeout = zero; - else if (tp) - timeout = *tp; + timeout_ms = 0; + else if (timeout >= 0) + timeout_ms = timeout; while (mod_s.cur_clientsds != NULL) { @@ -268,14 +378,29 @@ int Socket_getReadySocket(int more_work, struct timeval *tp, mutex_type mutex, i if (mod_s.cur_clientsds == NULL) { - int rc1; + static struct timeval zero = {0L, 0L}; /* 0 seconds */ + int rc1, maxfdp1_saved; fd_set pwset; + struct timeval timeout_tv = {0L, 0L}; + + if (timeout_ms > 0L) + { + timeout_tv.tv_sec = timeout_ms / 1000; + timeout_tv.tv_usec = (timeout_ms % 1000) * 1000; /* this field is microseconds! */ + } memcpy((void*)&(mod_s.rset), (void*)&(mod_s.rset_saved), sizeof(mod_s.rset)); memcpy((void*)&(pwset), (void*)&(mod_s.pending_wset), sizeof(pwset)); + maxfdp1_saved = mod_s.maxfdp1; + + if (maxfdp1_saved == 0) + { + sock = 0; + goto exit; /* no work to do */ + } /* Prevent performance issue by unlocking the socket_mutex while waiting for a ready socket. */ Thread_unlock_mutex(mutex); - *rc = select(mod_s.maxfdp1, &(mod_s.rset), &pwset, NULL, &timeout); + *rc = select(maxfdp1_saved, &(mod_s.rset), &pwset, NULL, &timeout_tv); Thread_lock_mutex(mutex); if (*rc == SOCKET_ERROR) { @@ -284,7 +409,7 @@ int Socket_getReadySocket(int more_work, struct timeval *tp, mutex_type mutex, i } Log(TRACE_MAX, -1, "Return code %d from read select", *rc); - if (Socket_continueWrites(&pwset, &sock) == SOCKET_ERROR) + if (Socket_continueWrites(&pwset, &sock, mutex) == SOCKET_ERROR) { *rc = SOCKET_ERROR; goto exit; @@ -328,6 +453,102 @@ int Socket_getReadySocket(int more_work, struct timeval *tp, mutex_type mutex, i FUNC_EXIT_RC(sock); return sock; } /* end getReadySocket */ +#else +/** + * Returns the next socket ready for communications as indicated by select + * @param more_work flag to indicate more work is waiting, and thus a timeout value of 0 should + * be used for the select + * @param timeout the timeout to be used in ms + * @param rc a value other than 0 indicates an error of the returned socket + * @return the socket next ready, or 0 if none is ready + */ +SOCKET Socket_getReadySocket(int more_work, int timeout, mutex_type mutex, int* rc) +{ + SOCKET sock = 0; + *rc = 0; + int timeout_ms = 1000; + + FUNC_ENTRY; + Thread_lock_mutex(mutex); + if (mod_s.nfds == 0 && mod_s.saved.nfds == 0) + goto exit; + + if (more_work) + timeout_ms = 0; + else if (timeout >= 0) + timeout_ms = timeout; + + while (mod_s.saved.cur_fd != -1) + { + if (isReady(mod_s.saved.cur_fd)) + break; + mod_s.saved.cur_fd = (mod_s.saved.cur_fd == mod_s.saved.nfds - 1) ? -1 : mod_s.saved.cur_fd + 1; + } + + if (mod_s.saved.cur_fd == -1) + { + if (mod_s.nfds != mod_s.saved.nfds) + { + mod_s.saved.nfds = mod_s.nfds; + if (mod_s.saved.fds) + mod_s.saved.fds = realloc(mod_s.saved.fds, mod_s.nfds * sizeof(struct pollfd)); + else + mod_s.saved.fds = malloc(mod_s.nfds * sizeof(struct pollfd)); + } + memcpy(mod_s.saved.fds, mod_s.fds, mod_s.nfds * sizeof(struct pollfd)); + + if (mod_s.saved.nfds == 0) + { + sock = 0; + goto exit; /* no work to do */ + } + + /* Prevent performance issue by unlocking the socket_mutex while waiting for a ready socket. */ + Thread_unlock_mutex(mutex); + *rc = poll(mod_s.saved.fds, mod_s.saved.nfds, timeout_ms); + Thread_lock_mutex(mutex); + if (*rc == SOCKET_ERROR) + { + Socket_error("poll", 0); + goto exit; + } + Log(TRACE_MAX, -1, "Return code %d from poll", *rc); + + if (Socket_continueWrites(&sock, mutex) == SOCKET_ERROR) + { + *rc = SOCKET_ERROR; + goto exit; + } + + if (*rc == 0) + { + sock = 0; + goto exit; /* no work to do */ + } + + mod_s.saved.cur_fd = 0; + while (mod_s.saved.cur_fd != -1) + { + if (isReady(mod_s.saved.cur_fd)) + break; + mod_s.saved.cur_fd = (mod_s.saved.cur_fd == mod_s.saved.nfds - 1) ? -1 : mod_s.saved.cur_fd + 1; + } + } + + *rc = 0; + if (mod_s.saved.cur_fd == -1) + sock = 0; + else + { + sock = mod_s.saved.fds[mod_s.saved.cur_fd].fd; + mod_s.saved.cur_fd = (mod_s.saved.cur_fd == mod_s.saved.nfds - 1) ? -1 : mod_s.saved.cur_fd + 1; + } +exit: + Thread_unlock_mutex(mutex); + FUNC_EXIT_RC(sock); + return sock; +} /* end getReadySocket */ +#endif /** @@ -336,7 +557,7 @@ int Socket_getReadySocket(int more_work, struct timeval *tp, mutex_type mutex, i * @param c the character read, returned * @return completion code */ -int Socket_getch(int socket, char* c) +int Socket_getch(SOCKET socket, char* c) { int rc = SOCKET_ERROR; @@ -374,7 +595,7 @@ int Socket_getch(int socket, char* c) * @param actual_len the actual number of bytes read * @return completion code */ -char *Socket_getdata(int socket, size_t bytes, size_t* actual_len, int *rc) +char *Socket_getdata(SOCKET socket, size_t bytes, size_t* actual_len, int *rc) { char* buf; @@ -419,11 +640,11 @@ char *Socket_getdata(int socket, size_t bytes, size_t* actual_len, int *rc) /** * Indicate whether any data is pending outbound for a socket. - * @return boolean - true == data pending. + * @return boolean - true == no pending data. */ -int Socket_noPendingWrites(int socket) +int Socket_noPendingWrites(SOCKET socket) { - int cursock = socket; + SOCKET cursock = socket; return ListFindItem(mod_s.write_pending, &cursock, intcompare) == NULL; } @@ -437,7 +658,7 @@ int Socket_noPendingWrites(int socket) * @param bytes number of bytes actually written returned * @return completion code, especially TCPSOCKET_INTERRUPTED */ -int Socket_writev(int socket, iobuf* iovecs, int count, unsigned long* bytes) +int Socket_writev(SOCKET socket, iobuf* iovecs, int count, unsigned long* bytes) { int rc; @@ -509,7 +730,7 @@ for testing purposes only! * @param buflens an array of corresponding buffer lengths * @return completion code, especially TCPSOCKET_INTERRUPTED */ -int Socket_putdatas(int socket, char* buf0, size_t buf0len, PacketBuffers bufs) +int Socket_putdatas(SOCKET socket, char* buf0, size_t buf0len, PacketBuffers bufs) { unsigned long bytes = 0L; iobuf iovecs[5]; @@ -544,7 +765,7 @@ int Socket_putdatas(int socket, char* buf0, size_t buf0len, PacketBuffers bufs) rc = TCPSOCKET_COMPLETE; else { - int* sockmem = (int*)malloc(sizeof(int)); + SOCKET* sockmem = (SOCKET*)malloc(sizeof(SOCKET)); if (!sockmem) { @@ -565,7 +786,9 @@ int Socket_putdatas(int socket, char* buf0, size_t buf0len, PacketBuffers bufs) rc = PAHO_MEMORY_ERROR; goto exit; } +#if defined(USE_SELECT) FD_SET(socket, &(mod_s.pending_wset)); +#endif rc = TCPSOCKET_INTERRUPTED; } } @@ -581,9 +804,11 @@ int Socket_putdatas(int socket, char* buf0, size_t buf0len, PacketBuffers bufs) * ready to read and write states. * @param socket the socket to add */ -void Socket_addPendingWrite(int socket) +void Socket_addPendingWrite(SOCKET socket) { +#if defined(USE_SELECT) FD_SET(socket, &(mod_s.pending_wset)); +#endif } @@ -591,10 +816,12 @@ void Socket_addPendingWrite(int socket) * Clear a socket from the pending write list - if one was added with Socket_addPendingWrite * @param socket the socket to remove */ -void Socket_clearPendingWrite(int socket) +void Socket_clearPendingWrite(SOCKET socket) { +#if defined(USE_SELECT) if (FD_ISSET(socket, &(mod_s.pending_wset))) FD_CLR(socket, &(mod_s.pending_wset)); +#endif } @@ -603,7 +830,7 @@ void Socket_clearPendingWrite(int socket) * @param socket the socket to close * @return completion code */ -int Socket_close_only(int socket) +int Socket_close_only(SOCKET socket) { int rc; @@ -625,14 +852,16 @@ int Socket_close_only(int socket) return rc; } - +#if defined(USE_SELECT) /** * Close a socket and remove it from the select list. * @param socket the socket to close * @return completion code */ -void Socket_close(int socket) +int Socket_close(SOCKET socket) { + int rc = 0; + FUNC_ENTRY; Socket_close_only(socket); FD_CLR(socket, &(mod_s.rset_saved)); @@ -648,7 +877,11 @@ void Socket_close(int socket) if (ListRemoveItem(mod_s.clientsds, &socket, intcompare)) Log(TRACE_MIN, -1, "Removed socket %d", socket); else + { Log(LOG_ERROR, -1, "Failed to remove socket %d", socket); + rc = SOCKET_ERROR; + goto exit; + } if (socket + 1 >= mod_s.maxfdp1) { /* now we have to reset mod_s.maxfdp1 */ @@ -660,8 +893,61 @@ void Socket_close(int socket) ++(mod_s.maxfdp1); Log(TRACE_MAX, -1, "Reset max fdp1 to %d", mod_s.maxfdp1); } - FUNC_EXIT; +exit: + FUNC_EXIT_RC(rc); + return rc; +} +#else +/** + * Close a socket and remove it from the select list. + * @param socket the socket to close + * @return completion code + */ +int Socket_close(SOCKET socket) +{ + struct pollfd* fd; + int rc = 0; + + FUNC_ENTRY; + Socket_close_only(socket); + Socket_abortWrite(socket); + SocketBuffer_cleanup(socket); + ListRemoveItem(mod_s.connect_pending, &socket, intcompare); + ListRemoveItem(mod_s.write_pending, &socket, intcompare); + + fd = bsearch(&socket, mod_s.fds, (size_t)mod_s.nfds, sizeof(mod_s.fds[0]), cmpsockfds); + if (fd) + { + struct pollfd* last_fd = &mod_s.fds[mod_s.nfds - 1]; + + if (--mod_s.nfds == 0) + { + free(mod_s.fds); + mod_s.fds = NULL; + } + else + { + if (fd != last_fd) + { + /* shift array to remove the socket in question */ + memmove(fd, fd + 1, (mod_s.nfds - (fd - mod_s.fds)) * sizeof(mod_s.fds[0])); + } + mod_s.fds = realloc(mod_s.fds, sizeof(mod_s.fds[0]) * mod_s.nfds); + if (mod_s.fds == NULL) + { + rc = PAHO_MEMORY_ERROR; + goto exit; + } + } + Log(TRACE_MIN, -1, "Removed socket %d", socket); + } + else + Log(LOG_ERROR, -1, "Failed to remove socket %d", socket); +exit: + FUNC_EXIT_RC(rc); + return rc; } +#endif /** @@ -670,12 +956,12 @@ void Socket_close(int socket) * @param port the TCP port * @param sock returns the new socket * @param timeout the timeout in milliseconds - * @return completion code + * @return completion code 0=good, SOCKET_ERROR=fail */ #if defined(__GNUC__) && defined(__linux__) -int Socket_new(const char* addr, size_t addr_len, int port, int* sock, long timeout) +int Socket_new(const char* addr, size_t addr_len, int port, SOCKET* sock, long timeout) #else -int Socket_new(const char* addr, size_t addr_len, int port, int* sock) +int Socket_new(const char* addr, size_t addr_len, int port, SOCKET* sock) #endif { int type = SOCK_STREAM; @@ -694,7 +980,7 @@ int Socket_new(const char* addr, size_t addr_len, int port, int* sock) struct addrinfo hints = {0, AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP, 0, NULL, NULL, NULL}; FUNC_ENTRY; - *sock = -1; + *sock = SOCKET_ERROR; memset(&address6, '\0', sizeof(address6)); if (addr[0] == '[') @@ -748,7 +1034,7 @@ int Socket_new(const char* addr, size_t addr_len, int port, int* sock) } if (res == NULL) - rc = -1; + rc = SOCKET_ERROR; else #if defined(AF_INET6) if (res->ai_family == AF_INET6) @@ -767,18 +1053,21 @@ int Socket_new(const char* addr, size_t addr_len, int port, int* sock) address.sin_addr = ((struct sockaddr_in*)(res->ai_addr))->sin_addr; } else - rc = -1; + rc = SOCKET_ERROR; freeaddrinfo(result); } else - Log(LOG_ERROR, -1, "getaddrinfo failed for addr %s with rc %d", addr_mem, rc); + { + Log(LOG_ERROR, -1, "getaddrinfo failed for addr %s with rc %d", addr_mem, rc); + rc = SOCKET_ERROR; + } if (rc != 0) Log(LOG_ERROR, -1, "%s is not a valid IP address", addr_mem); else { - *sock = (int)socket(family, type, 0); + *sock = socket(family, type, 0); if (*sock == INVALID_SOCKET) rc = Socket_error("socket", *sock); else @@ -818,7 +1107,7 @@ int Socket_new(const char* addr, size_t addr_len, int port, int* sock) rc = Socket_error("connect", *sock); if (rc == EINPROGRESS || rc == EWOULDBLOCK) { - int* pnewSd = (int*)malloc(sizeof(int)); + SOCKET* pnewSd = (SOCKET*)malloc(sizeof(SOCKET)); if (!pnewSd) { @@ -826,7 +1115,7 @@ int Socket_new(const char* addr, size_t addr_len, int port, int* sock) goto exit; } *pnewSd = *sock; - if (!ListAppend(mod_s.connect_pending, pnewSd, sizeof(int))) + if (!ListAppend(mod_s.connect_pending, pnewSd, sizeof(SOCKET))) { free(pnewSd); rc = PAHO_MEMORY_ERROR; @@ -840,7 +1129,7 @@ int Socket_new(const char* addr, size_t addr_len, int port, int* sock) if (rc != 0 && (rc != EINPROGRESS) && (rc != EWOULDBLOCK)) { Socket_close(*sock); /* close socket and remove from our list of sockets */ - *sock = -1; /* as initialized before */ + *sock = SOCKET_ERROR; /* as initialized before */ } } } @@ -861,14 +1150,19 @@ void Socket_setWriteCompleteCallback(Socket_writeComplete* mywritecomplete) writecomplete = mywritecomplete; } +static Socket_writeAvailable* writeAvailable = NULL; +void Socket_setWriteAvailableCallback(Socket_writeAvailable* mywriteavailable) +{ + writeAvailable = mywriteavailable; +} /** * Continue an outstanding write for a particular socket * @param socket that socket * @return completion code: 0=incomplete, 1=complete, -1=socket error */ -int Socket_continueWrite(int socket) +int Socket_continueWrite(SOCKET socket) { int rc = 0; pending_writes* pw; @@ -953,7 +1247,7 @@ int Socket_continueWrite(int socket) * @param socket that socket * @return completion code: 0=incomplete, 1=complete, -1=socket error */ -int Socket_abortWrite(int socket) +int Socket_abortWrite(SOCKET socket) { int i = -1, rc = 0; pending_writes* pw; @@ -964,7 +1258,10 @@ int Socket_abortWrite(int socket) #if defined(OPENSSL) if (pw->ssl) + { + rc = SSLSocket_abortWrite(pw); goto exit; + } #endif for (i = 0; i < pw->count; i++) @@ -981,13 +1278,23 @@ int Socket_abortWrite(int socket) } +#if defined(USE_SELECT) /** * Continue any outstanding writes for a socket set * @param pwset the set of sockets * @param sock in case of a socket error contains the affected socket * @return completion code, 0 or SOCKET_ERROR */ -int Socket_continueWrites(fd_set* pwset, int* sock) +int Socket_continueWrites(fd_set* pwset, int* sock, mutex_type mutex) +#else +/** + * Continue any outstanding socket writes + + * @param sock in case of a socket error contains the affected socket + * @return completion code, 0 or SOCKET_ERROR + */ +int Socket_continueWrites(SOCKET* sock, mutex_type mutex) +#endif { int rc1 = 0; ListElement* curpending = mod_s.write_pending->first; @@ -997,12 +1304,23 @@ int Socket_continueWrites(fd_set* pwset, int* sock) { int socket = *(int*)(curpending->content); int rc = 0; +#if defined(USE_SELECT) if (FD_ISSET(socket, pwset) && ((rc = Socket_continueWrite(socket)) != 0)) +#else + struct pollfd* fd; + + /* find the socket in the fds structure */ + fd = bsearch(&socket, mod_s.saved.fds, (size_t)mod_s.saved.nfds, sizeof(mod_s.saved.fds[0]), cmpsockfds); + + if ((fd->revents & POLLOUT) && ((rc = Socket_continueWrite(socket)) != 0)) +#endif { if (!SocketBuffer_writeComplete(socket)) Log(LOG_SEVERE, -1, "Failed to remove pending write from socket buffer list"); +#if defined(USE_SELECT) FD_CLR(socket, &(mod_s.pending_wset)); +#endif if (!ListRemove(mod_s.write_pending, curpending->content)) { Log(LOG_SEVERE, -1, "Failed to remove pending write from list"); @@ -1010,13 +1328,20 @@ int Socket_continueWrites(fd_set* pwset, int* sock) } curpending = mod_s.write_pending->current; + if (writeAvailable && rc > 0) + (*writeAvailable)(socket); + if (writecomplete) + { + Thread_unlock_mutex(mutex); (*writecomplete)(socket, rc); + Thread_lock_mutex(mutex); + } } else ListNextElement(mod_s.write_pending, &curpending); - if(rc == SOCKET_ERROR) + if (rc == SOCKET_ERROR) { *sock = socket; rc1 = SOCKET_ERROR; @@ -1033,7 +1358,7 @@ int Socket_continueWrites(fd_set* pwset, int* sock) * @param sock socket * @return the peer information */ -char* Socket_getaddrname(struct sockaddr* sa, int sock) +char* Socket_getaddrname(struct sockaddr* sa, SOCKET sock) { /** * maximum length of the address string @@ -1071,7 +1396,7 @@ char* Socket_getaddrname(struct sockaddr* sa, int sock) * @param sock the socket to inquire on * @return the peer information */ -char* Socket_getpeer(int sock) +char* Socket_getpeer(SOCKET sock) { struct sockaddr_in6 sa; socklen_t sal = sizeof(sa); diff --git a/src/Socket.h b/src/Socket.h index 39a8158e7..1d87274d9 100644 --- a/src/Socket.h +++ b/src/Socket.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2020 IBM Corp. and others + * Copyright (c) 2009, 2022 IBM Corp., Ian Craggs and others * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -26,6 +26,7 @@ #include #include #define MAXHOSTNAMELEN 256 +#define poll WSAPoll #if !defined(SSLSOCKET_H) #undef EAGAIN #define EAGAIN WSAEWOULDBLOCK @@ -51,6 +52,7 @@ #include #include #include +#include #include #else #include @@ -65,6 +67,7 @@ #include #include #define ULONG size_t +#define SOCKET int #endif #include "mutex_type.h" /* Needed for mutex_type */ @@ -108,38 +111,53 @@ typedef struct */ typedef struct { + List* connect_pending; /**< list of sockets for which a connect is pending */ + List* write_pending; /**< list of sockets for which a write is pending */ + +#if defined(USE_SELECT) fd_set rset, /**< socket read set (see select doc) */ rset_saved; /**< saved socket read set */ int maxfdp1; /**< max descriptor used +1 (again see select doc) */ List* clientsds; /**< list of client socket descriptors */ ListElement* cur_clientsds; /**< current client socket descriptor (iterator) */ - List* connect_pending; /**< list of sockets for which a connect is pending */ - List* write_pending; /**< list of sockets for which a write is pending */ fd_set pending_wset; /**< socket pending write set for select */ +#else + unsigned int nfds; /**< no of file descriptors for poll */ + struct pollfd* fds; /**< poll read file descriptors */ + + struct { + int cur_fd; /**< index into the fds_saved array */ + unsigned int nfds; /**< number of fds in the fds_saved array */ + struct pollfd* fds; + } saved; +#endif } Sockets; void Socket_outInitialize(void); void Socket_outTerminate(void); -int Socket_getReadySocket(int more_work, struct timeval *tp, mutex_type mutex, int* rc); -int Socket_getch(int socket, char* c); -char *Socket_getdata(int socket, size_t bytes, size_t* actual_len, int* rc); -int Socket_putdatas(int socket, char* buf0, size_t buf0len, PacketBuffers bufs); -void Socket_close(int socket); +SOCKET Socket_getReadySocket(int more_work, int timeout, mutex_type mutex, int* rc); +int Socket_getch(SOCKET socket, char* c); +char *Socket_getdata(SOCKET socket, size_t bytes, size_t* actual_len, int* rc); +int Socket_putdatas(SOCKET socket, char* buf0, size_t buf0len, PacketBuffers bufs); +int Socket_close(SOCKET socket); #if defined(__GNUC__) && defined(__linux__) /* able to use GNU's getaddrinfo_a to make timeouts possible */ -int Socket_new(const char* addr, size_t addr_len, int port, int* socket, long timeout); +int Socket_new(const char* addr, size_t addr_len, int port, SOCKET* socket, long timeout); #else -int Socket_new(const char* addr, size_t addr_len, int port, int* socket); +int Socket_new(const char* addr, size_t addr_len, int port, SOCKET* socket); #endif -int Socket_noPendingWrites(int socket); -char* Socket_getpeer(int sock); +int Socket_noPendingWrites(SOCKET socket); +char* Socket_getpeer(SOCKET sock); -void Socket_addPendingWrite(int socket); -void Socket_clearPendingWrite(int socket); +void Socket_addPendingWrite(SOCKET socket); +void Socket_clearPendingWrite(SOCKET socket); -typedef void Socket_writeComplete(int socket, int rc); +typedef void Socket_writeComplete(SOCKET socket, int rc); void Socket_setWriteCompleteCallback(Socket_writeComplete*); +typedef void Socket_writeAvailable(SOCKET socket); +void Socket_setWriteAvailableCallback(Socket_writeAvailable*); + #endif /* SOCKET_H */ diff --git a/src/SocketBuffer.c b/src/SocketBuffer.c index f6b817af5..42eff81fe 100644 --- a/src/SocketBuffer.c +++ b/src/SocketBuffer.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2020 IBM Corp. + * Copyright (c) 2009, 2022 IBM Corp., Ian Craggs and others * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -148,7 +148,7 @@ void SocketBuffer_terminate(void) * Cleanup any buffers for a specific socket * @param socket the socket to clean up */ -void SocketBuffer_cleanup(int socket) +void SocketBuffer_cleanup(SOCKET socket) { FUNC_ENTRY; SocketBuffer_writeComplete(socket); /* clean up write buffers */ @@ -173,7 +173,7 @@ void SocketBuffer_cleanup(int socket) * @param actual_len the actual length returned * @return the actual data */ -char* SocketBuffer_getQueuedData(int socket, size_t bytes, size_t* actual_len) +char* SocketBuffer_getQueuedData(SOCKET socket, size_t bytes, size_t* actual_len) { socket_queue* queue = NULL; @@ -216,7 +216,7 @@ char* SocketBuffer_getQueuedData(int socket, size_t bytes, size_t* actual_len) * @param c the character returned if any * @return completion code */ -int SocketBuffer_getQueuedChar(int socket, char* c) +int SocketBuffer_getQueuedChar(SOCKET socket, char* c) { int rc = SOCKETBUFFER_INTERRUPTED; @@ -249,7 +249,7 @@ int SocketBuffer_getQueuedChar(int socket, char* c) * @param socket the socket to get queued data for * @param actual_len the actual length of data that was read */ -void SocketBuffer_interrupted(int socket, size_t actual_len) +void SocketBuffer_interrupted(SOCKET socket, size_t actual_len) { socket_queue* queue = NULL; @@ -278,7 +278,7 @@ void SocketBuffer_interrupted(int socket, size_t actual_len) * @param socket the socket for which the operation is now complete * @return pointer to the default queue data */ -char* SocketBuffer_complete(int socket) +char* SocketBuffer_complete(SOCKET socket) { FUNC_ENTRY; if (ListFindItem(queues, &socket, socketcompare)) @@ -300,7 +300,7 @@ char* SocketBuffer_complete(int socket) * @param socket the socket for which to queue char for * @param c the character to queue */ -void SocketBuffer_queueChar(int socket, char c) +void SocketBuffer_queueChar(SOCKET socket, char c) { int error = 0; socket_queue* curq = def_queue; @@ -344,9 +344,9 @@ void SocketBuffer_queueChar(int socket, char c) * @param bytes actual data length that was written */ #if defined(OPENSSL) -int SocketBuffer_pendingWrite(int socket, SSL* ssl, int count, iobuf* iovecs, int* frees, size_t total, size_t bytes) +int SocketBuffer_pendingWrite(SOCKET socket, SSL* ssl, int count, iobuf* iovecs, int* frees, size_t total, size_t bytes) #else -int SocketBuffer_pendingWrite(int socket, int count, iobuf* iovecs, int* frees, size_t total, size_t bytes) +int SocketBuffer_pendingWrite(SOCKET socket, int count, iobuf* iovecs, int* frees, size_t total, size_t bytes) #endif { int i = 0; @@ -396,7 +396,7 @@ int pending_socketcompare(void* a, void* b) * @param socket the socket to get queued data for * @return pointer to the queued data or NULL */ -pending_writes* SocketBuffer_getWrite(int socket) +pending_writes* SocketBuffer_getWrite(SOCKET socket) { ListElement* le = ListFindItem(&writes, &socket, pending_socketcompare); return (le) ? (pending_writes*)(le->content) : NULL; @@ -408,7 +408,7 @@ pending_writes* SocketBuffer_getWrite(int socket) * @param socket the socket for which the operation is now complete * @return completion code, boolean - was the queue removed? */ -int SocketBuffer_writeComplete(int socket) +int SocketBuffer_writeComplete(SOCKET socket) { return ListRemoveItem(&writes, &socket, pending_socketcompare); } @@ -421,7 +421,7 @@ int SocketBuffer_writeComplete(int socket) * @param payload the payload of the QoS 0 write * @return pointer to the updated queued data structure, or NULL */ -pending_writes* SocketBuffer_updateWrite(int socket, char* topic, char* payload) +pending_writes* SocketBuffer_updateWrite(SOCKET socket, char* topic, char* payload) { pending_writes* pw = NULL; ListElement* le = NULL; diff --git a/src/SocketBuffer.h b/src/SocketBuffer.h index 0fc7d6e7d..1b2ab915a 100644 --- a/src/SocketBuffer.h +++ b/src/SocketBuffer.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2020 IBM Corp. + * Copyright (c) 2009, 2022 IBM Corp., Ian Craggs and others * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -18,11 +18,7 @@ #if !defined(SOCKETBUFFER_H) #define SOCKETBUFFER_H -#if defined(_WIN32) || defined(_WIN64) -#include -#else -#include -#endif +#include "Socket.h" #if defined(OPENSSL) #include @@ -36,7 +32,7 @@ typedef struct { - int socket; + SOCKET socket; unsigned int index; size_t headerlen; char fixed_header[5]; /**< header plus up to 4 length bytes */ @@ -47,7 +43,8 @@ typedef struct typedef struct { - int socket, count; + SOCKET socket; + int count; size_t total; #if defined(OPENSSL) SSL* ssl; @@ -65,20 +62,20 @@ typedef struct int SocketBuffer_initialize(void); void SocketBuffer_terminate(void); -void SocketBuffer_cleanup(int socket); -char* SocketBuffer_getQueuedData(int socket, size_t bytes, size_t* actual_len); -int SocketBuffer_getQueuedChar(int socket, char* c); -void SocketBuffer_interrupted(int socket, size_t actual_len); -char* SocketBuffer_complete(int socket); -void SocketBuffer_queueChar(int socket, char c); +void SocketBuffer_cleanup(SOCKET socket); +char* SocketBuffer_getQueuedData(SOCKET socket, size_t bytes, size_t* actual_len); +int SocketBuffer_getQueuedChar(SOCKET socket, char* c); +void SocketBuffer_interrupted(SOCKET socket, size_t actual_len); +char* SocketBuffer_complete(SOCKET socket); +void SocketBuffer_queueChar(SOCKET socket, char c); #if defined(OPENSSL) -int SocketBuffer_pendingWrite(int socket, SSL* ssl, int count, iobuf* iovecs, int* frees, size_t total, size_t bytes); +int SocketBuffer_pendingWrite(SOCKET socket, SSL* ssl, int count, iobuf* iovecs, int* frees, size_t total, size_t bytes); #else -int SocketBuffer_pendingWrite(int socket, int count, iobuf* iovecs, int* frees, size_t total, size_t bytes); +int SocketBuffer_pendingWrite(SOCKET socket, int count, iobuf* iovecs, int* frees, size_t total, size_t bytes); #endif -pending_writes* SocketBuffer_getWrite(int socket); -int SocketBuffer_writeComplete(int socket); -pending_writes* SocketBuffer_updateWrite(int socket, char* topic, char* payload); +pending_writes* SocketBuffer_getWrite(SOCKET socket); +int SocketBuffer_writeComplete(SOCKET socket); +pending_writes* SocketBuffer_updateWrite(SOCKET socket, char* topic, char* payload); #endif diff --git a/src/StackTrace.c b/src/StackTrace.c index d618b1fe7..0fd61e315 100644 --- a/src/StackTrace.c +++ b/src/StackTrace.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2020 IBM Corp. + * Copyright (c) 2009, 2022 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -109,7 +109,7 @@ void StackTrace_entry(const char* name, int line, enum LOG_LEVELS trace_level) if (!setStack(1)) goto exit; if (trace_level != -1) - Log_stackTrace(trace_level, 9, (int)my_thread->id, my_thread->current_depth, name, line, NULL); + Log_stackTrace(trace_level, 9, my_thread->id, my_thread->current_depth, name, line, NULL); strncpy(my_thread->callstack[my_thread->current_depth].name, name, sizeof(my_thread->callstack[0].name)-1); my_thread->callstack[(my_thread->current_depth)++].line = line; if (my_thread->current_depth > my_thread->maxdepth) @@ -133,9 +133,9 @@ void StackTrace_exit(const char* name, int line, void* rc, enum LOG_LEVELS trace if (trace_level != -1) { if (rc == NULL) - Log_stackTrace(trace_level, 10, (int)my_thread->id, my_thread->current_depth, name, line, NULL); + Log_stackTrace(trace_level, 10, my_thread->id, my_thread->current_depth, name, line, NULL); else - Log_stackTrace(trace_level, 11, (int)my_thread->id, my_thread->current_depth, name, line, (int*)rc); + Log_stackTrace(trace_level, 11, my_thread->id, my_thread->current_depth, name, line, (int*)rc); } exit: Thread_unlock_mutex(stack_mutex); diff --git a/src/Thread.c b/src/Thread.c index b8f789d6e..4bcca868e 100644 --- a/src/Thread.c +++ b/src/Thread.c @@ -403,25 +403,37 @@ int Thread_signal_cond(cond_type condvar) } /** - * Wait with a timeout (seconds) for condition variable + * Wait with a timeout (ms) for condition variable * @return 0 for success, ETIMEDOUT otherwise */ -int Thread_wait_cond(cond_type condvar, int timeout) +int Thread_wait_cond(cond_type condvar, int timeout_ms) { int rc = 0; struct timespec cond_timeout; + struct timespec interval; FUNC_ENTRY; + interval.tv_sec = timeout_ms / 1000; + interval.tv_nsec = (timeout_ms % 1000) * 1000000L; + #if defined(__APPLE__) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101200 /* for older versions of MacOS */ struct timeval cur_time; gettimeofday(&cur_time, NULL); - cond_timeout.tv_sec = cur_time.tv_sec + timeout; + cond_timeout.tv_sec = cur_time.tv_sec; cond_timeout.tv_nsec = cur_time.tv_usec * 1000; #else clock_gettime(CLOCK_REALTIME, &cond_timeout); - - cond_timeout.tv_sec += timeout; #endif + + cond_timeout.tv_sec += interval.tv_sec; + cond_timeout.tv_nsec += (timeout_ms % 1000) * 1000000L; + + if (cond_timeout.tv_nsec >= 1000000000L) + { + cond_timeout.tv_sec++; + cond_timeout.tv_nsec += (cond_timeout.tv_nsec - 1000000000L); + } + pthread_mutex_lock(&condvar->mutex); rc = pthread_cond_timedwait(&condvar->cond, &condvar->mutex, &cond_timeout); pthread_mutex_unlock(&condvar->mutex); diff --git a/src/WebSocket.c b/src/WebSocket.c index 09254f493..4870f9160 100644 --- a/src/WebSocket.c +++ b/src/WebSocket.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018, 2021 Wind River Systems, Inc., Ian Craggs and others + * Copyright (c) 2018, 2022 Wind River Systems, Inc., Ian Craggs and others * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -14,18 +14,12 @@ * Keith Holman - initial implementation and documentation * Ian Craggs - use memory tracking * Ian Craggs - fix for one MQTT packet spread over >1 ws frame + * Sven Gambel - move WebSocket proxy support to generic proxy support *******************************************************************************/ #include #include #include -// for timeout process in WebSocket_proxy_connect() -#include -#if defined(_WIN32) || defined(_WIN64) -#include -#else -#include -#endif #include "WebSocket.h" @@ -375,6 +369,7 @@ static void WebSocket_unmaskData(size_t idx, PacketBuffers* bufs) * sends out a websocket request on the given uri * * @param[in] net network connection + * @param[in] ssl ssl flag * @param[in] uri uri to connect to * * @retval SOCKET_ERROR on failure @@ -382,7 +377,7 @@ static void WebSocket_unmaskData(size_t idx, PacketBuffers* bufs) * * @see WebSocket_upgrade */ -int WebSocket_connect( networkHandles *net, const char *uri) +int WebSocket_connect( networkHandles *net, int ssl, const char *uri) { int rc; char *buf = NULL; @@ -419,7 +414,7 @@ int WebSocket_connect( networkHandles *net, const char *uri) Base64_encode( net->websocket_key, 25u, uuid, sizeof(uuid_t) ); #endif /* else if defined(_WIN32) || defined(_WIN64) */ - hostname_len = MQTTProtocol_addressPort(uri, &port, &topic, WS_DEFAULT_PORT); + hostname_len = MQTTProtocol_addressPort(uri, &port, &topic, ssl ? WSS_DEFAULT_PORT : WS_DEFAULT_PORT); /* if no topic, use default */ if ( !topic ) @@ -785,7 +780,7 @@ char *WebSocket_getRawSocketData(networkHandles *net, size_t bytes, size_t* actu *actual_len = bytes; rv = frame_buffer + frame_buffer_index; frame_buffer_index += bytes; - + *rc = (int)bytes; goto exit; } else @@ -1436,117 +1431,3 @@ int WebSocket_upgrade( networkHandles *net ) FUNC_EXIT_RC(rc); return rc; } - -/** - * Notify the IP address and port of the endpoint to proxy, and wait connection to endpoint. - * - * @param[in] net network connection to proxy. - * @param[in] ssl enable ssl. - * @param[in] hostname hostname of endpoint. - * - * @retval SOCKET_ERROR failed to network connection - * @retval 0 connection to endpoint - * - */ -int WebSocket_proxy_connect( networkHandles *net, int ssl, const char *hostname) -{ - int port, i, rc = 0, buf_len=0; - char *buf = NULL; - size_t hostname_len, actual_len = 0; - time_t current, timeout; - PacketBuffers nulbufs = {0, NULL, NULL, NULL, {0, 0, 0, 0}}; - - FUNC_ENTRY; - hostname_len = MQTTProtocol_addressPort(hostname, &port, NULL, WS_DEFAULT_PORT); - for ( i = 0; i < 2; ++i ) { -#if defined(OPENSSL) - if(ssl) { - if (net->https_proxy_auth) { - buf_len = snprintf( buf, (size_t)buf_len, "CONNECT %.*s:%d HTTP/1.1\r\n" - "Host: %.*s\r\n" - "Proxy-authorization: Basic %s\r\n" - "\r\n", - (int)hostname_len, hostname, port, - (int)hostname_len, hostname, net->https_proxy_auth); - } - else { - buf_len = snprintf( buf, (size_t)buf_len, "CONNECT %.*s:%d HTTP/1.1\r\n" - "Host: %.*s\r\n" - "\r\n", - (int)hostname_len, hostname, port, - (int)hostname_len, hostname); - } - } - else { -#endif - if (net->http_proxy_auth) { - buf_len = snprintf( buf, (size_t)buf_len, "CONNECT %.*s:%d HTTP/1.1\r\n" - "Host: %.*s\r\n" - "Proxy-authorization: Basic %s\r\n" - "\r\n", - (int)hostname_len, hostname, port, - (int)hostname_len, hostname, net->http_proxy_auth); - } - else { - buf_len = snprintf( buf, (size_t)buf_len, "CONNECT %.*s:%d HTTP/1.1\r\n" - "Host: %.*s\r\n" - "\r\n", - (int)hostname_len, hostname, port, - (int)hostname_len, hostname); - } -#if defined(OPENSSL) - } -#endif - if ( i==0 && buf_len > 0 ) { - ++buf_len; - if ((buf = malloc( buf_len )) == NULL) - { - rc = PAHO_MEMORY_ERROR; - goto exit; - } - - } - } - Log(TRACE_PROTOCOL, -1, "WebSocket_proxy_connect: \"%s\"", buf); - - Socket_putdatas(net->socket, buf, buf_len, nulbufs); - free(buf); - buf = NULL; - - time(&timeout); - timeout += (time_t)10; - - while(1) { - buf = Socket_getdata(net->socket, (size_t)12, &actual_len, &rc); - if(actual_len) { - if ( (strncmp( buf, "HTTP/1.0 200", 12 ) != 0) && (strncmp( buf, "HTTP/1.1 200", 12 ) != 0) ) - rc = SOCKET_ERROR; - break; - } - else { - time(¤t); - if(current > timeout) { - rc = SOCKET_ERROR; - break; - } -#if defined(_WIN32) || defined(_WIN64) - Sleep(250); -#else - usleep(250000); -#endif - } - } - - /* flush the SocketBuffer */ - actual_len = 1; - while (actual_len) - { - int rc1; - - buf = Socket_getdata(net->socket, (size_t)1, &actual_len, &rc1); - } - -exit: - FUNC_EXIT_RC(rc); - return rc; -} diff --git a/src/WebSocket.h b/src/WebSocket.h index e9f576536..4e437d4af 100644 --- a/src/WebSocket.h +++ b/src/WebSocket.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018, 2020 Wind River Systems, Inc. and others. All Rights Reserved. + * Copyright (c) 2018, 2022 Wind River Systems, Inc. and others. All Rights Reserved. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -12,6 +12,7 @@ * * Contributors: * Keith Holman - initial implementation and documentation + * Sven Gambel - move WebSocket proxy support to generic proxy support *******************************************************************************/ #if !defined(WEBSOCKET_H) @@ -54,7 +55,7 @@ void WebSocket_close(networkHandles *net, int status_code, const char *reason); /* sends upgrade request */ -int WebSocket_connect(networkHandles *net, const char *uri); +int WebSocket_connect(networkHandles *net, int ssl, const char *uri); /* obtain data from network socket */ int WebSocket_getch(networkHandles *net, char* c); @@ -71,7 +72,4 @@ void WebSocket_terminate(void); /* handles websocket upgrade request */ int WebSocket_upgrade(networkHandles *net); -/* Notify the IP address and port of the endpoint to proxy, and wait connection to endpoint */ -int WebSocket_proxy_connect( networkHandles *net, int ssl, const char *hostname); - #endif /* WEBSOCKET_H */ diff --git a/src/samples/MQTTAsync_publish.c b/src/samples/MQTTAsync_publish.c index 34ee8fc71..b2ac829a3 100644 --- a/src/samples/MQTTAsync_publish.c +++ b/src/samples/MQTTAsync_publish.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2012, 2020 IBM Corp. + * Copyright (c) 2012, 2022 IBM Corp., Ian Craggs * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -29,7 +29,7 @@ #include #endif -#define ADDRESS "tcp://mqtt.eclipse.org:1883" +#define ADDRESS "tcp://mqtt.eclipseprojects.io:1883" #define CLIENTID "ExampleClientPub" #define TOPIC "MQTT Examples" #define PAYLOAD "Hello World!" diff --git a/src/samples/MQTTAsync_subscribe.c b/src/samples/MQTTAsync_subscribe.c index e2680de48..ae3291164 100644 --- a/src/samples/MQTTAsync_subscribe.c +++ b/src/samples/MQTTAsync_subscribe.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2012, 2020 IBM Corp. + * Copyright (c) 2012, 2022 IBM Corp., Ian Craggs * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -29,7 +29,7 @@ #include #endif -#define ADDRESS "tcp://mqtt.eclipse.org:1883" +#define ADDRESS "tcp://mqtt.eclipseprojects.io:1883" #define CLIENTID "ExampleClientSub" #define TOPIC "MQTT Examples" #define PAYLOAD "Hello World!" @@ -40,6 +40,9 @@ int disc_finished = 0; int subscribed = 0; int finished = 0; +void onConnect(void* context, MQTTAsync_successData* response); +void onConnectFailure(void* context, MQTTAsync_failureData* response); + void connlost(void *context, char *cause) { MQTTAsync client = (MQTTAsync)context; @@ -53,6 +56,8 @@ void connlost(void *context, char *cause) printf("Reconnecting\n"); conn_opts.keepAliveInterval = 20; conn_opts.cleansession = 1; + conn_opts.onSuccess = onConnect; + conn_opts.onFailure = onConnectFailure; if ((rc = MQTTAsync_connect(client, &conn_opts)) != MQTTASYNC_SUCCESS) { printf("Failed to start connect, return code %d\n", rc); diff --git a/src/samples/MQTTClient_publish.c b/src/samples/MQTTClient_publish.c index bbe8b497d..399fd339f 100644 --- a/src/samples/MQTTClient_publish.c +++ b/src/samples/MQTTClient_publish.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2012, 2020 IBM Corp. + * Copyright (c) 2012, 2022 IBM Corp., Ian Craggs * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -19,7 +19,7 @@ #include #include "MQTTClient.h" -#define ADDRESS "tcp://mqtt.eclipse.org:1883" +#define ADDRESS "tcp://mqtt.eclipseprojects.io:1883" #define CLIENTID "ExampleClientPub" #define TOPIC "MQTT Examples" #define PAYLOAD "Hello World!" diff --git a/src/samples/MQTTClient_publish_async.c b/src/samples/MQTTClient_publish_async.c index d376ea5e4..c5685417d 100644 --- a/src/samples/MQTTClient_publish_async.c +++ b/src/samples/MQTTClient_publish_async.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2012, 2020 IBM Corp. + * Copyright (c) 2012, 2022 IBM Corp., Ian Craggs * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -25,7 +25,7 @@ #include #endif -#define ADDRESS "tcp://mqtt.eclipse.org:1883" +#define ADDRESS "tcp://mqtt.eclipseprojects.io:1883" #define CLIENTID "ExampleClientPub" #define TOPIC "MQTT Examples" #define PAYLOAD "Hello World!" diff --git a/src/samples/MQTTClient_subscribe.c b/src/samples/MQTTClient_subscribe.c index 29f7fa1a1..8a527a390 100644 --- a/src/samples/MQTTClient_subscribe.c +++ b/src/samples/MQTTClient_subscribe.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2012, 2020 IBM Corp. + * Copyright (c) 2012, 2022 IBM Corp., Ian Craggs * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -19,7 +19,7 @@ #include #include "MQTTClient.h" -#define ADDRESS "tcp://mqtt.eclipse.org:1883" +#define ADDRESS "tcp://mqtt.eclipseprojects.io:1883" #define CLIENTID "ExampleClientSub" #define TOPIC "MQTT Examples" #define PAYLOAD "Hello World!" diff --git a/src/samples/paho_cs_pub.c b/src/samples/paho_cs_pub.c index 574ab9fe7..a83203844 100644 --- a/src/samples/paho_cs_pub.c +++ b/src/samples/paho_cs_pub.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2012, 2020 IBM Corp. + * Copyright (c) 2012, 2022 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -52,7 +52,7 @@ struct pubsub_opts opts = }; -int myconnect(MQTTClient* client) +int myconnect(MQTTClient client) { MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer; MQTTClient_SSLOptions ssl_opts = MQTTClient_SSLOptions_initializer; diff --git a/src/samples/paho_cs_sub.c b/src/samples/paho_cs_sub.c index b6241e2b9..cf271175d 100644 --- a/src/samples/paho_cs_sub.c +++ b/src/samples/paho_cs_sub.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2012, 2020 IBM Corp., and others + * Copyright (c) 2012, 2022 IBM Corp., and others * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -49,7 +49,7 @@ struct pubsub_opts opts = }; -int myconnect(MQTTClient* client) +int myconnect(MQTTClient client) { MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer; MQTTClient_SSLOptions ssl_opts = MQTTClient_SSLOptions_initializer; @@ -243,7 +243,7 @@ int main(int argc, char** argv) MQTTClient_free(topicName); } if (rc != 0) - myconnect(&client); + myconnect(client); } exit: diff --git a/src/samples/pubsub_opts.h b/src/samples/pubsub_opts.h index 0f2268272..73bb09764 100644 --- a/src/samples/pubsub_opts.h +++ b/src/samples/pubsub_opts.h @@ -69,7 +69,7 @@ struct pubsub_opts char *name; char *value; } user_property; - /* websocket HTTP proxies */ + /* HTTP proxies */ char* http_proxy; char* https_proxy; }; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0e4c6a8ee..fc07acd04 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1025,7 +1025,7 @@ IF (PAHO_WITH_SSL) ADD_TEST( NAME test5-6-multiple-connections-static - COMMAND test5-static "--test_no" "9" "--hostname" ${MQTT_SSL_HOSTNAME} "--client_key" "${CERTDIR}/client.pem" "--server_key" "${CERTDIR}/test-root-ca.crt" + COMMAND test5-static "--test_no" "9" "--hostname" ${MQTT_SSL_HOSTNAME} "--client_key" "${CERTDIR}/client.pem" "--server_key" "${CERTDIR}/test-root-ca.crt" --verbose ) ADD_TEST( @@ -1040,7 +1040,7 @@ IF (PAHO_WITH_SSL) ADD_TEST( NAME test5-7-ws-big-messages-static - COMMAND test5-static "--test_no" "10" "--ws" "--hostname" ${MQTT_SSL_HOSTNAME} "--client_key" "${CERTDIR}/client.pem" "--server_key" "${CERTDIR}/test-root-ca.crt" + COMMAND test5-static "--test_no" "10" "--ws" "--hostname" ${MQTT_SSL_HOSTNAME} "--client_key" "${CERTDIR}/client.pem" "--server_key" "${CERTDIR}/test-root-ca.crt" --verbose ) ADD_TEST( @@ -1226,7 +1226,7 @@ IF (PAHO_WITH_SSL) ADD_TEST( NAME test5-7-ws-big-messages - COMMAND test5 "--test_no" "10" "--ws" "--hostname" ${MQTT_SSL_HOSTNAME} "--client_key" "${CERTDIR}/client.pem" "--server_key" "${CERTDIR}/test-root-ca.crt" + COMMAND test5 "--test_no" "10" "--ws" "--hostname" ${MQTT_SSL_HOSTNAME} "--client_key" "${CERTDIR}/client.pem" "--server_key" "${CERTDIR}/test-root-ca.crt" --verbose ) ADD_TEST( @@ -1397,6 +1397,11 @@ IF (PAHO_BUILD_STATIC) COMMAND test8-static "--test_no" "7" "--connection" ${MQTT_TEST_BROKER} ) + ADD_TEST( + NAME test8-6-blocked-acks-static + COMMAND test8-static "--test_no" "8" "--connection" ${MQTT_TEST_BROKER} "--size" "500000" --verbose + ) + SET_TESTS_PROPERTIES( test8-1-basic-connect-subscribe-receive-static test8-2-connect-timeout-static @@ -1405,6 +1410,7 @@ IF (PAHO_BUILD_STATIC) test8-5a-all-ha-connections-out-of-service-static test8-5b-all-ha-connections-out-of-service-except-the-last-one-static test8-5c-all-ha-connections-out-of-service-except-the-first-one-static + test8-6-blocked-acks-static PROPERTIES TIMEOUT 540 ) ENDIF() @@ -1455,6 +1461,11 @@ IF (PAHO_BUILD_SHARED) COMMAND test8 "--test_no" "7" "--connection" ${MQTT_TEST_BROKER} ) + ADD_TEST( + NAME test8-6-blocked-acks + COMMAND test8 "--test_no" "8" "--connection" ${MQTT_TEST_BROKER} "--size" "500000" --verbose + ) + SET_TESTS_PROPERTIES( test8-1-basic-connect-subscribe-receive test8-2-connect-timeout @@ -1463,6 +1474,7 @@ IF (PAHO_BUILD_SHARED) test8-5a-all-ha-connections-out-of-service test8-5b-all-ha-connections-out-of-service-except-the-last-one test8-5c-all-ha-connections-out-of-service-except-the-first-one + test8-6-blocked-acks PROPERTIES TIMEOUT 540 ) ENDIF() diff --git a/test/test1.c b/test/test1.c index c1927bf48..8410f7454 100644 --- a/test/test1.c +++ b/test/test1.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 IBM Corp. + * Copyright (c) 2009, 2022 IBM Corp., Ian Craggs * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -56,9 +56,9 @@ struct Options int iterations; } options = { - "tcp://mqtt.eclipse.org:1883", - NULL, "tcp://localhost:1883", + NULL, + "tcp://localhost:1884", 0, 0, 0, @@ -805,7 +805,14 @@ int test4_run(int qos) } } - MQTTClient_yield(); /* allow any unfinished protocol exchanges to finish */ + /* call yield a few times until unfinished protocol exchanges are finished */ + count = 0; + do + { + MQTTClient_yield(); + rc = MQTTClient_getPendingDeliveryTokens(c, &tokens); + assert("getPendingDeliveryTokens rc == 0", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + } while (tokens != NULL && ++count < 10); rc = MQTTClient_getPendingDeliveryTokens(c, &tokens); assert("getPendingDeliveryTokens rc == 0", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); diff --git a/test/test15.c b/test/test15.c index 630d68649..22b80ba57 100644 --- a/test/test15.c +++ b/test/test15.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2020 IBM Corp. + * Copyright (c) 2009, 2022 IBM Corp, Ian Craggs * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -1020,7 +1020,14 @@ int test4_run(int qos, int start_mqtt_version, int restore_mqtt_version) } } - MQTTClient_yield(); /* allow any unfinished protocol exchanges to finish */ + /* call yield a few times until unfinished protocol exchanges are finished */ + count = 0; + do + { + MQTTClient_yield(); + rc = MQTTClient_getPendingDeliveryTokens(c, &tokens); + assert("getPendingDeliveryTokens rc == 0", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + } while (tokens != NULL && ++count < 10); rc = MQTTClient_getPendingDeliveryTokens(c, &tokens); assert("getPendingDeliveryTokens rc == 0", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); diff --git a/test/test2.c b/test/test2.c index a12db24dc..ec970afb9 100644 --- a/test/test2.c +++ b/test/test2.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2020 IBM Corp. and others + * Copyright (c) 2009, 2022 IBM Corp., Ian Craggs and others * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -55,7 +55,7 @@ struct Options int iterations; } options = { - "tcp://m2m.eclipse.org:1883", + "tcp://localhost:1883", NULL, 0, 0, diff --git a/test/test3.c b/test/test3.c index 6db990f48..4e46d2f48 100644 --- a/test/test3.c +++ b/test/test3.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2012, 2020 IBM Corp. and others + * Copyright (c) 2012, 2022 IBM Corp., Ian Craggs and others * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -88,12 +88,12 @@ struct Options int websockets; } options = { - "ssl://m2m.eclipse.org:18883", - "ssl://m2m.eclipse.org:18884", - "ssl://m2m.eclipse.org:18887", - "ssl://m2m.eclipse.org:18885", - "ssl://m2m.eclipse.org:18886", - "ssl://m2m.eclipse.org:18888", + "ssl://localhost:18883", + "ssl://localhost:18884", + "ssl://localhost:18887", + "ssl://localhost:18885", + "ssl://localhost:18886", + "ssl://localhost:18888", NULL, 0, "../../../test/ssl/client.pem", diff --git a/test/test4.c b/test/test4.c index beb7e1a5b..9c34a9715 100644 --- a/test/test4.c +++ b/test/test4.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2020 IBM Corp. + * Copyright (c) 2009, 2022 IBM Corp., Ian Craggs * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -54,7 +54,7 @@ struct Options int iterations; } options = { - "mqtt.eclipse.org:1883", + "localhost:1883", 0, -1, 10000, @@ -277,6 +277,7 @@ void test1_onUnsubscribe(void* context, MQTTAsync_successData* response) MyLog(LOGA_DEBUG, "In onUnsubscribe onSuccess callback %p", c); opts.onSuccess = test1_onDisconnect; opts.context = c; + opts.timeout = 1000; rc = MQTTAsync_disconnect(c, &opts); assert("Disconnect successful", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); @@ -554,6 +555,7 @@ void test3_onUnsubscribe(void* context, MQTTAsync_successData* response) MyLog(LOGA_DEBUG, "In onUnsubscribe onSuccess callback \"%s\"", cd->clientid); opts.onSuccess = test3_onDisconnect; opts.context = cd; + opts.timeout = 1000; rc = MQTTAsync_disconnect(cd->c, &opts); assert("Disconnect successful", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); @@ -1176,6 +1178,7 @@ void test7_onUnsubscribe(void* context, MQTTAsync_successData* response) MyLog(LOGA_DEBUG, "In onUnsubscribe onSuccess callback %p", c); opts.onSuccess = test7_onDisconnect; opts.context = c; + opts.timeout = 1000; rc = MQTTAsync_disconnect(c, &opts); assert("Disconnect successful", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); diff --git a/test/test5.c b/test/test5.c index 682ad9f34..4e188c489 100644 --- a/test/test5.c +++ b/test/test5.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2012, 2020 IBM Corp. + * Copyright (c) 2012, 2022 IBM Corp., Ian Craggs * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -73,12 +73,12 @@ struct Options int start_port; } options = { - "ssl://m2m.eclipse.org:18883", - "ssl://m2m.eclipse.org:18884", - "ssl://m2m.eclipse.org:18887", - "ssl://m2m.eclipse.org:18885", - "ssl://m2m.eclipse.org:18886", - "ssl://m2m.eclipse.org:18888", + "ssl://localhost:18883", + "ssl://localhost:18884", + "ssl://localhost:18887", + "ssl://localhost:18885", + "ssl://localhost:18886", + "ssl://localhost:18888", NULL, // "../../../test/ssl/client.pem", NULL, NULL, // "../../../test/ssl/test-root-ca.crt", @@ -529,6 +529,7 @@ void asyncTestOnUnsubscribe(void* context, MQTTAsync_successData* response) MyLog(LOGA_DEBUG, "In asyncTestOnUnsubscribe callback, %s", tc->clientid); opts.onSuccess = asyncTestOnDisconnect; opts.context = tc; + opts.timeout = 1000; rc = MQTTAsync_disconnect(tc->client, &opts); } @@ -2294,7 +2295,7 @@ int test7(struct Options options) tc.subscribed = 0; tc.testFinished = 0; - opts.keepAliveInterval = 20; + opts.keepAliveInterval = 60; opts.cleansession = 1; //opts.username = "testuser"; //opts.password = "testpassword"; @@ -2326,7 +2327,7 @@ int test7(struct Options options) if (rc != MQTTASYNC_SUCCESS) goto exit; - while (test7OnUnsubscribed == 0 && test7OnPublishSuccessCount < options.message_count) + while (tc.testFinished == 0 && test7OnUnsubscribed == 0 && test7OnPublishSuccessCount < options.message_count) #if defined(_WIN32) Sleep(100); #else diff --git a/test/test8.c b/test/test8.c index 6514d24ce..76dca58de 100644 --- a/test/test8.c +++ b/test/test8.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2012, 2020 IBM Corp. and others + * Copyright (c) 2012, 2021 IBM Corp., Ian Craggs and others * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -21,14 +21,16 @@ #include "MQTTAsync.h" +#include "Thread.h" #include #include #if !defined(_WINDOWS) #include - #include + #include #include - #include + #include + #define WINAPI #else #include #endif @@ -45,7 +47,7 @@ struct Options { char* connection; /**< connection to system under test. */ int verbose; - int test_no; + int test_no; int size; /**< size of big message */ } options = { @@ -240,6 +242,7 @@ void test1_onUnsubscribe(void* context, MQTTAsync_successData* response) MyLog(LOGA_DEBUG, "In onUnsubscribe onSuccess callback %p", c); opts.onSuccess = test1_onDisconnect; opts.context = c; + opts.timeout = 1000; rc = MQTTAsync_disconnect(c, &opts); assert("Disconnect successful", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); @@ -523,6 +526,7 @@ void test3_onUnsubscribe(void* context, MQTTAsync_successData* response) MyLog(LOGA_DEBUG, "In onUnsubscribe onSuccess callback \"%s\"", cd->clientid); opts.onSuccess = test3_onDisconnect; opts.context = cd; + opts.timeout = 1000; rc = MQTTAsync_disconnect(cd->c, &opts); assert("Disconnect successful", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); @@ -1092,9 +1096,302 @@ int test5c(struct Options options) } +/********************************************************************* + +Test6: Acks blocked due to sending messages + +*********************************************************************/ +char* test6_topic = "test6_topic"; +char* test6_payload = NULL; +int test6_connected = 0; +int test6_payloadlen = 0; +int test6_message_count = 0; +int test6_disconnected = 0; + + +void test6_onDisconnect(void* context, MQTTAsync_successData* response) +{ + MyLog(LOGA_DEBUG, "In onDisconnect callback"); + test6_disconnected = 1; +} + + +int test6_messageArrived(void* context, char* topicName, int topicLen, MQTTAsync_message* message) +{ + MQTTAsync c = (MQTTAsync)context; + + MyLog(LOGA_DEBUG, "In messageArrived callback %p", c); + + assert("Message size correct", message->payloadlen == test6_payloadlen, + "message size was %d", message->payloadlen); + + MQTTAsync_freeMessage(&message); + MQTTAsync_free(topicName); + + return 1; +} + + +void test6_onPublishFailure(void* context, MQTTAsync_failureData* response) +{ + MyLog(LOGA_INFO, "In publish onFailure callback, context %p", context); + failures++; + test_finished = 1; +} + + +void test6_onPublish(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + int rc; + static int publish_count = 0; + + if (++publish_count > 100) + { + test_finished = 1; + goto exit; + } + + MyLog(LOGA_INFO, "In publish onSuccess callback, count %d", publish_count); + + if (test6_payload == NULL) { + test6_payload = malloc(options.size); + memset(test6_payload, ' ', options.size); + } + + MyLog(LOGA_DEBUG, "In publish onSuccess callback, context %p", context); + pubmsg.payload = test6_payload; + pubmsg.payloadlen = options.size; + pubmsg.qos = 0; + pubmsg.retained = 0; + + opts.onSuccess = test6_onPublish; + opts.onFailure = test6_onPublishFailure; + opts.context = c; + + MyLog(LOGA_INFO, "Calling sendMessage, count %d", publish_count); + rc = MQTTAsync_sendMessage(c, "test6_big_messages", &pubmsg, &opts); + MyLog(LOGA_INFO, "Called sendMessage, count %d rc %d", publish_count, rc); + assert("Good rc from publish", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); +exit: + MyLog(LOGA_DEBUG, "Leaving publish onSuccess callback, count %d, context %p", publish_count, context); +} + + +void test6_onSubscribeFailure(void* context, MQTTAsync_failureData* response) +{ + MyLog(LOGA_INFO, "In subscribe onFailure callback, context %p", context); + failures++; + test_finished = 1; +} + + +void test6_onSubscribe(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + int rc; + + if (test6_payload == NULL) { + test6_payload = malloc(options.size); + memset(test6_payload, ' ', options.size); + } + + MyLog(LOGA_INFO, "In subscribe onSuccess callback, context %p", context); + pubmsg.payload = test6_payload; + pubmsg.payloadlen = options.size; + pubmsg.qos = 0; + pubmsg.retained = 0; + + opts.onSuccess = test6_onPublish; + opts.onFailure = test6_onPublishFailure; + opts.context = c; + + rc = MQTTAsync_sendMessage(c, "test6_big_messages", &pubmsg, &opts); + assert("Good rc from publish", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); +} + + +void test6_onConnectFailure(void* context, MQTTAsync_failureData* response) +{ + MyLog(LOGA_INFO, "In connect onFailure callback, context %p", context); + failures++; + test_finished = 1; +} + + +void test6_onConnect(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + int rc; + + MyLog(LOGA_INFO, "In connect onSuccess callback, context %p", context); + opts.onSuccess = test6_onSubscribe; + opts.onFailure = test6_onSubscribeFailure; + opts.context = c; + + rc = MQTTAsync_subscribe(c, test6_topic, 2, &opts); + assert("Good rc from subscribe", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + if (rc != MQTTASYNC_SUCCESS) + test_finished = 1; +} + + +void test6_onConnected(void* context, MQTTAsync_successData* response) +{ + MQTTAsync d = (MQTTAsync)context; + + MyLog(LOGA_DEBUG, "In connect onSuccess callback, context %p", context); + test6_connected = 1; +} + + +void test6_connectionLost(void* context, char* cause) { + MyLog(LOGA_INFO, "Connection lost"); + test_finished = 1; +} + + +int test6(struct Options options) +{ + MQTTAsync c, d; + MQTTAsync_connectOptions opts = MQTTAsync_connectOptions_initializer; + MQTTAsync_disconnectOptions dopts = MQTTAsync_disconnectOptions_initializer; + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + int rc = 0; + char* test_topic = "C client test8 - test6"; + int messages_sent = 0; + int count = 0; + + failures = 0; + test_finished = 0; + MyLog(LOGA_INFO, "Starting test 6 - acks blocked due to sending messages"); + global_start_time = start_clock(); + + rc = MQTTAsync_create(&c, options.connection, "acks blocked", + MQTTCLIENT_PERSISTENCE_DEFAULT, NULL); + assert("good rc from create", rc == MQTTASYNC_SUCCESS, "rc was %d\n", rc); + if (rc != MQTTASYNC_SUCCESS) + { + MQTTAsync_destroy(&c); + goto exit; + } + + rc = MQTTAsync_setCallbacks(c, c, test6_connectionLost, test6_messageArrived, NULL); + assert("Good rc from setCallbacks", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + + opts.keepAliveInterval = 20; + opts.cleansession = 1; + opts.username = "testuser"; + opts.password = "testpassword"; + + opts.onSuccess = test6_onConnect; + opts.onFailure = test6_onConnectFailure; + opts.context = c; + + MyLog(LOGA_DEBUG, "Connecting"); + rc = MQTTAsync_connect(c, &opts); + assert("Good rc from connect", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + if (rc != MQTTASYNC_SUCCESS) + goto exit; + + rc = MQTTAsync_create(&d, options.connection, "acks blocked - send loop", + MQTTCLIENT_PERSISTENCE_NONE, NULL); + assert("good rc from create", rc == MQTTASYNC_SUCCESS, "rc was %d\n", rc); + if (rc != MQTTASYNC_SUCCESS) + { + MQTTAsync_destroy(&d); + goto exit; + } + + opts.onSuccess = test6_onConnected; + opts.context = d; + + MyLog(LOGA_DEBUG, "Connecting"); + rc = MQTTAsync_connect(d, &opts); + assert("Good rc from connect", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + if (rc != MQTTASYNC_SUCCESS) + goto exit; + + while (test6_connected == 0 && test_finished == 0 && failures == 0) + { + #if defined(_WIN32) + Sleep(100); + #else + usleep(10000L); + #endif + } + + pubmsg.payload = "small payload in loop"; + pubmsg.payloadlen = test6_payloadlen = strlen(pubmsg.payload)+1; + pubmsg.qos = 1; + pubmsg.retained = 0; + + while (test_finished == 0 && failures == 0) + { + rc = MQTTAsync_sendMessage(d, test6_topic, &pubmsg, NULL); + assert("Good rc from publish", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + if (rc != MQTTASYNC_SUCCESS) + break; + messages_sent++; + + #if defined(_WIN32) + Sleep(1000); + #else + usleep(100000L); + #endif + } + + dopts.onSuccess = test6_onDisconnect; + dopts.timeout = 1000; + + dopts.context = d; + test6_disconnected = 0; + rc = MQTTAsync_disconnect(d, &dopts); + assert("Disconnect start successful", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + while (test6_disconnected == 0 && ++count < 5) + { + #if defined(_WIN32) + Sleep(1000); + #else + usleep(100000L); + #endif + } + + dopts.context = c; + test6_disconnected = 0; + rc = MQTTAsync_disconnect(c, &dopts); + assert("Disconnect start successful", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + count = 0; + while (test6_disconnected == 0 && ++count < 5) + { + #if defined(_WIN32) + Sleep(1000); + #else + usleep(100000L); + #endif + } + + MQTTAsync_destroy(&c); + MQTTAsync_destroy(&d); + +exit: + if (test6_payload) + free(test6_payload); + MyLog(LOGA_INFO, "TEST6: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", tests, failures); + return failures; +} + + + void trace_callback(enum MQTTASYNC_TRACE_LEVELS level, char* message) { - if (strstr(message, "onnect") && !strstr(message, "isconnect")) + //if ((strstr(message, "onnect") && !strstr(message, "isconnect")) || level == MQTTASYNC_TRACE_ERROR) printf("Trace : %d, %s\n", level, message); } @@ -1102,7 +1399,7 @@ void trace_callback(enum MQTTASYNC_TRACE_LEVELS level, char* message) int main(int argc, char** argv) { int rc = 0; - int (*tests[])() = {NULL, test1, test2, test3, test4, test5a, test5b, test5c}; /* indexed starting from 1 */ + int (*tests[])() = {NULL, test1, test2, test3, test4, test5a, test5b, test5c, test6}; /* indexed starting from 1 */ MQTTAsync_nameValue* info; getopts(argc, argv); diff --git a/test/test9.c b/test/test9.c index 47a3f8c8d..fe9d08f20 100644 --- a/test/test9.c +++ b/test/test9.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2012, 2020 IBM Corp. and others + * Copyright (c) 2012, 2022 IBM Corp., Ian Craggs * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -56,8 +56,8 @@ struct Options int test_no; } options = { - "mqtt.eclipse.org:1883", "localhost:1883", + "localhost:1884", 0, 0, }; diff --git a/test/test95.c b/test/test95.c index b8ff688c3..b759790cd 100644 --- a/test/test95.c +++ b/test/test95.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2012, 2020 IBM Corp. and others + * Copyright (c) 2012, 2022 IBM Corp., Ian Craggs * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -57,8 +57,8 @@ struct Options int test_no; } options = { - "mqtt.eclipse.org:1883", "localhost:1883", + "localhost:1884", 0, 0, }; @@ -1656,7 +1656,7 @@ int test5(struct Options options) } createOptions.sendWhileDisconnected = 0; - createOptions.maxBufferedMessages = 0; + createOptions.maxBufferedMessages = 1; createOptions.MQTTVersion = MQTTVERSION_5; rc = MQTTAsync_createWithOptions(&d, options.connection, clientidd, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL, &createOptions); @@ -1826,7 +1826,7 @@ int test6(struct Options options) } createOptions.sendWhileDisconnected = 0; - createOptions.maxBufferedMessages = 0; + createOptions.maxBufferedMessages = 1; createOptions.MQTTVersion = MQTTVERSION_5; rc = MQTTAsync_createWithOptions(&d, options.connection, clientidd, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL, &createOptions); diff --git a/test/test_mqtt4async.c b/test/test_mqtt4async.c index 2c7e7b3e0..4e7803960 100644 --- a/test/test_mqtt4async.c +++ b/test/test_mqtt4async.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2020 IBM Corp. + * Copyright (c) 2009, 2022 IBM Corp., Ian Craggs * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -68,7 +68,7 @@ struct Options int iterations; } options = { - "m2m.eclipse.org:1883", + "localhost:1883", NULL, 0, 0, diff --git a/test/test_mqtt4sync.c b/test/test_mqtt4sync.c index 6460925e0..9305af199 100644 --- a/test/test_mqtt4sync.c +++ b/test/test_mqtt4sync.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2020 IBM Corp. + * Copyright (c) 2009, 2022 IBM Corp., Ian Craggs * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -68,7 +68,7 @@ struct Options int iterations; } options = { - "tcp://m2m.eclipse.org:1883", + "tcp://localhost:1883", NULL, 0, 0, diff --git a/test/test_persistence.c b/test/test_persistence.c index 481768c21..f8d82f696 100644 --- a/test/test_persistence.c +++ b/test/test_persistence.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2012, 2018 IBM Corp. + * Copyright (c) 2012, 2022 IBM Corp., Ian Craggs * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -57,8 +57,8 @@ struct Options int test_no; } options = { - "mqtt.eclipse.org:1883", "localhost:1883", + "localhost:1884", 0, 0, }; diff --git a/test/test_sync_session_present.c b/test/test_sync_session_present.c index f54773e65..0e7201a7b 100644 --- a/test/test_sync_session_present.c +++ b/test/test_sync_session_present.c @@ -60,9 +60,9 @@ struct Options int reconnect_period; } options = { - "tcp://mqtt.eclipse.org:1883", - NULL, "tcp://localhost:1883", + NULL, + "tcp://localhost:1884", "cli/test", NULL, NULL, diff --git a/travis-build.sh b/travis-build.sh index 669a22c24..ef262ec0a 100755 --- a/travis-build.sh +++ b/travis-build.sh @@ -6,7 +6,7 @@ rm -rf build.paho mkdir build.paho cd build.paho echo "travis build dir $TRAVIS_BUILD_DIR pwd $PWD with OpenSSL root $OPENSSL_ROOT_DIR" -cmake -DPAHO_BUILD_STATIC=$PAHO_BUILD_STATIC -DPAHO_BUILD_SHARED=$PAHO_BUILD_SHARED -DCMAKE_BUILD_TYPE=Debug -DPAHO_WITH_SSL=TRUE -DOPENSSL_ROOT_DIR=$OPENSSL_ROOT_DIR -DPAHO_BUILD_DOCUMENTATION=FALSE -DPAHO_BUILD_SAMPLES=TRUE -DPAHO_HIGH_PERFORMANCE=$PAHO_HIGH_PERFORMANCE .. +cmake -DPAHO_BUILD_STATIC=$PAHO_BUILD_STATIC -DPAHO_BUILD_SHARED=$PAHO_BUILD_SHARED -DCMAKE_BUILD_TYPE=Debug -DPAHO_WITH_SSL=TRUE -DOPENSSL_ROOT_DIR=$OPENSSL_ROOT_DIR -DPAHO_BUILD_DOCUMENTATION=FALSE -DPAHO_BUILD_SAMPLES=TRUE -DPAHO_HIGH_PERFORMANCE=$PAHO_HIGH_PERFORMANCE -DPAHO_USE_SELECT=$PAHO_USE_SELECT .. cmake --build . python3 ../test/mqttsas.py & ctest -VV --timeout 600 diff --git a/version.patch b/version.patch index f11c82a4c..9a037142a 100644 --- a/version.patch +++ b/version.patch @@ -1 +1 @@ -9 \ No newline at end of file +10 \ No newline at end of file