From 8459ece206c87fc76cc659d683e2206f0f3c78b5 Mon Sep 17 00:00:00 2001 From: Ian Craggs Date: Tue, 27 Oct 2020 19:24:26 +0000 Subject: [PATCH] Split MQTTAsync.c as it is getting a bit big --- Makefile | 4 +- src/CMakeLists.txt | 8 +- src/MQTTAsync.c | 3990 +++++------------------------------------- src/MQTTAsyncUtils.c | 3082 ++++++++++++++++++++++++++++++++ src/MQTTAsyncUtils.h | 178 ++ 5 files changed, 3661 insertions(+), 3601 deletions(-) create mode 100644 src/MQTTAsyncUtils.c create mode 100644 src/MQTTAsyncUtils.h diff --git a/Makefile b/Makefile index 08a627dd..e1a7eab6 100644 --- a/Makefile +++ b/Makefile @@ -84,8 +84,8 @@ man2dir = $(mandir)/man2 man3dir = $(mandir)/man3 SOURCE_FILES = $(wildcard $(srcdir)/*.c) -SOURCE_FILES_C = $(filter-out $(srcdir)/MQTTAsync.c $(srcdir)/MQTTVersion.c $(srcdir)/SSLSocket.c, $(SOURCE_FILES)) -SOURCE_FILES_CS = $(filter-out $(srcdir)/MQTTAsync.c $(srcdir)/MQTTVersion.c, $(SOURCE_FILES)) +SOURCE_FILES_C = $(filter-out $(srcdir)/MQTTAsync.c $(srcdir)/MQTTAsyncUtils.c $(srcdir)/MQTTVersion.c $(srcdir)/SSLSocket.c, $(SOURCE_FILES)) +SOURCE_FILES_CS = $(filter-out $(srcdir)/MQTTAsync.c $(srcdir)/MQTTAsyncUtils.c $(srcdir)/MQTTVersion.c, $(SOURCE_FILES)) SOURCE_FILES_A = $(filter-out $(srcdir)/MQTTClient.c $(srcdir)/MQTTVersion.c $(srcdir)/SSLSocket.c, $(SOURCE_FILES)) SOURCE_FILES_AS = $(filter-out $(srcdir)/MQTTClient.c $(srcdir)/MQTTVersion.c, $(SOURCE_FILES)) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 20b578ab..4137d380 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -98,7 +98,7 @@ ENDIF() IF (PAHO_BUILD_SHARED) ADD_LIBRARY(paho-mqtt3c SHARED $ MQTTClient.c) - ADD_LIBRARY(paho-mqtt3a SHARED $ MQTTAsync.c) + ADD_LIBRARY(paho-mqtt3a SHARED $ MQTTAsync.c MQTTAsyncUtils.c) TARGET_LINK_LIBRARIES(paho-mqtt3c ${LIBS_SYSTEM}) TARGET_LINK_LIBRARIES(paho-mqtt3a ${LIBS_SYSTEM}) @@ -147,7 +147,7 @@ ENDIF() IF (PAHO_BUILD_STATIC) ADD_LIBRARY(paho-mqtt3c-static STATIC $ MQTTClient.c) - ADD_LIBRARY(paho-mqtt3a-static STATIC $ MQTTAsync.c) + ADD_LIBRARY(paho-mqtt3a-static STATIC $ MQTTAsync.c MQTTAsyncUtils.c) TARGET_LINK_LIBRARIES(paho-mqtt3c-static ${LIBS_SYSTEM}) TARGET_LINK_LIBRARIES(paho-mqtt3a-static ${LIBS_SYSTEM}) @@ -198,7 +198,7 @@ IF (PAHO_WITH_SSL) SET_PROPERTY(TARGET common_ssl_obj PROPERTY COMPILE_DEFINITIONS "OPENSSL=1;PAHO_MQTT_EXPORTS=1") ADD_LIBRARY(paho-mqtt3cs SHARED $ MQTTClient.c SSLSocket.c) - ADD_LIBRARY(paho-mqtt3as SHARED $ MQTTAsync.c SSLSocket.c) + ADD_LIBRARY(paho-mqtt3as SHARED $ MQTTAsync.c MQTTAsyncUtils.c SSLSocket.c) SET_TARGET_PROPERTIES( paho-mqtt3cs paho-mqtt3as PROPERTIES @@ -250,7 +250,7 @@ IF (PAHO_WITH_SSL) SET_PROPERTY(TARGET common_ssl_obj_static PROPERTY COMPILE_DEFINITIONS "OPENSSL=1;PAHO_MQTT_STATIC=1") ADD_LIBRARY(paho-mqtt3cs-static STATIC $ MQTTClient.c SSLSocket.c) - ADD_LIBRARY(paho-mqtt3as-static STATIC $ MQTTAsync.c SSLSocket.c) + ADD_LIBRARY(paho-mqtt3as-static STATIC $ MQTTAsync.c MQTTAsyncUtils.c SSLSocket.c) SET_TARGET_PROPERTIES( paho-mqtt3cs-static paho-mqtt3as-static PROPERTIES diff --git a/src/MQTTAsync.c b/src/MQTTAsync.c index 783d98d5..83fed2fd 100644 --- a/src/MQTTAsync.c +++ b/src/MQTTAsync.c @@ -36,14 +36,9 @@ * Ian Craggs - check for NULL SSL options #334 * Ian Craggs - allocate username/password buffers #431 * Ian Craggs - MQTT 5.0 support + * Ian Craggs - refactor to reduce module size *******************************************************************************/ -/** - * @file - * \brief Asynchronous API implementation - * - */ - #include #include #if !defined(_WIN32) && !defined(_WIN64) @@ -54,6 +49,7 @@ #include "MQTTPersistence.h" #endif #include "MQTTAsync.h" +#include "MQTTAsyncUtils.h" #include "utf-8.h" #include "MQTTProtocol.h" #include "MQTTProtocolOut.h" @@ -64,15 +60,32 @@ #include "OsWrapper.h" #include "WebSocket.h" -#define URI_TCP "tcp://" -#define URI_WS "ws://" -#define URI_WSS "wss://" +static void MQTTAsync_freeServerURIs(MQTTAsyncs* m); #include "VersionInfo.h" const char *client_timestamp_eye = "MQTTAsyncV3_Timestamp " BUILD_TIMESTAMP; const char *client_version_eye = "MQTTAsyncV3_Version " CLIENT_VERSION; +volatile int global_initialized = 0; +List* MQTTAsync_handles = NULL; +List* MQTTAsync_commands = NULL; +int MQTTAsync_tostop = 0; + +static ClientStates ClientState = +{ + CLIENT_VERSION, /* version */ + NULL /* client list */ +}; + +MQTTProtocol state; +ClientStates* bstate = &ClientState; + +enum MQTTAsync_threadStates sendThread_state = STOPPED; +enum MQTTAsync_threadStates receiveThread_state = STOPPED; +thread_id_type sendThread_id = 0, + receiveThread_id = 0; + // global objects init declaration int MQTTAsync_init(void); @@ -92,7 +105,7 @@ void MQTTAsync_global_init(MQTTAsync_init_options* inits) void MQTTAsync_init_rand(void) { START_TIME_TYPE now = MQTTTime_start_clock(); - srand(now); + srand((unsigned int)now); } #elif defined(AIX) void MQTTAsync_init_rand(void) @@ -108,31 +121,11 @@ void MQTTAsync_init_rand(void) } #endif -static ClientStates ClientState = -{ - CLIENT_VERSION, /* version */ - NULL /* client list */ -}; - -ClientStates* bstate = &ClientState; - -MQTTProtocol state; - -enum MQTTAsync_threadStates -{ - STOPPED, STARTING, RUNNING, STOPPING -}; - -enum MQTTAsync_threadStates sendThread_state = STOPPED; -enum MQTTAsync_threadStates receiveThread_state = STOPPED; -static thread_id_type sendThread_id = 0, - receiveThread_id = 0; - #if defined(_WIN32) || defined(_WIN64) -static mutex_type mqttasync_mutex = NULL; -static mutex_type socket_mutex = NULL; -static mutex_type mqttcommand_mutex = NULL; -static sem_type send_sem = NULL; +mutex_type mqttasync_mutex = NULL; +mutex_type socket_mutex = NULL; +mutex_type mqttcommand_mutex = NULL; +sem_type send_sem = NULL; #if !defined(NO_HEAP_TRACKING) extern mutex_type stack_mutex; extern mutex_type heap_mutex; @@ -259,16 +252,16 @@ BOOL APIENTRY DllMain(HANDLE hModule, #else static pthread_mutex_t mqttasync_mutex_store = PTHREAD_MUTEX_INITIALIZER; -static mutex_type mqttasync_mutex = &mqttasync_mutex_store; +mutex_type mqttasync_mutex = &mqttasync_mutex_store; static pthread_mutex_t socket_mutex_store = PTHREAD_MUTEX_INITIALIZER; -static mutex_type socket_mutex = &socket_mutex_store; +mutex_type socket_mutex = &socket_mutex_store; static pthread_mutex_t mqttcommand_mutex_store = PTHREAD_MUTEX_INITIALIZER; -static mutex_type mqttcommand_mutex = &mqttcommand_mutex_store; +mutex_type mqttcommand_mutex = &mqttcommand_mutex_store; static cond_type_struct send_cond_store = { PTHREAD_COND_INITIALIZER, PTHREAD_MUTEX_INITIALIZER }; -static cond_type send_cond = &send_cond_store; +cond_type send_cond = &send_cond_store; int MQTTAsync_init(void) { @@ -298,279 +291,6 @@ int MQTTAsync_init(void) #define WINAPI #endif -static volatile int global_initialized = 0; -static List* handles = NULL; -static int tostop = 0; -static List* commands = NULL; - -typedef struct -{ - MQTTAsync_message* msg; - char* topicName; - int topicLen; - unsigned int seqno; /* only used on restore */ -} qEntry; - -typedef struct -{ - int type; - MQTTAsync_onSuccess* onSuccess; - MQTTAsync_onFailure* onFailure; - MQTTAsync_onSuccess5* onSuccess5; - MQTTAsync_onFailure5* onFailure5; - MQTTAsync_token token; - void* context; - START_TIME_TYPE start_time; - MQTTProperties properties; - union - { - struct - { - int count; - char** topics; - int* qoss; - MQTTSubscribe_options opts; - MQTTSubscribe_options* optlist; - } sub; - struct - { - int count; - char** topics; - } unsub; - struct - { - char* destinationName; - int payloadlen; - void* payload; - int qos; - int retained; - } pub; - struct - { - int internal; - int timeout; - enum MQTTReasonCodes reasonCode; - } dis; - struct - { - int currentURI; - int MQTTVersion; /**< current MQTT version being used to connect */ - } conn; - } details; -} MQTTAsync_command; - - -typedef struct MQTTAsync_struct -{ - char* serverURI; - int ssl; - int websocket; - Clients* c; - - /* "Global", to the client, callback definitions */ - MQTTAsync_connectionLost* cl; - MQTTAsync_messageArrived* ma; - MQTTAsync_deliveryComplete* dc; - void* clContext; /* the context to be associated with the conn lost callback*/ - void* maContext; /* the context to be associated with the msg arrived callback*/ - void* dcContext; /* the context to be associated with the deliv complete callback*/ - - MQTTAsync_connected* connected; - void* connected_context; /* the context to be associated with the connected callback*/ - - MQTTAsync_disconnected* disconnected; - void* disconnected_context; /* the context to be associated with the disconnected callback*/ - - MQTTAsync_updateConnectOptions* updateConnectOptions; - void* updateConnectOptions_context; - - /* Each time connect is called, we store the options that were used. These are reused in - any call to reconnect, or an automatic reconnect attempt */ - MQTTAsync_command connect; /* Connect operation properties */ - MQTTAsync_command disconnect; /* Disconnect operation properties */ - MQTTAsync_command* pending_write; /* Is there a socket write pending? */ - - List* responses; - unsigned int command_seqno; - - MQTTPacket* pack; - - /* added for offline buffering */ - MQTTAsync_createOptions* createOptions; - int shouldBeConnected; - int noBufferedMessages; /* the current number of buffered (publish) messages for this client */ - - /* added for automatic reconnect */ - int automaticReconnect; - int minRetryInterval; - int maxRetryInterval; - int serverURIcount; - char** serverURIs; - int connectTimeout; - - int currentInterval; - int currentIntervalBase; - START_TIME_TYPE lastConnectionFailedTime; - int retrying; - int reconnectNow; - - /* MQTT V5 properties */ - MQTTProperties* connectProps; - MQTTProperties* willProps; - -} MQTTAsyncs; - - -typedef struct -{ - MQTTAsync_command command; - MQTTAsyncs* client; - unsigned int seqno; /* only used on restore */ - int not_restored; - char* key; /* if not_restored, this holds the key */ -} MQTTAsync_queuedCommand; - - -static int clientSockCompare(void* a, void* b); -static void MQTTAsync_lock_mutex(mutex_type amutex); -static void MQTTAsync_unlock_mutex(mutex_type amutex); -static int MQTTAsync_checkConn(MQTTAsync_command* command, MQTTAsyncs* client); -static void MQTTAsync_terminate(void); -#if !defined(NO_PERSISTENCE) -static int MQTTAsync_unpersistCommand(MQTTAsync_queuedCommand* qcmd); -static int MQTTAsync_persistCommand(MQTTAsync_queuedCommand* qcmd); -static MQTTAsync_queuedCommand* MQTTAsync_restoreCommand(char* buffer, int buflen, int MQTTVersion, MQTTAsync_queuedCommand*); -/*static void MQTTAsync_insertInOrder(List* list, void* content, int size);*/ -static int MQTTAsync_restoreCommands(MQTTAsyncs* client); -#endif -static int MQTTAsync_addCommand(MQTTAsync_queuedCommand* command, int command_size); -static void MQTTAsync_startConnectRetry(MQTTAsyncs* m); -static void MQTTAsync_checkDisconnect(MQTTAsync handle, MQTTAsync_command* command); -static void MQTTProtocol_checkPendingWrites(void); -static void MQTTAsync_freeServerURIs(MQTTAsyncs* m); -static void MQTTAsync_freeCommand1(MQTTAsync_queuedCommand *command); -static void MQTTAsync_freeCommand(MQTTAsync_queuedCommand *command); -static void MQTTAsync_writeComplete(int socket, int rc); -static int MQTTAsync_processCommand(void); -static void MQTTAsync_checkTimeouts(void); -static thread_return_type WINAPI MQTTAsync_sendThread(void* n); -static void MQTTAsync_emptyMessageQueue(Clients* client); -static void MQTTAsync_freeResponses(MQTTAsyncs* m); -static void MQTTAsync_freeCommands(MQTTAsyncs* m); -static int MQTTAsync_unpersistCommandsAndMessages(Clients* c); -static int MQTTAsync_completeConnection(MQTTAsyncs* m, Connack* connack); -static thread_return_type WINAPI MQTTAsync_receiveThread(void* n); -static void MQTTAsync_stop(void); -static void MQTTAsync_closeOnly(Clients* client, enum MQTTReasonCodes reasonCode, MQTTProperties* props); -static void MQTTAsync_closeSession(Clients* client, enum MQTTReasonCodes reasonCode, MQTTProperties* props); -static int clientStructCompare(void* a, void* b); -static int MQTTAsync_cleanSession(Clients* client); -static int MQTTAsync_deliverMessage(MQTTAsyncs* m, char* topicName, size_t topicLen, MQTTAsync_message* mm); -static int MQTTAsync_disconnect1(MQTTAsync handle, const MQTTAsync_disconnectOptions* options, int internal); -static int MQTTAsync_disconnect_internal(MQTTAsync handle, int timeout); -static int cmdMessageIDCompare(void* a, void* b); -static int MQTTAsync_assignMsgId(MQTTAsyncs* m); -static int MQTTAsync_getNoBufferedMessages(MQTTAsyncs* m); -static void MQTTAsync_retry(void); -static int MQTTAsync_connecting(MQTTAsyncs* m); -static MQTTPacket* MQTTAsync_cycle(int* sock, unsigned long timeout, int* rc); -/*static int pubCompare(void* a, void* b);*/ - - -void MQTTAsync_sleep(long milliseconds) -{ - FUNC_ENTRY; -#if defined(_WIN32) || defined(_WIN64) - Sleep(milliseconds); -#else - usleep(milliseconds*1000); -#endif - FUNC_EXIT; -} - - -/* Add random amount of jitter for exponential backoff on retry - Jitter value will be +/- 20% of "base" interval, including max interval - https://www.awsarchitectureblog.com/2015/03/backoff.html - http://ee.lbl.gov/papers/sync_94.pdf */ -int MQTTAsync_randomJitter(int currentIntervalBase, int minInterval, int maxInterval) -{ - const int max_sleep = (int)(min(maxInterval, currentIntervalBase) * 1.2); // (e.g. 72 if base > 60) - const int min_sleep = (int)(max(minInterval, currentIntervalBase) / 1.2); // (e.g. 48 if base > 60) - - if (min_sleep >= max_sleep) // shouldn't happen, but just in case - { - return min_sleep; - } - - { - /* random_between(min_sleep, max_sleep) - http://stackoverflow.com/questions/2509679/how-to-generate-a-random-number-from-within-a-range */ - int r; - int range = max_sleep - min_sleep + 1; - const int buckets = RAND_MAX / range; - const int limit = buckets * range; - - /* Create equal size buckets all in a row, then fire randomly towards - * the buckets until you land in one of them. All buckets are equally - * likely. If you land off the end of the line of buckets, try again. */ - do - { - r = rand(); - } while (r >= limit); - - { - const int randResult = r / buckets; - return min_sleep + randResult; - } - } -} - - -/** - * List callback function for comparing clients by socket - * @param a first integer value - * @param b second integer value - * @return boolean indicating whether a and b are equal - */ -static int clientSockCompare(void* a, void* b) -{ - MQTTAsyncs* m = (MQTTAsyncs*)a; - return m->c->net.socket == *(int*)b; -} - - -static void MQTTAsync_lock_mutex(mutex_type amutex) -{ - int rc = Thread_lock_mutex(amutex); - if (rc != 0) - Log(LOG_ERROR, 0, "Error %s locking mutex", strerror(rc)); -} - - -static void MQTTAsync_unlock_mutex(mutex_type amutex) -{ - int rc = Thread_unlock_mutex(amutex); - if (rc != 0) - Log(LOG_ERROR, 0, "Error %s unlocking mutex", strerror(rc)); -} - - -/* - Check whether there are any more connect options. If not then we are finished - with connect attempts. -*/ -static int MQTTAsync_checkConn(MQTTAsync_command* command, MQTTAsyncs* client) -{ - int rc; - - FUNC_ENTRY; - rc = command->details.conn.currentURI + 1 < client->serverURIcount || - (command->details.conn.MQTTVersion == 4 && client->c->MQTTVersion == MQTTVERSION_DEFAULT); - FUNC_EXIT_RC(rc); - return rc; -} - int MQTTAsync_createWithOptions(MQTTAsync* handle, const char* serverURI, const char* clientId, int persistence_type, void* persistence_context, MQTTAsync_createOptions* options) @@ -634,8 +354,8 @@ int MQTTAsync_createWithOptions(MQTTAsync* handle, const char* serverURI, const bstate->clients = ListInitialize(); Socket_outInitialize(); Socket_setWriteCompleteCallback(MQTTAsync_writeComplete); - handles = ListInitialize(); - commands = ListInitialize(); + MQTTAsync_handles = ListInitialize(); + MQTTAsync_commands = ListInitialize(); #if defined(OPENSSL) SSLSocket_initialize(); #endif @@ -674,7 +394,7 @@ int MQTTAsync_createWithOptions(MQTTAsync* handle, const char* serverURI, const goto exit; } m->responses = ListInitialize(); - ListAppend(handles, m, sizeof(MQTTAsyncs)); + ListAppend(MQTTAsync_handles, m, sizeof(MQTTAsyncs)); if ((m->c = malloc(sizeof(Clients))) == NULL) { @@ -744,1837 +464,6 @@ int MQTTAsync_create(MQTTAsync* handle, const char* serverURI, const char* clien } -static void MQTTAsync_terminate(void) -{ - FUNC_ENTRY; - MQTTAsync_stop(); - if (global_initialized) - { - ListElement* elem = NULL; - ListFree(bstate->clients); - ListFree(handles); - while (ListNextElement(commands, &elem)) - MQTTAsync_freeCommand1((MQTTAsync_queuedCommand*)(elem->content)); - ListFree(commands); - handles = NULL; - WebSocket_terminate(); - #if !defined(NO_HEAP_TRACKING) - Heap_terminate(); - #endif - Log_terminate(); - global_initialized = 0; - } - FUNC_EXIT; -} - - -#if !defined(NO_PERSISTENCE) -static int MQTTAsync_unpersistCommand(MQTTAsync_queuedCommand* qcmd) -{ - int rc = 0; - char key[PERSISTENCE_MAX_KEY_LENGTH + 1]; - - FUNC_ENTRY; - if (qcmd->client->c->MQTTVersion >= MQTTVERSION_5) - sprintf(key, "%s%u", PERSISTENCE_V5_COMMAND_KEY, qcmd->seqno); - else - sprintf(key, "%s%u", PERSISTENCE_COMMAND_KEY, qcmd->seqno); - if ((rc = qcmd->client->c->persistence->premove(qcmd->client->c->phandle, key)) != 0) - Log(LOG_ERROR, 0, "Error %d removing command from persistence", rc); - FUNC_EXIT_RC(rc); - return rc; -} - - -static int MQTTAsync_persistCommand(MQTTAsync_queuedCommand* qcmd) -{ - int rc = 0; - MQTTAsyncs* aclient = qcmd->client; - MQTTAsync_command* command = &qcmd->command; - int* lens = NULL; - void** bufs = NULL; - int bufindex = 0, i, nbufs = 0; - char key[PERSISTENCE_MAX_KEY_LENGTH + 1]; - int props_allocated = 0; - int process = 1; - - FUNC_ENTRY; - switch (command->type) - { - case SUBSCRIBE: - nbufs = ((aclient->c->MQTTVersion >= MQTTVERSION_5) ? 4 : 3) + - (command->details.sub.count * 2); - - if (((lens = (int*)malloc(nbufs * sizeof(int))) == NULL) || - ((bufs = malloc(nbufs * sizeof(char *))) == NULL)) - { - rc = PAHO_MEMORY_ERROR; - goto exit; - } - bufs[bufindex] = &command->type; - lens[bufindex++] = sizeof(command->type); - - bufs[bufindex] = &command->token; - lens[bufindex++] = sizeof(command->token); - - bufs[bufindex] = &command->details.sub.count; - lens[bufindex++] = sizeof(command->details.sub.count); - - for (i = 0; i < command->details.sub.count; ++i) - { - 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 - { - if (command->details.sub.count == 1) - { - bufs[bufindex] = &command->details.sub.opts; - lens[bufindex++] = sizeof(command->details.sub.opts); - } - else - { - bufs[bufindex] = &command->details.sub.optlist[i]; - lens[bufindex++] = sizeof(command->details.sub.optlist[i]); - } - } - } - break; - - case UNSUBSCRIBE: - nbufs = ((aclient->c->MQTTVersion >= MQTTVERSION_5) ? 4 : 3) + - command->details.unsub.count; - - if (((lens = (int*)malloc(nbufs * sizeof(int))) == NULL) || - ((bufs = malloc(nbufs * sizeof(char *))) == NULL)) - { - rc = PAHO_MEMORY_ERROR; - goto exit; - } - - bufs[bufindex] = &command->type; - lens[bufindex++] = sizeof(command->type); - - bufs[bufindex] = &command->token; - lens[bufindex++] = sizeof(command->token); - - bufs[bufindex] = &command->details.unsub.count; - lens[bufindex++] = sizeof(command->details.unsub.count); - - for (i = 0; i < command->details.unsub.count; ++i) - { - bufs[bufindex] = command->details.unsub.topics[i]; - lens[bufindex++] = (int)strlen(command->details.unsub.topics[i]) + 1; - } - break; - - case PUBLISH: - nbufs = (aclient->c->MQTTVersion >= MQTTVERSION_5) ? 8 : 7; - - if (((lens = (int*)malloc(nbufs * sizeof(int))) == NULL) || - ((bufs = malloc(nbufs * sizeof(char *))) == NULL)) - { - rc = PAHO_MEMORY_ERROR; - goto exit; - } - - bufs[bufindex] = &command->type; - lens[bufindex++] = sizeof(command->type); - - bufs[bufindex] = &command->token; - lens[bufindex++] = sizeof(command->token); - - bufs[bufindex] = command->details.pub.destinationName; - lens[bufindex++] = (int)strlen(command->details.pub.destinationName) + 1; - - bufs[bufindex] = &command->details.pub.payloadlen; - lens[bufindex++] = sizeof(command->details.pub.payloadlen); - - bufs[bufindex] = command->details.pub.payload; - lens[bufindex++] = command->details.pub.payloadlen; - - bufs[bufindex] = &command->details.pub.qos; - lens[bufindex++] = sizeof(command->details.pub.qos); - - bufs[bufindex] = &command->details.pub.retained; - lens[bufindex++] = sizeof(command->details.pub.retained); - break; - - default: - process = 0; - break; - } - - /* - * Increment the command sequence number. Don't exceed the maximum value allowed - * by the value PERSISTENCE_MAX_KEY_LENGTH minus the max prefix string length - */ - if (++aclient->command_seqno == PERSISTENCE_SEQNO_LIMIT) - aclient->command_seqno = 0; - - - if (aclient->c->MQTTVersion >= MQTTVERSION_5 && process) /* persist properties */ - { - int temp_len = 0; - char* ptr = NULL; - - temp_len = MQTTProperties_len(&command->properties); - if ((ptr = bufs[bufindex] = malloc(temp_len)) == NULL) - { - rc = PAHO_MEMORY_ERROR; - goto exit; - } - props_allocated = bufindex; - rc = MQTTProperties_write(&ptr, &command->properties); - lens[bufindex++] = temp_len; - sprintf(key, "%s%u", PERSISTENCE_V5_COMMAND_KEY, aclient->command_seqno); - } - else - sprintf(key, "%s%u", PERSISTENCE_COMMAND_KEY, aclient->command_seqno); - - if (nbufs > 0) - { - if (aclient->c->beforeWrite) - rc = aclient->c->beforeWrite(aclient->c->beforeWrite_context, nbufs, (char**)bufs, lens); - - if ((rc = aclient->c->persistence->pput(aclient->c->phandle, key, nbufs, (char**)bufs, lens)) != 0) - Log(LOG_ERROR, 0, "Error persisting command, rc %d", rc); - qcmd->seqno = aclient->command_seqno; - } -exit: - if (props_allocated > 0) - free(bufs[props_allocated]); - if (lens) - free(lens); - if (bufs) - free(bufs); - FUNC_EXIT_RC(rc); - return rc; -} - - -static MQTTAsync_queuedCommand* MQTTAsync_restoreCommand(char* buffer, int buflen, int MQTTVersion, MQTTAsync_queuedCommand* qcommand) -{ - MQTTAsync_command* command = NULL; - char* ptr = buffer; - int i; - size_t data_size; - - FUNC_ENTRY; - if (qcommand == NULL) - { - if ((qcommand = malloc(sizeof(MQTTAsync_queuedCommand))) == NULL) - goto exit; - memset(qcommand, '\0', sizeof(MQTTAsync_queuedCommand)); - qcommand->not_restored = 1; /* don't restore all the command on the first call */ - } - else - qcommand->not_restored = 0; - - command = &qcommand->command; - - command->type = *(int*)ptr; - ptr += sizeof(int); - - command->token = *(MQTTAsync_token*)ptr; - ptr += sizeof(MQTTAsync_token); - - switch (command->type) - { - case SUBSCRIBE: - if (qcommand->not_restored == 0) - break; - command->details.sub.count = *(int*)ptr; - ptr += sizeof(int); - - if (command->details.sub.count > 0) - { - if ((command->details.sub.topics = (char **)malloc(sizeof(char *) * command->details.sub.count)) == NULL) - { - free(qcommand); - qcommand = NULL; - goto exit; - } - if (MQTTVersion < MQTTVERSION_5) - { - if ((command->details.sub.qoss = (int *)malloc(sizeof(int) * command->details.sub.count)) == NULL) - { - free(qcommand); - qcommand = NULL; - goto exit; - } - } - else 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) - { - free(qcommand); - qcommand = NULL; - goto exit; - } - } - } - - for (i = 0; i < command->details.sub.count; ++i) - { - data_size = strlen(ptr) + 1; - - if ((command->details.sub.topics[i] = malloc(data_size)) == NULL) - { - free(qcommand); - qcommand = NULL; - goto exit; - } - strcpy(command->details.sub.topics[i], ptr); - ptr += data_size; - - if (MQTTVersion < MQTTVERSION_5) - { - command->details.sub.qoss[i] = *(int*)ptr; - ptr += sizeof(int); - } - else - { - if (command->details.sub.count == 1) - { - command->details.sub.opts = *(MQTTSubscribe_options*)ptr; - ptr += sizeof(MQTTSubscribe_options); - } - else - { - command->details.sub.optlist[i] = *(MQTTSubscribe_options*)ptr; - ptr += sizeof(MQTTSubscribe_options); - } - } - } - break; - - case UNSUBSCRIBE: - if (qcommand->not_restored == 0) - break; - command->details.unsub.count = *(int*)ptr; - ptr += sizeof(int); - - if (command->details.unsub.count > 0) - { - command->details.unsub.topics = (char **)malloc(sizeof(char *) * command->details.unsub.count); - if (command->details.unsub.topics == NULL) - { - free(qcommand); - qcommand = NULL; - goto exit; - } - } - - for (i = 0; i < command->details.unsub.count; ++i) - { - data_size = strlen(ptr) + 1; - - if ((command->details.unsub.topics[i] = malloc(data_size)) == NULL) - { - free(qcommand); - qcommand = NULL; - goto exit; - } - strcpy(command->details.unsub.topics[i], ptr); - ptr += data_size; - } - break; - - case PUBLISH: - data_size = strlen(ptr) + 1; - if (qcommand->not_restored == 0) - { - if ((command->details.pub.destinationName = malloc(data_size)) == NULL) - { - free(qcommand); - qcommand = NULL; - goto exit; - } - strcpy(command->details.pub.destinationName, ptr); - } - ptr += data_size; - - command->details.pub.payloadlen = *(int*)ptr; - ptr += sizeof(int); - - data_size = command->details.pub.payloadlen; - if (qcommand->not_restored == 0) - { - if ((command->details.pub.payload = malloc(data_size)) == NULL) - { - free(qcommand); - qcommand = NULL; - goto exit; - } - memcpy(command->details.pub.payload, ptr, data_size); - } - ptr += data_size; - - command->details.pub.qos = *(int*)ptr; - ptr += sizeof(int); - - command->details.pub.retained = *(int*)ptr; - ptr += sizeof(int); - break; - - default: - free(qcommand); - qcommand = NULL; - - } - if (qcommand != NULL && qcommand->not_restored == 0 && MQTTVersion >= MQTTVERSION_5 && - MQTTProperties_read(&command->properties, &ptr, buffer + buflen) != 1) - { - Log(LOG_ERROR, -1, "Error restoring properties from persistence"); - free(qcommand); - qcommand = NULL; - } -exit: - FUNC_EXIT; - return qcommand; -} - -struct keyloc { - int seqno; - ListElement* elem; -}; - -/** - * Inserts the specified message into the list, maintaining command sequence no order. - * @param list the list to insert the message into. - * @param content the message to add. - * @param size size of the message. - */ -static void MQTTAsync_insertInOrder(List* list, void* content, int size, struct keyloc* keyloc_array, int array_size) -{ - ListElement* insert_point = NULL; - ListElement* inserted = NULL; - int seqno = ((MQTTAsync_queuedCommand*)content)->seqno; - int low = 0; - - FUNC_ENTRY; - /*printf("\nseqno %d array_size %d\n", seqno, array_size); - - int i; - for (i = 0; i < array_size; ++i) - printf("%d ", keyloc_array[i].seqno); - printf("\n"); */ - - /* look through keyloc array to find location to insert message - binary search by sequence number */ - if (array_size > 0 && seqno > keyloc_array[array_size - 1].seqno) - { - low = array_size - 1; - } - else if (array_size > 0) - { - int high = array_size - 1; - int divide_index = array_size / 2; - - //printf("divide index %d\n", divide_index); - //int count = 0; - while (high - low > 1) - { - if (seqno < keyloc_array[divide_index].seqno) - high = divide_index; - else - low = divide_index; - - divide_index = (high - low) / 2 + low; - /*printf("high %d low %d divide_index %d\n", high, low, divide_index); - if (++count == 10) - exit(99);*/ - } - } - //printf("low %d\n", low); - - insert_point = keyloc_array[low].elem; - if (insert_point) - insert_point = insert_point->next; - - //if (insert_point) - // printf("insert after %d %d\n", keyloc_array[low].seqno, ((MQTTAsync_queuedCommand*)insert_point->content)->seqno); - inserted = ListInsert(list, content, size, insert_point); - - if (array_size > 0 && low + 1 < array_size) - { - /* make space for new entry in array after low point*/ - //printf("array_size - low %d\n", array_size - low); - memmove(&keyloc_array[low+2], &keyloc_array[low+1], (array_size - low - 1) * sizeof(struct keyloc)); - } - keyloc_array[low+1].seqno = seqno; - keyloc_array[low+1].elem = inserted; - - /*if (array_size > 50) - exit(99);*/ - - FUNC_EXIT; -} - -static int cmpkeys(const void *p1, const void *p2) -{ - int key1 = atoi(strchr(*(char * const *)p1, '-') + 1); - int key2 = atoi(strchr(*(char * const *)p2, '-') + 1); - - return (key1 == key2) ? 0 : ((key1 < key2) ? -1 : 1); -} - - -static int MQTTAsync_restoreCommands(MQTTAsyncs* client) -{ - int rc = 0; - char **msgkeys; - int nkeys; - int i = 0; - Clients* c = client->c; - int commands_restored = 0; - - FUNC_ENTRY; - if (c->persistence && (rc = c->persistence->pkeys(c->phandle, &msgkeys, &nkeys)) == 0 && nkeys > 0) - { - MQTTAsync_queuedCommand* sentinel = NULL; - /* keep track of location of nkeys key locations for fast insert */ - struct keyloc* keyloc_array = malloc(sizeof(struct keyloc) * (nkeys + 1)); - - /* let's have the sequence number array sorted */ - qsort(msgkeys, (size_t)nkeys, sizeof(char*), cmpkeys); - /*for (i = 0; i < nkeys; ++i) printf("%s ", msgkeys[i]); printf("\n");*/ - if (keyloc_array == NULL) - { - rc = PAHO_MEMORY_ERROR; - goto exit; - } - sentinel = malloc(sizeof(MQTTAsync_queuedCommand)); - if (sentinel == NULL) - { - free(keyloc_array); - rc = PAHO_MEMORY_ERROR; - goto exit; - } - sentinel->seqno = -1; - keyloc_array[0].seqno = -1; - keyloc_array[0].elem = ListAppend(commands, sentinel, sizeof(MQTTAsync_queuedCommand)); - - while (rc == 0 && i < nkeys) - { - char *buffer = NULL; - int buflen; - - if (strncmp(msgkeys[i], PERSISTENCE_COMMAND_KEY, strlen(PERSISTENCE_COMMAND_KEY)) != 0 && - strncmp(msgkeys[i], PERSISTENCE_V5_COMMAND_KEY, strlen(PERSISTENCE_V5_COMMAND_KEY)) != 0) - { - ; - } - else - { - MQTTAsync_queuedCommand* cmd = NULL; - if ((rc = c->persistence->pget(c->phandle, msgkeys[i], &buffer, &buflen)) == 0 && - (c->afterRead == NULL || (rc = c->afterRead(c->afterRead_context, &buffer, &buflen)) == 0)) - { - int MQTTVersion = (strncmp(msgkeys[i], PERSISTENCE_V5_COMMAND_KEY, strlen(PERSISTENCE_V5_COMMAND_KEY)) == 0) - ? MQTTVERSION_5 : MQTTVERSION_3_1_1; - cmd = MQTTAsync_restoreCommand(buffer, buflen, MQTTVersion, NULL); - /* As the entire command is not restored on the first read to save memory, we temporarily store - * the filename of the persisted command to be used when restoreCommand is called the second time. - */ - cmd->key = malloc(strlen(msgkeys[i])+1); - strcpy(cmd->key, msgkeys[i]); - } - - if (cmd) - { - cmd->client = client; - cmd->seqno = atoi(strchr(msgkeys[i], '-')+1); /* key format is tag'-'seqno */ - MQTTAsync_insertInOrder(commands, cmd, sizeof(MQTTAsync_queuedCommand), keyloc_array, i + 1); - if (buffer) - free(buffer); - client->command_seqno = max(client->command_seqno, cmd->seqno); - commands_restored++; - if (cmd->command.type == PUBLISH) - client->noBufferedMessages++; - } - } - if (msgkeys[i]) - free(msgkeys[i]); - i++; - } - if (msgkeys != NULL) - free(msgkeys); - - /*int j; for (j = 0; j < i + 1; ++j) printf("%d ", keyloc_array[j].seqno); printf("\n"); */ - ListRemoveHead(commands); /* remove sentinel */ - /*ListElement* pos = NULL; while (ListNextElement(commands, &pos)) printf("%d ", ((MQTTAsync_queuedCommand*)(pos->content))->seqno); printf("\n");*/ - - free(keyloc_array); - } -exit: - Log(TRACE_MINIMUM, -1, "%d commands restored for client %s", commands_restored, c->clientID); - FUNC_EXIT_RC(rc); - return rc; -} - - -static int MQTTAsync_unpersistCommandsAndMessages(Clients* c) -{ - int rc = 0; - char **msgkeys; - int nkeys; - int i = 0; - int messages_deleted = 0; - - FUNC_ENTRY; - if (c->persistence && (rc = c->persistence->pkeys(c->phandle, &msgkeys, &nkeys)) == 0) - { - while (rc == 0 && i < nkeys) - { - if (strncmp(msgkeys[i], PERSISTENCE_COMMAND_KEY, strlen(PERSISTENCE_COMMAND_KEY)) == 0 || - strncmp(msgkeys[i], PERSISTENCE_V5_COMMAND_KEY, strlen(PERSISTENCE_V5_COMMAND_KEY)) == 0 || - strncmp(msgkeys[i], PERSISTENCE_QUEUE_KEY, strlen(PERSISTENCE_QUEUE_KEY)) == 0 || - strncmp(msgkeys[i], PERSISTENCE_V5_QUEUE_KEY, strlen(PERSISTENCE_V5_QUEUE_KEY)) == 0) - { - if ((rc = c->persistence->premove(c->phandle, msgkeys[i])) == 0) - messages_deleted++; - else - Log(LOG_ERROR, 0, "Error %d removing queued message from persistence", rc); - } - if (msgkeys[i]) - free(msgkeys[i]); - i++; - } - if (msgkeys != NULL) - free(msgkeys); - } - Log(TRACE_MINIMUM, -1, "%d queued messages deleted for client %s", messages_deleted, c->clientID); - FUNC_EXIT_RC(rc); - return rc; -} - - -static int MQTTAsync_unpersistInflightMessages(Clients* c) -{ - int rc = 0; - char **msgkeys; - int nkeys; - int i = 0; - int messages_deleted = 0; - - FUNC_ENTRY; - if (c->persistence && (rc = c->persistence->pkeys(c->phandle, &msgkeys, &nkeys)) == 0) - { - while (rc == 0 && i < nkeys) - { - if (strncmp(msgkeys[i], PERSISTENCE_PUBLISH_SENT, strlen(PERSISTENCE_PUBLISH_SENT)) == 0 || - strncmp(msgkeys[i], PERSISTENCE_V5_PUBLISH_SENT, strlen(PERSISTENCE_V5_PUBLISH_SENT)) == 0 || - strncmp(msgkeys[i], PERSISTENCE_PUBREL, strlen(PERSISTENCE_PUBREL)) == 0 || - strncmp(msgkeys[i], PERSISTENCE_V5_PUBREL, strlen(PERSISTENCE_V5_PUBREL)) == 0 || - strncmp(msgkeys[i], PERSISTENCE_PUBLISH_RECEIVED, strlen(PERSISTENCE_PUBLISH_RECEIVED)) == 0 || - strncmp(msgkeys[i], PERSISTENCE_V5_PUBLISH_RECEIVED, strlen(PERSISTENCE_V5_PUBLISH_RECEIVED)) == 0) - { - if ((rc = c->persistence->premove(c->phandle, msgkeys[i])) == 0) - messages_deleted++; - else - Log(LOG_ERROR, 0, "Error %d removing inflight message from persistence", rc); - } - if (msgkeys[i]) - free(msgkeys[i]); - i++; - } - if (msgkeys != NULL) - free(msgkeys); - } - Log(TRACE_MINIMUM, -1, "%d inflight messages deleted for client %s", messages_deleted, c->clientID); - FUNC_EXIT_RC(rc); - return rc; -} -#endif - -/** - * List callback function for comparing client handles and command types being CONNECT or DISCONNECT - * @param a first MQTTAsync_queuedCommand pointer - * @param b second MQTTAsync_queuedCommand pointer - * @return boolean indicating whether a and b are equal - */ -static int clientCompareConnectCommand(void* a, void* b) -{ - MQTTAsync_queuedCommand* cmd1 = (MQTTAsync_queuedCommand*)a; - MQTTAsync_queuedCommand* cmd2 = (MQTTAsync_queuedCommand*)b; - if (cmd1->client == cmd2->client) - { - if (cmd1->command.type == cmd2->command.type) - { - if (cmd1->command.type == CONNECT || cmd1->command.type == DISCONNECT) - { - return 1; //Item found in the list - } - } - } - return 0; //Item NOT found in the list -} - -static int MQTTAsync_addCommand(MQTTAsync_queuedCommand* command, int command_size) -{ - int rc = MQTTASYNC_SUCCESS; - - FUNC_ENTRY; - MQTTAsync_lock_mutex(mqttcommand_mutex); - /* Don't set start time if the connect command is already in process #218 */ - if ((command->command.type != CONNECT) || (command->client->c->connect_state == NOT_IN_PROGRESS)) - command->command.start_time = MQTTTime_start_clock(); - if (command->command.type == CONNECT || - (command->command.type == DISCONNECT && command->command.details.dis.internal)) - { - MQTTAsync_queuedCommand* head = NULL; - - if (commands->first) - head = (MQTTAsync_queuedCommand*)(commands->first->content); - - if (head != NULL && head->client == command->client && head->command.type == command->command.type) - MQTTAsync_freeCommand(command); /* ignore duplicate connect or disconnect command */ - else - { - ListRemoveItem(commands, command, clientCompareConnectCommand); /* remove command from the list if already there */ - ListInsert(commands, command, command_size, commands->first); /* add to the head of the list */ - } - } - else - { - ListAppend(commands, command, command_size); -#if !defined(NO_PERSISTENCE) - if (command->client->c->persistence) - { - if (command->command.type == PUBLISH && - command->client->createOptions && command->client->createOptions->struct_version >= 2 && - command->client->createOptions->persistQoS0 == 0 && command->command.details.pub.qos == 0) - ; /* don't persist QoS0 if that create option is set to 0 */ - else - { - int rc = MQTTAsync_persistCommand(command); - if (command->command.type == PUBLISH && rc == 0) - { - char key[PERSISTENCE_MAX_KEY_LENGTH + 1]; - - command->not_restored = 1; - if (command->client->c->MQTTVersion >= MQTTVERSION_5) - sprintf(key, "%s%u", PERSISTENCE_V5_COMMAND_KEY, command->seqno); - else - sprintf(key, "%s%u", PERSISTENCE_COMMAND_KEY, command->seqno); - command->key = malloc(strlen(key+1)); - strcpy(command->key, key); - - free(command->command.details.pub.payload); - command->command.details.pub.payload = NULL; - free(command->command.details.pub.destinationName); - command->command.details.pub.destinationName = NULL; - MQTTProperties_free(&command->command.properties); - } - } - } -#endif - if (command->command.type == PUBLISH) - { - /* delete oldest message if buffer is full. We wouldn't be here if delete newest was in operation */ - if (command->client->createOptions && (command->client->noBufferedMessages >= command->client->createOptions->maxBufferedMessages)) - { - MQTTAsync_queuedCommand* first_publish = NULL; - ListElement* current = NULL; - - /* Find first publish command for this client and detach it */ - while (ListNextElement(commands, ¤t)) - { - MQTTAsync_queuedCommand* cmd = (MQTTAsync_queuedCommand*)(current->content); - - if (cmd->client == command->client && cmd->command.type == PUBLISH) - { - first_publish = cmd; - break; - } - } - if (first_publish) - { - ListDetach(commands, first_publish); - - MQTTAsync_freeCommand(first_publish); - #if !defined(NO_PERSISTENCE) - if (command->client->c->persistence) - MQTTAsync_unpersistCommand(first_publish); - #endif - } - } - else - command->client->noBufferedMessages++; - } - } - MQTTAsync_unlock_mutex(mqttcommand_mutex); -#if !defined(_WIN32) && !defined(_WIN64) - rc = Thread_signal_cond(send_cond); - if (rc != 0) - Log(LOG_ERROR, 0, "Error %d from signal cond", rc); -#else - rc = Thread_post_sem(send_sem); -#endif - FUNC_EXIT_RC(rc); - return rc; -} - - -static void MQTTAsync_startConnectRetry(MQTTAsyncs* m) -{ - if (m->automaticReconnect && m->shouldBeConnected) - { - m->lastConnectionFailedTime = MQTTTime_start_clock(); - if (m->retrying) - { - m->currentIntervalBase = min(m->currentIntervalBase * 2, m->maxRetryInterval); - } - else - { - m->currentIntervalBase = m->minRetryInterval; - m->retrying = 1; - } - m->currentInterval = MQTTAsync_randomJitter(m->currentIntervalBase, m->minRetryInterval, m->maxRetryInterval); - } -} - - -int MQTTAsync_reconnect(MQTTAsync handle) -{ - int rc = MQTTASYNC_FAILURE; - MQTTAsyncs* m = handle; - - FUNC_ENTRY; - MQTTAsync_lock_mutex(mqttasync_mutex); - - if (m->automaticReconnect) - { - if (m->shouldBeConnected) - { - m->reconnectNow = 1; - if (m->retrying == 0) - { - m->currentIntervalBase = m->minRetryInterval; - m->currentInterval = m->minRetryInterval; - m->retrying = 1; - } - rc = MQTTASYNC_SUCCESS; - } - } - else - { - /* to reconnect, put the connect command to the head of the command queue */ - MQTTAsync_queuedCommand* conn = malloc(sizeof(MQTTAsync_queuedCommand)); - if (!conn) - { - rc = PAHO_MEMORY_ERROR; - goto exit; - } - memset(conn, '\0', sizeof(MQTTAsync_queuedCommand)); - conn->client = m; - conn->command = m->connect; - /* make sure that the version attempts are restarted */ - if (m->c->MQTTVersion == MQTTVERSION_DEFAULT) - conn->command.details.conn.MQTTVersion = 0; - rc = MQTTAsync_addCommand(conn, sizeof(m->connect)); - } - -exit: - MQTTAsync_unlock_mutex(mqttasync_mutex); - FUNC_EXIT_RC(rc); - return rc; -} - - -static void MQTTAsync_checkDisconnect(MQTTAsync handle, MQTTAsync_command* command) -{ - MQTTAsyncs* m = handle; - - FUNC_ENTRY; - /* wait for all inflight message flows to finish, up to timeout */; - if (m->c->outboundMsgs->count == 0 || MQTTTime_elapsed(command->start_time) >= (ELAPSED_TIME_TYPE)command->details.dis.timeout) - { - int was_connected = m->c->connected; - MQTTAsync_closeSession(m->c, command->details.dis.reasonCode, &command->properties); - if (command->details.dis.internal) - { - if (m->cl && was_connected) - { - Log(TRACE_MIN, -1, "Calling connectionLost for client %s", m->c->clientID); - (*(m->cl))(m->clContext, NULL); - } - MQTTAsync_startConnectRetry(m); - } - else if (command->onSuccess) - { - MQTTAsync_successData data; - - memset(&data, '\0', sizeof(data)); - Log(TRACE_MIN, -1, "Calling disconnect complete for client %s", m->c->clientID); - (*(command->onSuccess))(command->context, &data); - } - else if (command->onSuccess5) - { - MQTTAsync_successData5 data = MQTTAsync_successData5_initializer; - - data.reasonCode = MQTTASYNC_SUCCESS; - Log(TRACE_MIN, -1, "Calling disconnect complete for client %s", m->c->clientID); - (*(command->onSuccess5))(command->context, &data); - } - } - FUNC_EXIT; -} - -/** - * 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) -{ - int rc; - Thread_lock_mutex(socket_mutex); - rc = Socket_noPendingWrites(socket); - Thread_unlock_mutex(socket_mutex); - return rc; -} - -/** - * See if any pending writes have been completed, and cleanup if so. - * Cleaning up means removing any publication data that was stored because the write did - * not originally complete. - */ -static void MQTTProtocol_checkPendingWrites(void) -{ - FUNC_ENTRY; - if (state.pending_writes.count > 0) - { - ListElement* le = state.pending_writes.first; - while (le) - { - if (Socket_noPendingWrites(((pending_write*)(le->content))->socket)) - { - MQTTProtocol_removePublication(((pending_write*)(le->content))->p); - state.pending_writes.current = le; - ListRemove(&(state.pending_writes), le->content); /* does NextElement itself */ - le = state.pending_writes.current; - } - else - ListNextElement(&(state.pending_writes), &le); - } - } - FUNC_EXIT; -} - - -static void MQTTAsync_freeServerURIs(MQTTAsyncs* m) -{ - int i; - - for (i = 0; i < m->serverURIcount; ++i) - free(m->serverURIs[i]); - m->serverURIcount = 0; - if (m->serverURIs) - free(m->serverURIs); - m->serverURIs = NULL; -} - - -static void MQTTAsync_freeCommand1(MQTTAsync_queuedCommand *command) -{ - if (command->command.type == SUBSCRIBE) - { - int i; - - for (i = 0; i < command->command.details.sub.count; i++) - free(command->command.details.sub.topics[i]); - - free(command->command.details.sub.topics); - command->command.details.sub.topics = NULL; - free(command->command.details.sub.qoss); - command->command.details.sub.qoss = NULL; - } - else if (command->command.type == UNSUBSCRIBE) - { - int i; - - for (i = 0; i < command->command.details.unsub.count; i++) - free(command->command.details.unsub.topics[i]); - - free(command->command.details.unsub.topics); - command->command.details.unsub.topics = NULL; - } - else if (command->command.type == PUBLISH) - { - /* qos 1 and 2 topics are freed in the protocol code when the flows are completed */ - if (command->command.details.pub.destinationName) - free(command->command.details.pub.destinationName); - command->command.details.pub.destinationName = NULL; - if (command->command.details.pub.payload) - free(command->command.details.pub.payload); - command->command.details.pub.payload = NULL; - } - MQTTProperties_free(&command->command.properties); - if (command->not_restored && command->key) - free(command->key); -} - -static void MQTTAsync_freeCommand(MQTTAsync_queuedCommand *command) -{ - MQTTAsync_freeCommand1(command); - free(command); -} - - -static void MQTTAsync_writeComplete(int socket, int rc) -{ - ListElement* found = NULL; - - FUNC_ENTRY; - /* a partial write is now complete for a socket - this will be on a publish*/ - - MQTTProtocol_checkPendingWrites(); - - /* find the client using this socket */ - if ((found = ListFindItem(handles, &socket, clientSockCompare)) != NULL) - { - MQTTAsyncs* m = (MQTTAsyncs*)(found->content); - - m->c->net.lastSent = MQTTTime_now(); - - /* see if there is a pending write flagged */ - if (m->pending_write) - { - ListElement* cur_response = NULL; - MQTTAsync_command* command = m->pending_write; - MQTTAsync_queuedCommand* com = NULL; - - cur_response = NULL; - while (ListNextElement(m->responses, &cur_response)) - { - com = (MQTTAsync_queuedCommand*)(cur_response->content); - if (&com->command == m->pending_write) - break; - } - - if (cur_response) /* we found a response */ - { - if (command->type == PUBLISH) - { - if (rc == 1 && command->details.pub.qos == 0) - { - if (command->onSuccess) - { - MQTTAsync_successData data; - - data.token = command->token; - data.alt.pub.destinationName = command->details.pub.destinationName; - data.alt.pub.message.payload = command->details.pub.payload; - data.alt.pub.message.payloadlen = command->details.pub.payloadlen; - data.alt.pub.message.qos = command->details.pub.qos; - data.alt.pub.message.retained = command->details.pub.retained; - Log(TRACE_MIN, -1, "Calling publish success for client %s", m->c->clientID); - (*(command->onSuccess))(command->context, &data); - } - else if (command->onSuccess5) - { - MQTTAsync_successData5 data = MQTTAsync_successData5_initializer; - - data.token = command->token; - data.alt.pub.destinationName = command->details.pub.destinationName; - data.alt.pub.message.payload = command->details.pub.payload; - data.alt.pub.message.payloadlen = command->details.pub.payloadlen; - data.alt.pub.message.qos = command->details.pub.qos; - data.alt.pub.message.retained = command->details.pub.retained; - data.properties = command->properties; - Log(TRACE_MIN, -1, "Calling publish success for client %s", m->c->clientID); - (*(command->onSuccess5))(command->context, &data); - } - } - else if (rc == -1) - { - if (command->onFailure) - { - MQTTAsync_failureData data; - - data.token = command->token; - data.code = rc; - data.message = NULL; - Log(TRACE_MIN, -1, "Calling publish failure for client %s", m->c->clientID); - (*(command->onFailure))(command->context, &data); - } - else if (command->onFailure5) - { - MQTTAsync_failureData5 data; - - data.token = command->token; - data.code = rc; - data.message = NULL; - data.packet_type = PUBLISH; - Log(TRACE_MIN, -1, "Calling publish failure for client %s", m->c->clientID); - (*(command->onFailure5))(command->context, &data); - } - } - else - com = NULL; /* Don't delete response we haven't acknowledged */ - /* QoS 0 payloads are freed elsewhere after a write complete, - * so we should indicate that. - */ - if (command->details.pub.qos == 0) - command->details.pub.payload = NULL; - } - if (com) - { - Log(TRACE_PROTOCOL, -1, "writeComplete: Removing response for msgid %d", com->command.token); - ListDetach(m->responses, com); - MQTTAsync_freeCommand(com); - } - } /* if cur_response */ - m->pending_write = NULL; - } /* if pending_write */ - } - FUNC_EXIT; -} - - -static int MQTTAsync_processCommand(void) -{ - int rc = 0; - MQTTAsync_queuedCommand* command = NULL; - ListElement* cur_command = NULL; - List* ignored_clients = NULL; - - FUNC_ENTRY; - MQTTAsync_lock_mutex(mqttasync_mutex); - MQTTAsync_lock_mutex(mqttcommand_mutex); - - /* only the first command in the list must be processed for any particular client, so if we skip - a command for a client, we must skip all following commands for that client. Use a list of - ignored clients to keep track - */ - ignored_clients = ListInitialize(); - - /* don't try a command until there isn't a pending write for that client, and we are not connecting */ - while (ListNextElement(commands, &cur_command)) - { - MQTTAsync_queuedCommand* cmd = (MQTTAsync_queuedCommand*)(cur_command->content); - - if (ListFind(ignored_clients, cmd->client)) - continue; - - if (cmd->command.type == CONNECT || cmd->command.type == DISCONNECT || (cmd->client->c->connected && - cmd->client->c->connect_state == NOT_IN_PROGRESS && MQTTAsync_Socket_noPendingWrites(cmd->client->c->net.socket))) - { - if ((cmd->command.type == PUBLISH || cmd->command.type == SUBSCRIBE || cmd->command.type == UNSUBSCRIBE) && - cmd->client->c->outboundMsgs->count >= MAX_MSG_ID - 1) - { - ; /* no more message ids available */ - } - else if (((cmd->command.type == PUBLISH && cmd->command.details.pub.qos > 0) || - cmd->command.type == SUBSCRIBE || cmd->command.type == UNSUBSCRIBE) && - (cmd->client->c->outboundMsgs->count >= cmd->client->c->maxInflightMessages)) - { - Log(TRACE_MIN, -1, "Blocking on server receive maximum for client %s", - cmd->client->c->clientID); /* flow control */ - } - else - { - command = cmd; - break; - } - } - ListAppend(ignored_clients, cmd->client, sizeof(cmd->client)); - } - ListFreeNoContent(ignored_clients); - if (command) - { - if (command->command.type == PUBLISH) - command->client->noBufferedMessages--; - ListDetach(commands, command); -#if !defined(NO_PERSISTENCE) - /*printf("outboundmsgs count %d max inflight %d qos %d %d %d\n", command->client->c->outboundMsgs->count, command->client->c->maxInflightMessages, - command->command.details.pub.qos, command->client->c->MQTTVersion, command->command.type);*/ - if (command->client->c->persistence) - { - if (command->not_restored) - { - char* buffer = NULL; - int buflen = 0; - - if ((rc = command->client->c->persistence->pget(command->client->c->phandle, command->key, &buffer, &buflen)) == 0 - && (command->client->c->afterRead == NULL || - (rc = command->client->c->afterRead(command->client->c->afterRead_context, &buffer, &buflen)) == 0)) - { - int MQTTVersion = (strncmp(command->key, PERSISTENCE_V5_COMMAND_KEY, strlen(PERSISTENCE_V5_COMMAND_KEY)) == 0) - ? MQTTVERSION_5 : MQTTVERSION_3_1_1; - 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); - } - MQTTAsync_unpersistCommand(command); - } -#endif - } - MQTTAsync_unlock_mutex(mqttcommand_mutex); - - if (!command) - goto exit; /* nothing to do */ - - if (command->command.type == CONNECT) - { - if (command->client->c->connect_state != NOT_IN_PROGRESS || command->client->c->connected) - rc = 0; - else - { - char* serverURI = command->client->serverURI; - - if (command->client->serverURIcount > 0) - { - if (command->command.details.conn.currentURI < command->client->serverURIcount) - { - serverURI = command->client->serverURIs[command->command.details.conn.currentURI]; - - if (strncmp(URI_TCP, serverURI, strlen(URI_TCP)) == 0) - serverURI += strlen(URI_TCP); - else if (strncmp(URI_WS, serverURI, strlen(URI_WS)) == 0) - { - serverURI += strlen(URI_WS); - command->client->websocket = 1; - } -#if defined(OPENSSL) - else if (strncmp(URI_SSL, serverURI, strlen(URI_SSL)) == 0) - { - serverURI += strlen(URI_SSL); - command->client->ssl = 1; - } - else if (strncmp(URI_WSS, serverURI, strlen(URI_WSS)) == 0) - { - serverURI += strlen(URI_WSS); - command->client->ssl = 1; - command->client->websocket = 1; - } -#endif - } - } - - if (command->client->c->MQTTVersion == MQTTVERSION_DEFAULT) - { - if (command->command.details.conn.MQTTVersion == MQTTVERSION_DEFAULT) - command->command.details.conn.MQTTVersion = MQTTVERSION_3_1_1; - else if (command->command.details.conn.MQTTVersion == MQTTVERSION_3_1_1) - command->command.details.conn.MQTTVersion = MQTTVERSION_3_1; - } - else - command->command.details.conn.MQTTVersion = command->client->c->MQTTVersion; - - Log(TRACE_PROTOCOL, -1, "Connecting to serverURI %s with MQTT version %d", serverURI, command->command.details.conn.MQTTVersion); -#if defined(OPENSSL) -#if defined(__GNUC__) && defined(__linux__) - rc = MQTTProtocol_connect(serverURI, command->client->c, command->client->ssl, command->client->websocket, - command->command.details.conn.MQTTVersion, command->client->connectProps, command->client->willProps, 100); -#else - rc = MQTTProtocol_connect(serverURI, command->client->c, command->client->ssl, command->client->websocket, - command->command.details.conn.MQTTVersion, command->client->connectProps, command->client->willProps); -#endif -#else -#if defined(__GNUC__) && defined(__linux__) - rc = MQTTProtocol_connect(serverURI, command->client->c, command->client->websocket, - command->command.details.conn.MQTTVersion, command->client->connectProps, command->client->willProps, 100); -#else - rc = MQTTProtocol_connect(serverURI, command->client->c, command->client->websocket, - command->command.details.conn.MQTTVersion, command->client->connectProps, command->client->willProps); -#endif -#endif - - if (command->client->c->connect_state == NOT_IN_PROGRESS) - rc = SOCKET_ERROR; - - /* if the TCP connect is pending, then we must call select to determine when the connect has completed, - which is indicated by the socket being ready *either* for reading *or* writing. The next couple of lines - make sure we check for writeability as well as readability, otherwise we wait around longer than we need to - in Socket_getReadySocket() */ - if (rc == EINPROGRESS) - Socket_addPendingWrite(command->client->c->net.socket); - } - } - else if (command->command.type == SUBSCRIBE) - { - List* topics = ListInitialize(); - List* qoss = ListInitialize(); - MQTTProperties* props = NULL; - MQTTSubscribe_options* subopts = NULL; - int i; - - for (i = 0; i < command->command.details.sub.count; i++) - { - ListAppend(topics, command->command.details.sub.topics[i], strlen(command->command.details.sub.topics[i])); - ListAppend(qoss, &command->command.details.sub.qoss[i], sizeof(int)); - } - if (command->client->c->MQTTVersion >= MQTTVERSION_5) - { - props = &command->command.properties; - if (command->command.details.sub.count > 1) - subopts = command->command.details.sub.optlist; - else - subopts = &command->command.details.sub.opts; - } - rc = MQTTProtocol_subscribe(command->client->c, topics, qoss, command->command.token, subopts, props); - ListFreeNoContent(topics); - ListFreeNoContent(qoss); - if (command->client->c->MQTTVersion >= MQTTVERSION_5 && command->command.details.sub.count > 1) - free(command->command.details.sub.optlist); - } - else if (command->command.type == UNSUBSCRIBE) - { - List* topics = ListInitialize(); - MQTTProperties* props = NULL; - int i; - - for (i = 0; i < command->command.details.unsub.count; i++) - ListAppend(topics, command->command.details.unsub.topics[i], strlen(command->command.details.unsub.topics[i])); - - if (command->client->c->MQTTVersion >= MQTTVERSION_5) - props = &command->command.properties; - - rc = MQTTProtocol_unsubscribe(command->client->c, topics, command->command.token, props); - ListFreeNoContent(topics); - } - else if (command->command.type == PUBLISH) - { - Messages* msg = NULL; - Publish* p = NULL; - MQTTProperties initialized = MQTTProperties_initializer; - - if ((p = malloc(sizeof(Publish))) == NULL) - { - rc = PAHO_MEMORY_ERROR; - goto exit; - } - - p->payload = command->command.details.pub.payload; - p->payloadlen = command->command.details.pub.payloadlen; - p->topic = command->command.details.pub.destinationName; - p->msgId = command->command.token; - p->MQTTVersion = command->client->c->MQTTVersion; - p->properties = initialized; - if (p->MQTTVersion >= MQTTVERSION_5) - p->properties = command->command.properties; - - rc = MQTTProtocol_startPublish(command->client->c, p, command->command.details.pub.qos, command->command.details.pub.retained, &msg); - - if (command->command.details.pub.qos == 0) - { - if (rc == TCPSOCKET_COMPLETE) - { - if (command->command.onSuccess) - { - MQTTAsync_successData data; - - data.token = command->command.token; - data.alt.pub.destinationName = command->command.details.pub.destinationName; - data.alt.pub.message.payload = command->command.details.pub.payload; - data.alt.pub.message.payloadlen = command->command.details.pub.payloadlen; - data.alt.pub.message.qos = command->command.details.pub.qos; - data.alt.pub.message.retained = command->command.details.pub.retained; - Log(TRACE_MIN, -1, "Calling publish success for client %s", command->client->c->clientID); - (*(command->command.onSuccess))(command->command.context, &data); - } - else if (command->command.onSuccess5) - { - MQTTAsync_successData5 data = MQTTAsync_successData5_initializer; - - data.token = command->command.token; - data.alt.pub.destinationName = command->command.details.pub.destinationName; - data.alt.pub.message.payload = command->command.details.pub.payload; - data.alt.pub.message.payloadlen = command->command.details.pub.payloadlen; - data.alt.pub.message.qos = command->command.details.pub.qos; - data.alt.pub.message.retained = command->command.details.pub.retained; - data.properties = command->command.properties; - Log(TRACE_MIN, -1, "Calling publish success for client %s", command->client->c->clientID); - (*(command->command.onSuccess5))(command->command.context, &data); - } - } - else - { - if (rc != SOCKET_ERROR) - command->command.details.pub.destinationName = NULL; /* this will be freed by the protocol code */ - command->client->pending_write = &command->command; - } - } - else - { - 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 */ - } - free(p); /* should this be done if the write isn't complete? */ - } - else if (command->command.type == DISCONNECT) - { - if (command->client->c->connect_state != NOT_IN_PROGRESS || command->client->c->connected != 0) - { - if (command->client->c->connect_state != NOT_IN_PROGRESS) - { - if (command->client->connect.onFailure) - { - MQTTAsync_failureData data; - - data.token = 0; - data.code = MQTTASYNC_OPERATION_INCOMPLETE; - data.message = NULL; - Log(TRACE_MIN, -1, "Calling connect failure for client %s", command->client->c->clientID); - (*(command->client->connect.onFailure))(command->client->connect.context, &data); - /* Null out callback pointers so they aren't accidentally called again */ - command->client->connect.onFailure = NULL; - command->client->connect.onSuccess = NULL; - } - else if (command->client->connect.onFailure5) - { - MQTTAsync_failureData5 data; - - data.token = 0; - data.code = MQTTASYNC_OPERATION_INCOMPLETE; - data.message = NULL; - Log(TRACE_MIN, -1, "Calling connect failure for client %s", command->client->c->clientID); - (*(command->client->connect.onFailure5))(command->client->connect.context, &data); - /* Null out callback pointers so they aren't accidentally called again */ - command->client->connect.onFailure5 = NULL; - command->client->connect.onSuccess5 = NULL; - } - } - command->client->c->connect_state = DISCONNECTING; - MQTTAsync_checkDisconnect(command->client, &command->command); - } - } - - if (command->command.type == CONNECT && rc != SOCKET_ERROR && rc != MQTTASYNC_PERSISTENCE_ERROR) - { - command->client->connect = command->command; - MQTTAsync_freeCommand(command); - } - else if (command->command.type == DISCONNECT) - { - command->client->disconnect = command->command; - MQTTAsync_freeCommand(command); - } - else if (command->command.type == PUBLISH && command->command.details.pub.qos == 0 && - rc != SOCKET_ERROR && rc != MQTTASYNC_PERSISTENCE_ERROR) - { - if (rc == TCPSOCKET_INTERRUPTED) - ListAppend(command->client->responses, command, sizeof(command)); - else - MQTTAsync_freeCommand(command); - } - else if (rc == SOCKET_ERROR || rc == MQTTASYNC_PERSISTENCE_ERROR) - { - if (command->command.type == CONNECT) - { - MQTTAsync_disconnectOptions opts = MQTTAsync_disconnectOptions_initializer; - MQTTAsync_disconnect(command->client, &opts); /* not "internal" because we don't want to call connection lost */ - command->client->shouldBeConnected = 1; /* as above call is not "internal" we need to reset this */ - } - else - MQTTAsync_disconnect_internal(command->client, 0); - - if (command->command.type == CONNECT - && MQTTAsync_checkConn(&command->command, command->client)) - { - Log(TRACE_MIN, -1, "Connect failed, more to try"); - - if (command->client->c->MQTTVersion == MQTTVERSION_DEFAULT) - { - if (command->command.details.conn.MQTTVersion == MQTTVERSION_3_1) - { - command->command.details.conn.currentURI++; - command->command.details.conn.MQTTVersion = MQTTVERSION_DEFAULT; - } - } else - command->command.details.conn.currentURI++; /* Here currentURI becomes larger than command->client->serverURIcount. This needs to be handled to avoid segmentation faults! */ - - /* put the connect command back to the head of the command queue, using the next serverURI */ - rc = MQTTAsync_addCommand(command, - sizeof(command->command.details.conn)); - } else - { - if (command->command.onFailure) - { - MQTTAsync_failureData data; - - data.token = 0; - data.code = rc; - data.message = NULL; - Log(TRACE_MIN, -1, "Calling command failure for client %s", command->client->c->clientID); - (*(command->command.onFailure))(command->command.context, &data); - } - else if (command->command.onFailure5) - { - MQTTAsync_failureData5 data = MQTTAsync_failureData5_initializer; - - data.code = rc; - Log(TRACE_MIN, -1, "Calling command failure for client %s", command->client->c->clientID); - (*(command->command.onFailure5))(command->command.context, &data); - } - if (command->command.type == CONNECT) - { - command->client->connect = command->command; - MQTTAsync_startConnectRetry(command->client); - } - MQTTAsync_freeCommand(command); /* free up the command if necessary */ - } - } - else /* put the command into a waiting for response queue for each client, indexed by msgid */ - ListAppend(command->client->responses, command, sizeof(command)); - -exit: - MQTTAsync_unlock_mutex(mqttasync_mutex); - rc = (command != NULL); - FUNC_EXIT_RC(rc); - return rc; -} - - -static void nextOrClose(MQTTAsyncs* m, int rc, char* message) -{ - int was_connected = m->c->connected; - FUNC_ENTRY; - - if (MQTTAsync_checkConn(&m->connect, m)) - { - MQTTAsync_queuedCommand* conn; - - MQTTAsync_closeOnly(m->c, MQTTREASONCODE_SUCCESS, NULL); - if (m->cl && was_connected) - { - Log(TRACE_MIN, -1, "Calling connectionLost for client %s", m->c->clientID); - (*(m->cl))(m->clContext, NULL); - } - /* put the connect command back to the head of the command queue, using the next serverURI */ - if ((conn = malloc(sizeof(MQTTAsync_queuedCommand))) == NULL) - goto exit; - memset(conn, '\0', sizeof(MQTTAsync_queuedCommand)); - conn->client = m; - conn->command = m->connect; - Log(TRACE_MIN, -1, "Connect failed, more to try"); - - if (conn->client->c->MQTTVersion == MQTTVERSION_DEFAULT) - { - if (conn->command.details.conn.MQTTVersion == MQTTVERSION_3_1) - { - conn->command.details.conn.currentURI++; - conn->command.details.conn.MQTTVersion = MQTTVERSION_DEFAULT; - } - } - else - conn->command.details.conn.currentURI++; - - MQTTAsync_addCommand(conn, sizeof(m->connect)); - } - else - { - MQTTAsync_closeSession(m->c, MQTTREASONCODE_SUCCESS, NULL); - if (m->cl && was_connected) - { - Log(TRACE_MIN, -1, "Calling connectionLost for client %s", m->c->clientID); - (*(m->cl))(m->clContext, NULL); - } - if (m->connect.onFailure) - { - MQTTAsync_failureData data; - - data.token = 0; - data.code = rc; - data.message = message; - Log(TRACE_MIN, -1, "Calling connect failure for client %s", m->c->clientID); - (*(m->connect.onFailure))(m->connect.context, &data); - /* Null out callback pointers so they aren't accidentally called again */ - m->connect.onFailure = NULL; - m->connect.onSuccess = NULL; - } - else if (m->connect.onFailure5) - { - MQTTAsync_failureData5 data = MQTTAsync_failureData5_initializer; - - data.token = 0; - data.code = rc; - data.message = message; - Log(TRACE_MIN, -1, "Calling connect failure for client %s", m->c->clientID); - (*(m->connect.onFailure5))(m->connect.context, &data); - /* Null out callback pointers so they aren't accidentally called again */ - m->connect.onFailure5 = NULL; - m->connect.onSuccess5 = NULL; - } - MQTTAsync_startConnectRetry(m); - } -exit: - FUNC_EXIT; -} - - -static void MQTTAsync_checkTimeouts(void) -{ - ListElement* current = NULL; - static START_TIME_TYPE last = START_TIME_ZERO; - START_TIME_TYPE now; - - FUNC_ENTRY; - MQTTAsync_lock_mutex(mqttasync_mutex); - now = MQTTTime_now(); - if (MQTTTime_difftime(now, last) < (DIFF_TIME_TYPE)3000) - goto exit; - last = now; - while (ListNextElement(handles, ¤t)) /* for each client */ - { - MQTTAsyncs* m = (MQTTAsyncs*)(current->content); - - /* check disconnect timeout */ - if (m->c->connect_state == DISCONNECTING) - MQTTAsync_checkDisconnect(m, &m->disconnect); - - /* check connect timeout */ - if (m->c->connect_state != NOT_IN_PROGRESS && MQTTTime_elapsed(m->connect.start_time) > (ELAPSED_TIME_TYPE)(m->connectTimeout * 1000)) - { - nextOrClose(m, MQTTASYNC_FAILURE, "TCP connect timeout"); - continue; - } - - /* There was a section here that removed timed-out responses. But if the command had completed and - * there was a response, then we may as well report it, no? - * - * In any case, that section was disabled when automatic reconnect was implemented. - */ - - if (m->automaticReconnect && m->retrying) - { - if (m->reconnectNow || MQTTTime_elapsed(m->lastConnectionFailedTime) > (ELAPSED_TIME_TYPE)(m->currentInterval * 1000)) - { - /* to reconnect put the connect command to the head of the command queue */ - MQTTAsync_queuedCommand* conn = malloc(sizeof(MQTTAsync_queuedCommand)); - if (!conn) - goto exit; - memset(conn, '\0', sizeof(MQTTAsync_queuedCommand)); - conn->client = m; - conn->command = m->connect; - /* make sure that the version attempts are restarted */ - if (m->c->MQTTVersion == MQTTVERSION_DEFAULT) - conn->command.details.conn.MQTTVersion = 0; - if (m->updateConnectOptions) - { - MQTTAsync_connectData connectData = MQTTAsync_connectData_initializer; - int callback_rc = MQTTASYNC_SUCCESS; - - connectData.username = m->c->username; - connectData.binarypwd.data = m->c->password; - connectData.binarypwd.len = m->c->passwordlen; - Log(TRACE_MIN, -1, "Calling updateConnectOptions for client %s", m->c->clientID); - callback_rc = (*(m->updateConnectOptions))(m->updateConnectOptions_context, &connectData); - - if (callback_rc == 1) - { - if (connectData.username != m->c->username) - { - if (m->c->username) - free((void*)m->c->username); - if (connectData.username) - m->c->username = MQTTStrdup(connectData.username); - else - m->c->username = NULL; - } - if (connectData.binarypwd.data != m->c->password) - { - if (m->c->password) - free((void*)m->c->password); - if (connectData.binarypwd.data) - { - m->c->passwordlen = connectData.binarypwd.len; - if ((m->c->password = malloc(m->c->passwordlen))) - memcpy((void*)m->c->password, connectData.binarypwd.data, m->c->passwordlen); - } - else - { - m->c->password = NULL; - m->c->passwordlen = 0; - } - } - } - } - Log(TRACE_MIN, -1, "Automatically attempting to reconnect"); - MQTTAsync_addCommand(conn, sizeof(m->connect)); - m->reconnectNow = 0; - } - } - } -exit: - MQTTAsync_unlock_mutex(mqttasync_mutex); - FUNC_EXIT; -} - - -static thread_return_type WINAPI MQTTAsync_sendThread(void* n) -{ - FUNC_ENTRY; - MQTTAsync_lock_mutex(mqttasync_mutex); - sendThread_state = RUNNING; - sendThread_id = Thread_getid(); - MQTTAsync_unlock_mutex(mqttasync_mutex); - while (!tostop) - { - int rc; - - while (commands->count > 0) - { - if (MQTTAsync_processCommand() == 0) - break; /* no commands were processed, so go into a wait */ - } -#if !defined(_WIN32) && !defined(_WIN64) - if ((rc = Thread_wait_cond(send_cond, 1)) != 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) - Log(LOG_ERROR, -1, "Error %d waiting for semaphore", rc); -#endif - - MQTTAsync_checkTimeouts(); - } - sendThread_state = STOPPING; - MQTTAsync_lock_mutex(mqttasync_mutex); - sendThread_state = STOPPED; - sendThread_id = 0; - MQTTAsync_unlock_mutex(mqttasync_mutex); - FUNC_EXIT; -#if defined(_WIN32) || defined(_WIN64) - ExitThread(0); -#endif - return 0; -} - - -static void MQTTAsync_emptyMessageQueue(Clients* client) -{ - FUNC_ENTRY; - /* empty message queue */ - if (client->messageQueue->count > 0) - { - ListElement* current = NULL; - while (ListNextElement(client->messageQueue, ¤t)) - { - qEntry* qe = (qEntry*)(current->content); - free(qe->topicName); - free(qe->msg->payload); - free(qe->msg); - } - ListEmpty(client->messageQueue); - } - FUNC_EXIT; -} - - -static void MQTTAsync_freeResponses(MQTTAsyncs* m) -{ - int count = 0; - - FUNC_ENTRY; - if (m->responses) - { - ListElement* cur_response = NULL; - - while (ListNextElement(m->responses, &cur_response)) - { - MQTTAsync_queuedCommand* command = (MQTTAsync_queuedCommand*)(cur_response->content); - - if (command->command.onFailure) - { - MQTTAsync_failureData data; - - data.token = command->command.token; - data.code = MQTTASYNC_OPERATION_INCOMPLETE; /* interrupted return code */ - data.message = NULL; - - Log(TRACE_MIN, -1, "Calling %s failure for client %s", - MQTTPacket_name(command->command.type), m->c->clientID); - (*(command->command.onFailure))(command->command.context, &data); - } - else if (command->command.onFailure5) - { - MQTTAsync_failureData5 data = MQTTAsync_failureData5_initializer; - - data.token = command->command.token; - data.code = MQTTASYNC_OPERATION_INCOMPLETE; /* interrupted return code */ - data.message = NULL; - - Log(TRACE_MIN, -1, "Calling %s failure for client %s", - MQTTPacket_name(command->command.type), m->c->clientID); - (*(command->command.onFailure5))(command->command.context, &data); - } - - MQTTAsync_freeCommand1(command); - count++; - } - ListEmpty(m->responses); - } - Log(TRACE_MINIMUM, -1, "%d responses removed for client %s", count, m->c->clientID); - FUNC_EXIT; -} - - -static void MQTTAsync_freeCommands(MQTTAsyncs* m) -{ - int count = 0; - ListElement* current = NULL; - ListElement *next = NULL; - - FUNC_ENTRY; - /* remove commands in the command queue relating to this client */ - current = ListNextElement(commands, &next); - ListNextElement(commands, &next); - while (current) - { - MQTTAsync_queuedCommand* command = (MQTTAsync_queuedCommand*)(current->content); - - if (command->client == m) - { - ListDetach(commands, command); - - if (command->command.onFailure) - { - MQTTAsync_failureData data; - - data.token = command->command.token; - data.code = MQTTASYNC_OPERATION_INCOMPLETE; /* interrupted return code */ - data.message = NULL; - - Log(TRACE_MIN, -1, "Calling %s failure for client %s", - MQTTPacket_name(command->command.type), m->c->clientID); - (*(command->command.onFailure))(command->command.context, &data); - } - else if (command->command.onFailure5) - { - MQTTAsync_failureData5 data = MQTTAsync_failureData5_initializer; - - data.token = command->command.token; - data.code = MQTTASYNC_OPERATION_INCOMPLETE; /* interrupted return code */ - data.message = NULL; - - Log(TRACE_MIN, -1, "Calling %s failure for client %s", - MQTTPacket_name(command->command.type), m->c->clientID); - (*(command->command.onFailure5))(command->command.context, &data); - } - - MQTTAsync_freeCommand(command); - count++; - } - current = next; - ListNextElement(commands, &next); - } - Log(TRACE_MINIMUM, -1, "%d commands removed for client %s", count, m->c->clientID); - FUNC_EXIT; -} - - void MQTTAsync_destroy(MQTTAsync* handle) { MQTTAsyncs* m = *handle; @@ -2624,7 +513,7 @@ void MQTTAsync_destroy(MQTTAsync* handle) free(m->willProps); m->willProps = NULL; } - if (!ListRemove(handles, m)) + if (!ListRemove(MQTTAsync_handles, m)) Log(LOG_ERROR, -1, "free error"); *handle = NULL; if (bstate->clients->count == 0) @@ -2636,873 +525,6 @@ exit: } -void MQTTAsync_freeMessage(MQTTAsync_message** message) -{ - FUNC_ENTRY; - MQTTProperties_free(&(*message)->properties); - free((*message)->payload); - free(*message); - *message = NULL; - FUNC_EXIT; -} - - -void MQTTAsync_free(void* memory) -{ - FUNC_ENTRY; - free(memory); - FUNC_EXIT; -} - - -void* MQTTAsync_malloc(size_t size) -{ - void* val; - int rc = 0; - - FUNC_ENTRY; - val = malloc(size); - rc = (val != NULL); - FUNC_EXIT_RC(rc); - return val; -} - - -static int MQTTAsync_completeConnection(MQTTAsyncs* m, Connack* connack) -{ - int rc = MQTTASYNC_FAILURE; - - FUNC_ENTRY; - if (m->c->connect_state == WAIT_FOR_CONNACK) /* MQTT connect sent - wait for CONNACK */ - { - Log(LOG_PROTOCOL, 1, NULL, m->c->net.socket, m->c->clientID, connack->rc); - if ((rc = connack->rc) == MQTTASYNC_SUCCESS) - { - m->retrying = 0; - m->c->connected = 1; - m->c->good = 1; - m->c->connect_state = NOT_IN_PROGRESS; - if (m->c->cleansession || m->c->cleanstart) - rc = MQTTAsync_cleanSession(m->c); - else if (m->c->MQTTVersion >= MQTTVERSION_3_1_1 && connack->flags.bits.sessionPresent == 0) - { - Log(LOG_PROTOCOL, -1, "Cleaning session state on connect because sessionPresent is 0"); - rc = MQTTAsync_cleanSession(m->c); - } - if (m->c->outboundMsgs->count > 0) - { - ListElement* outcurrent = NULL; - START_TIME_TYPE zero = START_TIME_ZERO; - - while (ListNextElement(m->c->outboundMsgs, &outcurrent)) - { - Messages* messages = (Messages*)(outcurrent->content); - memset(&messages->lastTouch, '\0', sizeof(messages->lastTouch)); - } - MQTTProtocol_retry(zero, 1, 1); - if (m->c->connected != 1) - rc = MQTTASYNC_DISCONNECTED; - } - } - m->pack = NULL; -#if !defined(_WIN32) && !defined(_WIN64) - Thread_signal_cond(send_cond); -#else - Thread_post_sem(send_sem); -#endif - } - FUNC_EXIT_RC(rc); - return rc; -} - - -/* This is the thread function that handles the calling of callback functions if set */ -static thread_return_type WINAPI MQTTAsync_receiveThread(void* n) -{ - long timeout = 10L; /* first time in we have a small timeout. Gets things started more quickly */ - - FUNC_ENTRY; - MQTTAsync_lock_mutex(mqttasync_mutex); - receiveThread_state = RUNNING; - receiveThread_id = Thread_getid(); - while (!tostop) - { - int rc = SOCKET_ERROR; - int sock = -1; - MQTTAsyncs* m = NULL; - MQTTPacket* pack = NULL; - - MQTTAsync_unlock_mutex(mqttasync_mutex); - pack = MQTTAsync_cycle(&sock, timeout, &rc); - MQTTAsync_lock_mutex(mqttasync_mutex); - if (tostop) - break; - timeout = 1000L; - - if (sock == 0) - continue; - /* find client corresponding to socket */ - if (ListFindItem(handles, &sock, clientSockCompare) == NULL) - { - Log(TRACE_MINIMUM, -1, "Could not find client corresponding to socket %d", sock); - /* Socket_close(sock); - removing socket in this case is not necessary (Bug 442400) */ - continue; - } - m = (MQTTAsyncs*)(handles->current->content); - if (m == NULL) - { - Log(LOG_ERROR, -1, "Client structure was NULL for socket %d - removing socket", sock); - Socket_close(sock); - continue; - } - if (rc == SOCKET_ERROR) - { - Log(TRACE_MINIMUM, -1, "Error from MQTTAsync_cycle() - removing socket %d", sock); - nextOrClose(m, rc, "socket error"); - } - else - { - if (m->c->messageQueue->count > 0 && m->ma) - { - qEntry* qe = (qEntry*)(m->c->messageQueue->first->content); - int topicLen = qe->topicLen; - - if (strlen(qe->topicName) == topicLen) - topicLen = 0; - - if (MQTTAsync_deliverMessage(m, qe->topicName, topicLen, qe->msg)) - { -#if !defined(NO_PERSISTENCE) - if (m->c->persistence) - MQTTPersistence_unpersistQueueEntry(m->c, (MQTTPersistence_qEntry*)qe); -#endif - ListRemove(m->c->messageQueue, qe); /* qe is freed here */ - } - else - Log(TRACE_MIN, -1, "False returned from messageArrived for client %s, message remains on queue", - m->c->clientID); - } - if (pack) - { - if (pack->header.bits.type == CONNACK) - { - Connack* connack = (Connack*)pack; - int sessionPresent = connack->flags.bits.sessionPresent; - - rc = MQTTAsync_completeConnection(m, connack); - if (rc == MQTTASYNC_SUCCESS) - { - int onSuccess = 0; - if ((m->serverURIcount > 0) - && (m->connect.details.conn.currentURI < m->serverURIcount)) - { - Log(TRACE_MIN, -1, "Connect succeeded to %s", - m->serverURIs[m->connect.details.conn.currentURI]); - } - onSuccess = (m->connect.onSuccess != NULL || - m->connect.onSuccess5 != NULL); /* save setting of onSuccess callback */ - if (m->connect.onSuccess) - { - MQTTAsync_successData data; - memset(&data, '\0', sizeof(data)); - Log(TRACE_MIN, -1, "Calling connect success for client %s", m->c->clientID); - if ((m->serverURIcount > 0) - && (m->connect.details.conn.currentURI < m->serverURIcount)) - data.alt.connect.serverURI = m->serverURIs[m->connect.details.conn.currentURI]; - else - data.alt.connect.serverURI = m->serverURI; - data.alt.connect.MQTTVersion = m->connect.details.conn.MQTTVersion; - data.alt.connect.sessionPresent = sessionPresent; - (*(m->connect.onSuccess))(m->connect.context, &data); - /* Null out callback pointers so they aren't accidentally called again */ - m->connect.onSuccess = NULL; - m->connect.onFailure = NULL; - } - else if (m->connect.onSuccess5) - { - MQTTAsync_successData5 data = MQTTAsync_successData5_initializer; - Log(TRACE_MIN, -1, "Calling connect success for client %s", m->c->clientID); - if (m->serverURIcount > 0) - data.alt.connect.serverURI = m->serverURIs[m->connect.details.conn.currentURI]; - else - data.alt.connect.serverURI = m->serverURI; - data.alt.connect.MQTTVersion = m->connect.details.conn.MQTTVersion; - data.alt.connect.sessionPresent = sessionPresent; - data.properties = connack->properties; - data.reasonCode = connack->rc; - (*(m->connect.onSuccess5))(m->connect.context, &data); - /* Null out callback pointers so they aren't accidentally called again */ - m->connect.onSuccess5 = NULL; - m->connect.onFailure5 = NULL; - } - if (m->connected) - { - char* reason = (onSuccess) ? "connect onSuccess called" : "automatic reconnect"; - Log(TRACE_MIN, -1, "Calling connected for client %s", m->c->clientID); - (*(m->connected))(m->connected_context, reason); - } - if (m->c->MQTTVersion >= MQTTVERSION_5) - { - if (MQTTProperties_hasProperty(&connack->properties, MQTTPROPERTY_CODE_RECEIVE_MAXIMUM)) - { - int recv_max = MQTTProperties_getNumericValue(&connack->properties, MQTTPROPERTY_CODE_RECEIVE_MAXIMUM); - if (m->c->maxInflightMessages > recv_max) - m->c->maxInflightMessages = recv_max; - } - } - } - else - { - nextOrClose(m, rc, "CONNACK return code"); - } - MQTTPacket_freeConnack(connack); - } - else if (pack->header.bits.type == SUBACK) - { - ListElement* current = NULL; - - /* use the msgid to find the callback to be called */ - while (ListNextElement(m->responses, ¤t)) - { - MQTTAsync_queuedCommand* command = (MQTTAsync_queuedCommand*)(current->content); - if (command->command.token == ((Suback*)pack)->msgId) - { - Suback* sub = (Suback*)pack; - if (!ListDetach(m->responses, command)) /* remove the response from the list */ - Log(LOG_ERROR, -1, "Subscribe command not removed from command list"); - - /* Call the failure callback if there is one subscribe in the MQTT packet and - * the return code is 0x80 (failure). If the MQTT packet contains >1 subscription - * request, then we call onSuccess with the list of returned QoSs, which inelegantly, - * could include some failures, or worse, the whole list could have failed. - */ - if (m->c->MQTTVersion >= MQTTVERSION_5) - { - if (sub->qoss->count == 1 && *(int*)(sub->qoss->first->content) >= MQTTREASONCODE_UNSPECIFIED_ERROR) - { - if (command->command.onFailure5) - { - MQTTAsync_failureData5 data = MQTTAsync_failureData5_initializer; - - data.token = command->command.token; - data.reasonCode = *(int*)(sub->qoss->first->content); - data.message = NULL; - data.properties = sub->properties; - Log(TRACE_MIN, -1, "Calling subscribe failure for client %s", m->c->clientID); - (*(command->command.onFailure5))(command->command.context, &data); - } - } - else if (command->command.onSuccess5) - { - MQTTAsync_successData5 data; - enum MQTTReasonCodes* array = NULL; - - data.reasonCode = *(int*)(sub->qoss->first->content); - data.alt.sub.reasonCodeCount = sub->qoss->count; - if (sub->qoss->count > 1) - { - ListElement* cur_qos = NULL; - enum MQTTReasonCodes* element = array = data.alt.sub.reasonCodes = malloc(sub->qoss->count * sizeof(enum MQTTReasonCodes)); - if (array) - while (ListNextElement(sub->qoss, &cur_qos)) - *element++ = *(int*)(cur_qos->content); - } - data.token = command->command.token; - data.properties = sub->properties; - Log(TRACE_MIN, -1, "Calling subscribe success for client %s", m->c->clientID); - (*(command->command.onSuccess5))(command->command.context, &data); - if (array) - free(array); - } - } - else if (sub->qoss->count == 1 && *(int*)(sub->qoss->first->content) == MQTT_BAD_SUBSCRIBE) - { - if (command->command.onFailure) - { - MQTTAsync_failureData data; - - data.token = command->command.token; - data.code = *(int*)(sub->qoss->first->content); - data.message = NULL; - Log(TRACE_MIN, -1, "Calling subscribe failure for client %s", m->c->clientID); - (*(command->command.onFailure))(command->command.context, &data); - } - } - else if (command->command.onSuccess) - { - MQTTAsync_successData data; - int* array = NULL; - - if (sub->qoss->count == 1) - data.alt.qos = *(int*)(sub->qoss->first->content); - else if (sub->qoss->count > 1) - { - ListElement* cur_qos = NULL; - int* element = array = data.alt.qosList = malloc(sub->qoss->count * sizeof(int)); - if (array) - while (ListNextElement(sub->qoss, &cur_qos)) - *element++ = *(int*)(cur_qos->content); - } - data.token = command->command.token; - Log(TRACE_MIN, -1, "Calling subscribe success for client %s", m->c->clientID); - (*(command->command.onSuccess))(command->command.context, &data); - if (array) - free(array); - } - MQTTAsync_freeCommand(command); - break; - } - } - rc = MQTTProtocol_handleSubacks(pack, m->c->net.socket); - } - else if (pack->header.bits.type == UNSUBACK) - { - ListElement* current = NULL; - Unsuback* unsub = (Unsuback*)pack; - - /* use the msgid to find the callback to be called */ - while (ListNextElement(m->responses, ¤t)) - { - MQTTAsync_queuedCommand* command = (MQTTAsync_queuedCommand*)(current->content); - if (command->command.token == ((Unsuback*)pack)->msgId) - { - if (!ListDetach(m->responses, command)) /* remove the response from the list */ - Log(LOG_ERROR, -1, "Unsubscribe command not removed from command list"); - if (command->command.onSuccess || command->command.onSuccess5) - { - Log(TRACE_MIN, -1, "Calling unsubscribe success for client %s", m->c->clientID); - if (command->command.onSuccess) - { - MQTTAsync_successData data; - - memset(&data, '\0', sizeof(data)); - data.token = command->command.token; - (*(command->command.onSuccess))(command->command.context, &data); - } - else - { - MQTTAsync_successData5 data = MQTTAsync_successData5_initializer; - enum MQTTReasonCodes* array = NULL; - - data.reasonCode = *(enum MQTTReasonCodes*)(unsub->reasonCodes->first->content); - data.alt.unsub.reasonCodeCount = unsub->reasonCodes->count; - if (unsub->reasonCodes->count > 1) - { - ListElement* cur_rc = NULL; - enum MQTTReasonCodes* element = array = data.alt.unsub.reasonCodes = malloc(unsub->reasonCodes->count * sizeof(enum MQTTReasonCodes)); - if (array) - while (ListNextElement(unsub->reasonCodes, &cur_rc)) - *element++ = *(enum MQTTReasonCodes*)(cur_rc->content); - } - data.token = command->command.token; - data.properties = unsub->properties; - Log(TRACE_MIN, -1, "Calling unsubscribe success for client %s", m->c->clientID); - (*(command->command.onSuccess5))(command->command.context, &data); - if (array) - free(array); - } - } - MQTTAsync_freeCommand(command); - break; - } - } - rc = MQTTProtocol_handleUnsubacks(pack, m->c->net.socket); - } - else if (pack->header.bits.type == DISCONNECT) - { - Ack* disc = (Ack*)pack; - - 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); - } - } - } - } - receiveThread_state = STOPPED; - receiveThread_id = 0; - MQTTAsync_unlock_mutex(mqttasync_mutex); -#if !defined(_WIN32) && !defined(_WIN64) - if (sendThread_state != STOPPED) - Thread_signal_cond(send_cond); -#else - if (sendThread_state != STOPPED) - Thread_post_sem(send_sem); -#endif - FUNC_EXIT; -#if defined(_WIN32) || defined(_WIN64) - ExitThread(0); -#endif - return 0; -} - - -static void MQTTAsync_stop(void) -{ -#if !defined(NOSTACKTRACE) - int rc = 0; -#endif - - FUNC_ENTRY; - if (sendThread_state != STOPPED || receiveThread_state != STOPPED) - { - int conn_count = 0; - ListElement* current = NULL; - - if (handles != NULL) - { - /* find out how many handles are still connected */ - while (ListNextElement(handles, ¤t)) - { - if (((MQTTAsyncs*)(current->content))->c->connect_state > NOT_IN_PROGRESS || - ((MQTTAsyncs*)(current->content))->c->connected) - ++conn_count; - } - } - Log(TRACE_MIN, -1, "Conn_count is %d", conn_count); - /* stop the background thread, if we are the last one to be using it */ - if (conn_count == 0) - { - int count = 0; - tostop = 1; - while ((sendThread_state != STOPPED || receiveThread_state != STOPPED) && ++count < 100) - { - MQTTAsync_unlock_mutex(mqttasync_mutex); - Log(TRACE_MIN, -1, "sleeping"); - MQTTAsync_sleep(100L); - MQTTAsync_lock_mutex(mqttasync_mutex); - } -#if !defined(NOSTACKTRACE) - rc = 1; -#endif - tostop = 0; - } - } - FUNC_EXIT_RC(rc); -} - - -int MQTTAsync_setCallbacks(MQTTAsync handle, void* context, - MQTTAsync_connectionLost* cl, - MQTTAsync_messageArrived* ma, - MQTTAsync_deliveryComplete* dc) -{ - int rc = MQTTASYNC_SUCCESS; - MQTTAsyncs* m = handle; - - FUNC_ENTRY; - MQTTAsync_lock_mutex(mqttasync_mutex); - - if (m == NULL || ma == NULL || m->c == NULL || m->c->connect_state != NOT_IN_PROGRESS) - rc = MQTTASYNC_FAILURE; - else - { - m->clContext = m->maContext = m->dcContext = context; - m->cl = cl; - m->ma = ma; - m->dc = dc; - } - - MQTTAsync_unlock_mutex(mqttasync_mutex); - FUNC_EXIT_RC(rc); - return rc; -} - -int MQTTAsync_setConnectionLostCallback(MQTTAsync handle, void* context, - MQTTAsync_connectionLost* cl) -{ - int rc = MQTTASYNC_SUCCESS; - MQTTAsyncs* m = handle; - - FUNC_ENTRY; - MQTTAsync_lock_mutex(mqttasync_mutex); - - if (m == NULL || m->c->connect_state != 0) - rc = MQTTASYNC_FAILURE; - else - { - m->clContext = context; - m->cl = cl; - } - - MQTTAsync_unlock_mutex(mqttasync_mutex); - FUNC_EXIT_RC(rc); - return rc; -} - - -int MQTTAsync_setMessageArrivedCallback(MQTTAsync handle, void* context, - MQTTAsync_messageArrived* ma) -{ - int rc = MQTTASYNC_SUCCESS; - MQTTAsyncs* m = handle; - - FUNC_ENTRY; - MQTTAsync_lock_mutex(mqttasync_mutex); - - if (m == NULL || ma == NULL || m->c->connect_state != 0) - rc = MQTTASYNC_FAILURE; - else - { - m->maContext = context; - m->ma = ma; - } - - MQTTAsync_unlock_mutex(mqttasync_mutex); - FUNC_EXIT_RC(rc); - return rc; -} - -int MQTTAsync_setDeliveryCompleteCallback(MQTTAsync handle, void* context, - MQTTAsync_deliveryComplete* dc) -{ - int rc = MQTTASYNC_SUCCESS; - MQTTAsyncs* m = handle; - - FUNC_ENTRY; - MQTTAsync_lock_mutex(mqttasync_mutex); - - if (m == NULL || m->c->connect_state != 0) - rc = MQTTASYNC_FAILURE; - else - { - m->dcContext = context; - m->dc = dc; - } - - MQTTAsync_unlock_mutex(mqttasync_mutex); - FUNC_EXIT_RC(rc); - return rc; -} - - -int MQTTAsync_setDisconnected(MQTTAsync handle, void* context, MQTTAsync_disconnected* disconnected) -{ - int rc = MQTTASYNC_SUCCESS; - MQTTAsyncs* m = handle; - - FUNC_ENTRY; - MQTTAsync_lock_mutex(mqttasync_mutex); - - if (m == NULL || m->c->connect_state != NOT_IN_PROGRESS) - rc = MQTTASYNC_FAILURE; - else - { - m->disconnected_context = context; - m->disconnected = disconnected; - } - - MQTTAsync_unlock_mutex(mqttasync_mutex); - FUNC_EXIT_RC(rc); - return rc; -} - - -int MQTTAsync_setConnected(MQTTAsync handle, void* context, MQTTAsync_connected* connected) -{ - int rc = MQTTASYNC_SUCCESS; - MQTTAsyncs* m = handle; - - FUNC_ENTRY; - MQTTAsync_lock_mutex(mqttasync_mutex); - - if (m == NULL || m->c->connect_state != NOT_IN_PROGRESS) - rc = MQTTASYNC_FAILURE; - else - { - m->connected_context = context; - m->connected = connected; - } - - MQTTAsync_unlock_mutex(mqttasync_mutex); - FUNC_EXIT_RC(rc); - return rc; -} - - -int MQTTAsync_setUpdateConnectOptions(MQTTAsync handle, void* context, MQTTAsync_updateConnectOptions* updateOptions) -{ - int rc = MQTTASYNC_SUCCESS; - MQTTAsyncs* m = handle; - - FUNC_ENTRY; - MQTTAsync_lock_mutex(mqttasync_mutex); - - if (m == NULL) - rc = MQTTASYNC_FAILURE; - else - { - m->updateConnectOptions_context = context; - m->updateConnectOptions = updateOptions; - } - - MQTTAsync_unlock_mutex(mqttasync_mutex); - FUNC_EXIT_RC(rc); - return rc; -} - - -int MQTTAsync_setBeforePersistenceWrite(MQTTAsync handle, void* context, MQTTPersistence_beforeWrite* co) -{ - int rc = MQTTASYNC_SUCCESS; - MQTTAsyncs* m = handle; - - FUNC_ENTRY; - MQTTAsync_lock_mutex(mqttasync_mutex); - - if (m == NULL) - rc = MQTTASYNC_FAILURE; - else - { - m->c->beforeWrite = co; - m->c->beforeWrite_context = context; - } - - MQTTAsync_unlock_mutex(mqttasync_mutex); - FUNC_EXIT_RC(rc); - return rc; -} - - -int MQTTAsync_setAfterPersistenceRead(MQTTAsync handle, void* context, MQTTPersistence_afterRead* co) -{ - int rc = MQTTASYNC_SUCCESS; - MQTTAsyncs* m = handle; - - FUNC_ENTRY; - MQTTAsync_lock_mutex(mqttasync_mutex); - - if (m == NULL) - rc = MQTTASYNC_FAILURE; - else - { - m->c->afterRead = co; - m->c->afterRead_context = context; - } - - MQTTAsync_unlock_mutex(mqttasync_mutex); - FUNC_EXIT_RC(rc); - return rc; -} - - -static void MQTTAsync_closeOnly(Clients* client, enum MQTTReasonCodes reasonCode, MQTTProperties* props) -{ - FUNC_ENTRY; - client->good = 0; - client->ping_outstanding = 0; - if (client->net.socket > 0) - { - MQTTProtocol_checkPendingWrites(); - if (client->connected && Socket_noPendingWrites(client->net.socket)) - MQTTPacket_send_disconnect(client, reasonCode, props); - Thread_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 */ - client->session = NULL; /* show the session has been freed */ - SSLSocket_close(&client->net); -#endif - Socket_close(client->net.socket); - client->net.socket = 0; -#if defined(OPENSSL) - client->net.ssl = NULL; -#endif - Thread_unlock_mutex(socket_mutex); - } - client->connected = 0; - client->connect_state = NOT_IN_PROGRESS; - FUNC_EXIT; -} - - -static void MQTTAsync_closeSession(Clients* client, enum MQTTReasonCodes reasonCode, MQTTProperties* props) -{ - FUNC_ENTRY; - MQTTAsync_closeOnly(client, reasonCode, props); - - if (client->cleansession || - (client->MQTTVersion >= MQTTVERSION_5 && client->sessionExpiry == 0)) - MQTTAsync_cleanSession(client); - - FUNC_EXIT; -} - - -/** - * List callback function for comparing clients by client structure - * @param a Async structure - * @param b Client structure - * @return boolean indicating whether a and b are equal - */ -static int clientStructCompare(void* a, void* b) -{ - MQTTAsyncs* m = (MQTTAsyncs*)a; - return m->c == (Clients*)b; -} - - -/** - * Clean the MQTT session data. This includes the MQTT inflight messages, because - * that is part of the MQTT state that will be cleared by the MQTT broker too. - * However, queued up messages, outgoing or incoming, need (should?) not be cleared - * as they are outside the scope of the MQTT session. - */ -static int MQTTAsync_cleanSession(Clients* client) -{ - int rc = 0; - ListElement* found = NULL; - - FUNC_ENTRY; -#if !defined(NO_PERSISTENCE) - rc = MQTTAsync_unpersistInflightMessages(client); -#endif - MQTTProtocol_emptyMessageList(client->inboundMsgs); - MQTTProtocol_emptyMessageList(client->outboundMsgs); - client->msgID = 0; - if ((found = ListFindItem(handles, client, clientStructCompare)) != NULL) - { - MQTTAsyncs* m = (MQTTAsyncs*)(found->content); - MQTTAsync_freeResponses(m); - } - else - Log(LOG_ERROR, -1, "cleanSession: did not find client structure in handles list"); - FUNC_EXIT_RC(rc); - return rc; -} - - -/* -static int MQTTAsync_freeSession(Clients* client) -{ - int rc = 0; - ListElement* found = NULL; - - FUNC_ENTRY; - MQTTAsync_emptyMessageQueue(client); - - if ((found = ListFindItem(handles, client, clientStructCompare)) != NULL) - { - MQTTAsyncs* m = (MQTTAsyncs*)(found->content); - MQTTAsync_freeCommands(m); - } - else - Log(LOG_ERROR, -1, "freeSession: did not find client structure in handles list"); - FUNC_EXIT_RC(rc); - return rc; -}*/ - - -/* -* Deliver a message to the messageArrived callback -* @param m a client structure -* @param topicName the name of the topic on which the message is being delivered -* @param topicLen the length of the topic name string -* @param mm the message to be delivered -* @return boolean 1 means message has been delivered, 0 that it has not -*/ -static int MQTTAsync_deliverMessage(MQTTAsyncs* m, char* topicName, size_t topicLen, MQTTAsync_message* mm) -{ - int rc; - - Log(TRACE_MIN, -1, "Calling messageArrived for client %s, queue depth %d", - m->c->clientID, m->c->messageQueue->count); - rc = (*(m->ma))(m->maContext, topicName, (int)topicLen, mm); - /* if 0 (false) is returned by the callback then it failed, so we don't remove the message from - * the queue, and it will be retried later. If 1 is returned then the message data may have been freed, - * so we must be careful how we use it. - */ - return rc; -} - - -void Protocol_processPublication(Publish* publish, Clients* client, int allocatePayload) -{ - MQTTAsync_message* mm = NULL; - MQTTAsync_message initialized = MQTTAsync_message_initializer; - int rc = 0; - - FUNC_ENTRY; - if ((mm = malloc(sizeof(MQTTAsync_message))) == NULL) - goto exit; - memcpy(mm, &initialized, sizeof(MQTTAsync_message)); - - if (allocatePayload) - { - if ((mm->payload = malloc(publish->payloadlen)) == NULL) - { - free(mm); - goto exit; - } - memcpy(mm->payload, publish->payload, publish->payloadlen); - } else - mm->payload = publish->payload; - mm->payloadlen = publish->payloadlen; - mm->qos = publish->header.bits.qos; - mm->retained = publish->header.bits.retain; - if (publish->header.bits.qos == 2) - mm->dup = 0; /* ensure that a QoS2 message is not passed to the application with dup = 1 */ - else - mm->dup = publish->header.bits.dup; - mm->msgid = publish->msgId; - - if (publish->MQTTVersion >= MQTTVERSION_5) - mm->properties = MQTTProperties_copy(&publish->properties); - - if (client->messageQueue->count == 0 && client->connected) - { - ListElement* found = NULL; - - if ((found = ListFindItem(handles, client, clientStructCompare)) == NULL) - Log(LOG_ERROR, -1, "processPublication: did not find client structure in handles list"); - else - { - MQTTAsyncs* m = (MQTTAsyncs*)(found->content); - - if (m->ma) - rc = MQTTAsync_deliverMessage(m, publish->topic, publish->topiclen, mm); - } - } - - if (rc == 0) /* if message was not delivered, queue it up */ - { - qEntry* qe = malloc(sizeof(qEntry)); - - if (!qe) - goto exit; - qe->msg = mm; - qe->topicName = publish->topic; - qe->topicLen = publish->topiclen; - ListAppend(client->messageQueue, qe, sizeof(qe) + sizeof(mm) + mm->payloadlen + strlen(qe->topicName)+1); -#if !defined(NO_PERSISTENCE) - if (client->persistence) - MQTTPersistence_persistQueueEntry(client, (MQTTPersistence_qEntry*)qe); -#endif - } -exit: - publish->topic = NULL; - FUNC_EXIT; -} - - -static int retryLoopInterval = 5; - -static void setRetryLoopInterval(int keepalive) -{ - int proposed = keepalive / 10; - - if (proposed < 1) - proposed = 1; - else if (proposed > 5) - proposed = 5; - if (proposed < retryLoopInterval) - retryLoopInterval = proposed; -} - - int MQTTAsync_connect(MQTTAsync handle, const MQTTAsync_connectOptions* options) { MQTTAsyncs* m = handle; @@ -3603,7 +625,7 @@ int MQTTAsync_connect(MQTTAsync handle, const MQTTAsync_connectOptions* options) m->connect.context = options->context; m->connectTimeout = options->connectTimeout; - tostop = 0; + MQTTAsync_tostop = 0; /* don't lock async mutex if we are being called from a callback */ thread_id = Thread_getid(); @@ -3879,154 +901,53 @@ exit: } -static int MQTTAsync_disconnect1(MQTTAsync handle, const MQTTAsync_disconnectOptions* options, int internal) +int MQTTAsync_reconnect(MQTTAsync handle) { + int rc = MQTTASYNC_FAILURE; MQTTAsyncs* m = handle; - int rc = MQTTASYNC_SUCCESS; - MQTTAsync_queuedCommand* dis; - - FUNC_ENTRY; - if (m == NULL || m->c == NULL) - { - rc = MQTTASYNC_FAILURE; - goto exit; - } - if (!internal) - m->shouldBeConnected = 0; - if (m->c->connected == 0) - { - rc = MQTTASYNC_DISCONNECTED; - goto exit; - } - - /* Add disconnect request to operation queue */ - if ((dis = malloc(sizeof(MQTTAsync_queuedCommand))) == NULL) - { - rc = PAHO_MEMORY_ERROR; - goto exit; - } - memset(dis, '\0', sizeof(MQTTAsync_queuedCommand)); - dis->client = m; - if (options) - { - dis->command.onSuccess = options->onSuccess; - dis->command.onFailure = options->onFailure; - dis->command.onSuccess5 = options->onSuccess5; - dis->command.onFailure5 = options->onFailure5; - dis->command.context = options->context; - dis->command.details.dis.timeout = options->timeout; - if (m->c->MQTTVersion >= MQTTVERSION_5 && options->struct_version >= 1) - { - dis->command.properties = MQTTProperties_copy(&options->properties); - dis->command.details.dis.reasonCode = options->reasonCode; - } - } - dis->command.type = DISCONNECT; - dis->command.details.dis.internal = internal; - rc = MQTTAsync_addCommand(dis, sizeof(dis)); - -exit: - FUNC_EXIT_RC(rc); - return rc; -} - - -static int MQTTAsync_disconnect_internal(MQTTAsync handle, int timeout) -{ - MQTTAsync_disconnectOptions options = MQTTAsync_disconnectOptions_initializer; - - options.timeout = timeout; - return MQTTAsync_disconnect1(handle, &options, 1); -} - - -void MQTTProtocol_closeSession(Clients* c, int sendwill) -{ - MQTTAsync_disconnect_internal((MQTTAsync)c->context, 0); -} - - -int MQTTAsync_disconnect(MQTTAsync handle, const MQTTAsync_disconnectOptions* options) -{ - if (options != NULL && (strncmp(options->struct_id, "MQTD", 4) != 0 || options->struct_version < 0 || options->struct_version > 1)) - return MQTTASYNC_BAD_STRUCTURE; - else - return MQTTAsync_disconnect1(handle, options, 0); -} - - -int MQTTAsync_isConnected(MQTTAsync handle) -{ - MQTTAsyncs* m = handle; - int rc = 0; FUNC_ENTRY; MQTTAsync_lock_mutex(mqttasync_mutex); - if (m && m->c) - rc = m->c->connected; + + if (m->automaticReconnect) + { + if (m->shouldBeConnected) + { + m->reconnectNow = 1; + if (m->retrying == 0) + { + m->currentIntervalBase = m->minRetryInterval; + m->currentInterval = m->minRetryInterval; + m->retrying = 1; + } + rc = MQTTASYNC_SUCCESS; + } + } + else + { + /* to reconnect, put the connect command to the head of the command queue */ + MQTTAsync_queuedCommand* conn = malloc(sizeof(MQTTAsync_queuedCommand)); + if (!conn) + { + rc = PAHO_MEMORY_ERROR; + goto exit; + } + memset(conn, '\0', sizeof(MQTTAsync_queuedCommand)); + conn->client = m; + conn->command = m->connect; + /* make sure that the version attempts are restarted */ + if (m->c->MQTTVersion == MQTTVERSION_DEFAULT) + conn->command.details.conn.MQTTVersion = 0; + rc = MQTTAsync_addCommand(conn, sizeof(m->connect)); + } + +exit: MQTTAsync_unlock_mutex(mqttasync_mutex); FUNC_EXIT_RC(rc); return rc; } -static int cmdMessageIDCompare(void* a, void* b) -{ - MQTTAsync_queuedCommand* cmd = (MQTTAsync_queuedCommand*)a; - return cmd->command.token == *(int*)b; -} - - -/** - * Assign a new message id for a client. Make sure it isn't already being used and does - * not exceed the maximum. - * @param m a client structure - * @return the next message id to use, or 0 if none available - */ -static int MQTTAsync_assignMsgId(MQTTAsyncs* m) -{ - int start_msgid; - int msgid; - thread_id_type thread_id = 0; - int locked = 0; - - /* need to check: commands list and response list for a client */ - FUNC_ENTRY; - /* We might be called in a callback. In which case, this mutex will be already locked. */ - thread_id = Thread_getid(); - if (thread_id != sendThread_id && thread_id != receiveThread_id) - { - MQTTAsync_lock_mutex(mqttasync_mutex); - locked = 1; - } - - /* Fetch last message ID in locked state */ - start_msgid = m->c->msgID; - msgid = start_msgid; - - MQTTAsync_lock_mutex(mqttcommand_mutex); - msgid = (msgid == MAX_MSG_ID) ? 1 : msgid + 1; - while (ListFindItem(commands, &msgid, cmdMessageIDCompare) || - ListFindItem(m->c->outboundMsgs, &msgid, messageIDCompare) || - ListFindItem(m->responses, &msgid, cmdMessageIDCompare)) - { - msgid = (msgid == MAX_MSG_ID) ? 1 : msgid + 1; - if (msgid == start_msgid) - { /* we've tried them all - none free */ - msgid = 0; - break; - } - } - MQTTAsync_unlock_mutex(mqttcommand_mutex); - if (msgid != 0) - m->c->msgID = msgid; - if (locked) - MQTTAsync_unlock_mutex(mqttasync_mutex); - FUNC_EXIT_RC(msgid); - return msgid; -} - - int MQTTAsync_subscribeMany(MQTTAsync handle, int count, char* const* topic, int* qos, MQTTAsync_responseOptions* response) { MQTTAsyncs* m = handle; @@ -4242,17 +1163,6 @@ int MQTTAsync_unsubscribe(MQTTAsync handle, const char* topic, MQTTAsync_respons } -static int MQTTAsync_getNoBufferedMessages(MQTTAsyncs* m) -{ - int count = 0; - - MQTTAsync_lock_mutex(mqttcommand_mutex); - count = m->noBufferedMessages; - MQTTAsync_unlock_mutex(mqttcommand_mutex); - return count; -} - - int MQTTAsync_send(MQTTAsync handle, const char* destinationName, int payloadlen, const void* payload, int qos, int retained, MQTTAsync_responseOptions* response) { @@ -4350,7 +1260,6 @@ exit: } - int MQTTAsync_sendMessage(MQTTAsync handle, const char* destinationName, const MQTTAsync_message* message, MQTTAsync_responseOptions* response) { @@ -4381,447 +1290,24 @@ exit: } -static void MQTTAsync_retry(void) +int MQTTAsync_disconnect(MQTTAsync handle, const MQTTAsync_disconnectOptions* options) { - static START_TIME_TYPE last = START_TIME_ZERO; - START_TIME_TYPE now; - - FUNC_ENTRY; - now = MQTTTime_now(); - if (MQTTTime_difftime(now, last) > (DIFF_TIME_TYPE)(retryLoopInterval * 1000)) - { - last = MQTTTime_now(); - MQTTProtocol_keepalive(now); - MQTTProtocol_retry(now, 1, 0); - } + if (options != NULL && (strncmp(options->struct_id, "MQTD", 4) != 0 || options->struct_version < 0 || options->struct_version > 1)) + return MQTTASYNC_BAD_STRUCTURE; else - MQTTProtocol_retry(now, 0, 0); - FUNC_EXIT; + return MQTTAsync_disconnect1(handle, options, 0); } -static int MQTTAsync_connecting(MQTTAsyncs* m) +int MQTTAsync_isConnected(MQTTAsync handle) { - int rc = -1; - char* serverURI = m->serverURI; -#if defined(OPENSSL) - int default_port = MQTT_DEFAULT_PORT; -#endif - - FUNC_ENTRY; - if (m->serverURIcount > 0) - { - serverURI = m->serverURIs[m->connect.details.conn.currentURI]; - - /* skip URI scheme */ - if (strncmp(URI_TCP, serverURI, strlen(URI_TCP)) == 0) - serverURI += strlen(URI_TCP); - else if (strncmp(URI_WS, serverURI, strlen(URI_WS)) == 0) - { - serverURI += strlen(URI_WS); -#if defined(OPENSSL) - default_port = WS_DEFAULT_PORT; -#endif - } -#if defined(OPENSSL) - else if (strncmp(URI_SSL, serverURI, strlen(URI_SSL)) == 0) - { - serverURI += strlen(URI_SSL); - default_port = SECURE_MQTT_DEFAULT_PORT; - } - else if (strncmp(URI_WSS, serverURI, strlen(URI_WSS)) == 0) - { - serverURI += strlen(URI_WSS); - default_port = WS_DEFAULT_PORT; - } -#endif - } - - if (m->c->connect_state == TCP_IN_PROGRESS) /* TCP connect started - check for completion */ - { - int error; - socklen_t len = sizeof(error); - - if ((rc = getsockopt(m->c->net.socket, SOL_SOCKET, SO_ERROR, (char*)&error, &len)) == 0) - rc = error; - - if (rc != 0) - goto exit; - - Socket_clearPendingWrite(m->c->net.socket); - -#if defined(OPENSSL) - if (m->ssl) - { - int port; - size_t hostname_len; - int setSocketForSSLrc = 0; - - if (m->websocket && 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 ) - goto exit; - } - - hostname_len = MQTTProtocol_addressPort(serverURI, &port, NULL, default_port); - setSocketForSSLrc = SSLSocket_setSocketForSSL(&m->c->net, m->c->sslopts, - serverURI, hostname_len); - - if (setSocketForSSLrc != MQTTASYNC_SUCCESS) - { - if (m->c->session != NULL) - if ((rc = SSL_set_session(m->c->net.ssl, m->c->session)) != 1) - Log(TRACE_MIN, -1, "Failed to set SSL session with stored data, non critical"); - rc = m->c->sslopts->struct_version >= 3 ? - SSLSocket_connect(m->c->net.ssl, m->c->net.socket, serverURI, - m->c->sslopts->verify, m->c->sslopts->ssl_error_cb, m->c->sslopts->ssl_error_context) : - SSLSocket_connect(m->c->net.ssl, m->c->net.socket, serverURI, - m->c->sslopts->verify, NULL, NULL); - if (rc == TCPSOCKET_INTERRUPTED) - { - rc = MQTTCLIENT_SUCCESS; /* the connect is still in progress */ - m->c->connect_state = SSL_IN_PROGRESS; - } - else if (rc == SSL_FATAL) - { - rc = SOCKET_ERROR; - goto exit; - } - else if (rc == 1) - { - if ( m->websocket ) - { - m->c->connect_state = WEBSOCKET_IN_PROGRESS; - if ((rc = WebSocket_connect(&m->c->net, serverURI)) == SOCKET_ERROR ) - goto exit; - } - else - { - rc = MQTTCLIENT_SUCCESS; - m->c->connect_state = WAIT_FOR_CONNACK; - if (MQTTPacket_send_connect(m->c, m->connect.details.conn.MQTTVersion, - m->connectProps, m->willProps) == SOCKET_ERROR) - { - rc = SOCKET_ERROR; - goto exit; - } - } - if (!m->c->cleansession && m->c->session == NULL) - m->c->session = SSL_get1_session(m->c->net.ssl); - } - } - else - { - rc = SOCKET_ERROR; - goto exit; - } - } - else - { -#endif - 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 ) - goto exit; - } - else - { - m->c->connect_state = WAIT_FOR_CONNACK; /* TCP/SSL connect completed, in which case send the MQTT connect packet */ - if ((rc = MQTTPacket_send_connect(m->c, m->connect.details.conn.MQTTVersion, - m->connectProps, m->willProps)) == SOCKET_ERROR) - goto exit; - } -#if defined(OPENSSL) - } -#endif - } -#if defined(OPENSSL) - else if (m->c->connect_state == SSL_IN_PROGRESS) /* SSL connect sent - wait for completion */ - { - rc = m->c->sslopts->struct_version >= 3 ? - SSLSocket_connect(m->c->net.ssl, m->c->net.socket, serverURI, - m->c->sslopts->verify, m->c->sslopts->ssl_error_cb, m->c->sslopts->ssl_error_context) : - SSLSocket_connect(m->c->net.ssl, m->c->net.socket, serverURI, - m->c->sslopts->verify, NULL, NULL); - if (rc != 1) - goto exit; - - if(!m->c->cleansession && m->c->session == NULL) - m->c->session = SSL_get1_session(m->c->net.ssl); - - if ( m->websocket ) - { - m->c->connect_state = WEBSOCKET_IN_PROGRESS; - if ((rc = WebSocket_connect(&m->c->net, serverURI)) == SOCKET_ERROR ) - goto exit; - } - else - { - m->c->connect_state = WAIT_FOR_CONNACK; /* SSL connect completed, in which case send the MQTT connect packet */ - if ((rc = MQTTPacket_send_connect(m->c, m->connect.details.conn.MQTTVersion, - m->connectProps, m->willProps)) == SOCKET_ERROR) - goto exit; - } - } -#endif - else if (m->c->connect_state == WEBSOCKET_IN_PROGRESS) /* Websocket connect sent - wait for completion */ - { - if ((rc = WebSocket_upgrade( &m->c->net ) ) == SOCKET_ERROR ) - goto exit; - else if (rc != TCPSOCKET_INTERRUPTED) - { - m->c->connect_state = WAIT_FOR_CONNACK; /* Websocket upgrade completed, in which case send the MQTT connect packet */ - if ((rc = MQTTPacket_send_connect(m->c, m->connect.details.conn.MQTTVersion, m->connectProps, m->willProps)) == SOCKET_ERROR) - goto exit; - } - } - -exit: - if ((rc != 0 && rc != TCPSOCKET_INTERRUPTED && (m->c->connect_state != SSL_IN_PROGRESS && m->c->connect_state != WEBSOCKET_IN_PROGRESS)) || (rc == SSL_FATAL)) - nextOrClose(m, MQTTASYNC_FAILURE, "TCP/TLS connect failure"); - - FUNC_EXIT_RC(rc); - return rc; -} - - -static MQTTPacket* MQTTAsync_cycle(int* sock, unsigned long timeout, int* rc) -{ - struct timeval tp = {0L, 0L}; - MQTTPacket* pack = NULL; - - 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 - /* 0 from getReadySocket indicates no work to do, rc -1 == error */ - *sock = Socket_getReadySocket(0, &tp,socket_mutex, &rc1); - *rc = rc1; - if (!tostop && *sock == 0 && (tp.tv_sec > 0L || tp.tv_usec > 0L)) - MQTTAsync_sleep(100L); -#if defined(OPENSSL) - } -#endif - MQTTAsync_lock_mutex(mqttasync_mutex); - if (*sock > 0 && rc1 == 0) - { - MQTTAsyncs* m = NULL; - if (ListFindItem(handles, sock, clientSockCompare) != NULL) - m = (MQTTAsync)(handles->current->content); - if (m != NULL) - { - Log(TRACE_MINIMUM, -1, "m->c->connect_state = %d", m->c->connect_state); - if (m->c->connect_state == TCP_IN_PROGRESS || m->c->connect_state == SSL_IN_PROGRESS || m->c->connect_state == WEBSOCKET_IN_PROGRESS) - *rc = MQTTAsync_connecting(m); - else - pack = MQTTPacket_Factory(m->c->MQTTVersion, &m->c->net, rc); - if (m->c->connect_state == WAIT_FOR_CONNACK && *rc == SOCKET_ERROR) - { - Log(TRACE_MINIMUM, -1, "CONNECT sent but MQTTPacket_Factory has returned SOCKET_ERROR"); - nextOrClose(m, MQTTASYNC_FAILURE, "TCP connect completion failure"); - } - } - if (pack) - { - int freed = 1; - - /* Note that these handle... functions free the packet structure that they are dealing with */ - if (pack->header.bits.type == PUBLISH) - *rc = MQTTProtocol_handlePublishes(pack, *sock); - else if (pack->header.bits.type == PUBACK || pack->header.bits.type == PUBCOMP || - pack->header.bits.type == PUBREC) - { - int msgid = 0, - msgtype = 0, - ackrc = 0, - mqttversion = 0; - MQTTProperties msgprops = MQTTProperties_initializer; - - /* This block is so that the ack variable is local and isn't accidentally reused */ - { - static Ack ack; - ack = *(Ack*)pack; - /* these values are stored because the packet structure is freed in the handle functions */ - msgid = ack.msgId; - msgtype = pack->header.bits.type; - if (ack.MQTTVersion >= MQTTVERSION_5) - { - ackrc = ack.rc; - msgprops = MQTTProperties_copy(&ack.properties); - mqttversion = ack.MQTTVersion; - } - } - - if (pack->header.bits.type == PUBCOMP) - *rc = MQTTProtocol_handlePubcomps(pack, *sock); - else if (pack->header.bits.type == PUBREC) - *rc = MQTTProtocol_handlePubrecs(pack, *sock); - else if (pack->header.bits.type == PUBACK) - *rc = MQTTProtocol_handlePubacks(pack, *sock); - if (!m) - Log(LOG_ERROR, -1, "PUBCOMP, PUBACK or PUBREC received for no client, msgid %d", msgid); - if (m && (msgtype != PUBREC || ackrc >= MQTTREASONCODE_UNSPECIFIED_ERROR)) - { - ListElement* current = NULL; - - if (m->dc) - { - Log(TRACE_MIN, -1, "Calling deliveryComplete for client %s, msgid %d", m->c->clientID, msgid); - (*(m->dc))(m->dcContext, msgid); - } - /* use the msgid to find the callback to be called */ - while (ListNextElement(m->responses, ¤t)) - { - MQTTAsync_queuedCommand* command = (MQTTAsync_queuedCommand*)(current->content); - if (command->command.token == msgid) - { - if (!ListDetach(m->responses, command)) /* then remove the response from the list */ - Log(LOG_ERROR, -1, "Publish command not removed from command list"); - if (command->command.onSuccess) - { - MQTTAsync_successData data; - - data.token = command->command.token; - data.alt.pub.destinationName = command->command.details.pub.destinationName; - data.alt.pub.message.payload = command->command.details.pub.payload; - data.alt.pub.message.payloadlen = command->command.details.pub.payloadlen; - data.alt.pub.message.qos = command->command.details.pub.qos; - data.alt.pub.message.retained = command->command.details.pub.retained; - Log(TRACE_MIN, -1, "Calling publish success for client %s", m->c->clientID); - (*(command->command.onSuccess))(command->command.context, &data); - } - else if (command->command.onSuccess5 && ackrc < MQTTREASONCODE_UNSPECIFIED_ERROR) - { - MQTTAsync_successData5 data = MQTTAsync_successData5_initializer; - - data.token = command->command.token; - data.alt.pub.destinationName = command->command.details.pub.destinationName; - data.alt.pub.message.payload = command->command.details.pub.payload; - data.alt.pub.message.payloadlen = command->command.details.pub.payloadlen; - data.alt.pub.message.qos = command->command.details.pub.qos; - data.alt.pub.message.retained = command->command.details.pub.retained; - data.properties = command->command.properties; - Log(TRACE_MIN, -1, "Calling publish success for client %s", m->c->clientID); - (*(command->command.onSuccess5))(command->command.context, &data); - } - else if (command->command.onFailure5 && ackrc >= MQTTREASONCODE_UNSPECIFIED_ERROR) - { - MQTTAsync_failureData5 data = MQTTAsync_failureData5_initializer; - - data.token = command->command.token; - data.reasonCode = ackrc; - data.properties = msgprops; - data.packet_type = pack->header.bits.type; - Log(TRACE_MIN, -1, "Calling publish failure for client %s", m->c->clientID); - (*(command->command.onFailure5))(command->command.context, &data); - } - MQTTAsync_freeCommand(command); - break; - } - } - if (mqttversion >= MQTTVERSION_5) - MQTTProperties_free(&msgprops); - } - } - else if (pack->header.bits.type == PUBREL) - *rc = MQTTProtocol_handlePubrels(pack, *sock); - else if (pack->header.bits.type == PINGRESP) - *rc = MQTTProtocol_handlePingresps(pack, *sock); - else - freed = 0; - if (freed) - pack = NULL; - } - } - MQTTAsync_retry(); - MQTTAsync_unlock_mutex(mqttasync_mutex); - FUNC_EXIT_RC(*rc); - return pack; -} - -/* -static int pubCompare(void* a, void* b) -{ - Messages* msg = (Messages*)a; - return msg->publish == (Publications*)b; -}*/ - - -int MQTTAsync_getPendingTokens(MQTTAsync handle, MQTTAsync_token **tokens) -{ - int rc = MQTTASYNC_SUCCESS; MQTTAsyncs* m = handle; - ListElement* current = NULL; - int count = 0; + int rc = 0; FUNC_ENTRY; MQTTAsync_lock_mutex(mqttasync_mutex); - *tokens = NULL; - - if (m == NULL) - { - rc = MQTTASYNC_FAILURE; - goto exit; - } - - /* calculate the number of pending tokens - commands plus inflight */ - while (ListNextElement(commands, ¤t)) - { - MQTTAsync_queuedCommand* cmd = (MQTTAsync_queuedCommand*)(current->content); - - if (cmd->client == m) - count++; - } - if (m->c) - count += m->c->outboundMsgs->count; - if (count == 0) - goto exit; /* no tokens to return */ - *tokens = malloc(sizeof(MQTTAsync_token) * (count + 1)); /* add space for sentinel at end of list */ - if (!*tokens) - { - rc = PAHO_MEMORY_ERROR; - goto exit; - } - - /* First add the unprocessed commands to the pending tokens */ - current = NULL; - count = 0; - while (ListNextElement(commands, ¤t)) - { - MQTTAsync_queuedCommand* cmd = (MQTTAsync_queuedCommand*)(current->content); - - if (cmd->client == m) - (*tokens)[count++] = cmd->command.token; - } - - /* Now add the inflight messages */ - if (m->c && m->c->outboundMsgs->count > 0) - { - current = NULL; - while (ListNextElement(m->c->outboundMsgs, ¤t)) - { - Messages* m = (Messages*)(current->content); - (*tokens)[count++] = m->msgid; - } - } - (*tokens)[count] = -1; /* indicate end of list */ - -exit: + if (m && m->c) + rc = m->c->connected; MQTTAsync_unlock_mutex(mqttasync_mutex); FUNC_EXIT_RC(rc); return rc; @@ -4845,7 +1331,7 @@ int MQTTAsync_isComplete(MQTTAsync handle, MQTTAsync_token dt) /* First check unprocessed commands */ current = NULL; - while (ListNextElement(commands, ¤t)) + while (ListNextElement(MQTTAsync_commands, ¤t)) { MQTTAsync_queuedCommand* cmd = (MQTTAsync_queuedCommand*)(current->content); @@ -4921,6 +1407,275 @@ exit: } +int MQTTAsync_getPendingTokens(MQTTAsync handle, MQTTAsync_token **tokens) +{ + int rc = MQTTASYNC_SUCCESS; + MQTTAsyncs* m = handle; + ListElement* current = NULL; + int count = 0; + + FUNC_ENTRY; + MQTTAsync_lock_mutex(mqttasync_mutex); + *tokens = NULL; + + if (m == NULL) + { + rc = MQTTASYNC_FAILURE; + goto exit; + } + + /* calculate the number of pending tokens - commands plus inflight */ + while (ListNextElement(MQTTAsync_commands, ¤t)) + { + MQTTAsync_queuedCommand* cmd = (MQTTAsync_queuedCommand*)(current->content); + + if (cmd->client == m) + count++; + } + if (m->c) + count += m->c->outboundMsgs->count; + if (count == 0) + goto exit; /* no tokens to return */ + *tokens = malloc(sizeof(MQTTAsync_token) * (count + 1)); /* add space for sentinel at end of list */ + if (!*tokens) + { + rc = PAHO_MEMORY_ERROR; + goto exit; + } + + /* First add the unprocessed commands to the pending tokens */ + current = NULL; + count = 0; + while (ListNextElement(MQTTAsync_commands, ¤t)) + { + MQTTAsync_queuedCommand* cmd = (MQTTAsync_queuedCommand*)(current->content); + + if (cmd->client == m) + (*tokens)[count++] = cmd->command.token; + } + + /* Now add the inflight messages */ + if (m->c && m->c->outboundMsgs->count > 0) + { + current = NULL; + while (ListNextElement(m->c->outboundMsgs, ¤t)) + { + Messages* m = (Messages*)(current->content); + (*tokens)[count++] = m->msgid; + } + } + (*tokens)[count] = -1; /* indicate end of list */ + +exit: + MQTTAsync_unlock_mutex(mqttasync_mutex); + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTAsync_setCallbacks(MQTTAsync handle, void* context, + MQTTAsync_connectionLost* cl, + MQTTAsync_messageArrived* ma, + MQTTAsync_deliveryComplete* dc) +{ + int rc = MQTTASYNC_SUCCESS; + MQTTAsyncs* m = handle; + + FUNC_ENTRY; + MQTTAsync_lock_mutex(mqttasync_mutex); + + if (m == NULL || ma == NULL || m->c == NULL || m->c->connect_state != NOT_IN_PROGRESS) + rc = MQTTASYNC_FAILURE; + else + { + m->clContext = m->maContext = m->dcContext = context; + m->cl = cl; + m->ma = ma; + m->dc = dc; + } + + MQTTAsync_unlock_mutex(mqttasync_mutex); + FUNC_EXIT_RC(rc); + return rc; +} + +int MQTTAsync_setConnectionLostCallback(MQTTAsync handle, void* context, + MQTTAsync_connectionLost* cl) +{ + int rc = MQTTASYNC_SUCCESS; + MQTTAsyncs* m = handle; + + FUNC_ENTRY; + MQTTAsync_lock_mutex(mqttasync_mutex); + + if (m == NULL || m->c->connect_state != 0) + rc = MQTTASYNC_FAILURE; + else + { + m->clContext = context; + m->cl = cl; + } + + MQTTAsync_unlock_mutex(mqttasync_mutex); + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTAsync_setMessageArrivedCallback(MQTTAsync handle, void* context, + MQTTAsync_messageArrived* ma) +{ + int rc = MQTTASYNC_SUCCESS; + MQTTAsyncs* m = handle; + + FUNC_ENTRY; + MQTTAsync_lock_mutex(mqttasync_mutex); + + if (m == NULL || ma == NULL || m->c->connect_state != 0) + rc = MQTTASYNC_FAILURE; + else + { + m->maContext = context; + m->ma = ma; + } + + MQTTAsync_unlock_mutex(mqttasync_mutex); + FUNC_EXIT_RC(rc); + return rc; +} + +int MQTTAsync_setDeliveryCompleteCallback(MQTTAsync handle, void* context, + MQTTAsync_deliveryComplete* dc) +{ + int rc = MQTTASYNC_SUCCESS; + MQTTAsyncs* m = handle; + + FUNC_ENTRY; + MQTTAsync_lock_mutex(mqttasync_mutex); + + if (m == NULL || m->c->connect_state != 0) + rc = MQTTASYNC_FAILURE; + else + { + m->dcContext = context; + m->dc = dc; + } + + MQTTAsync_unlock_mutex(mqttasync_mutex); + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTAsync_setDisconnected(MQTTAsync handle, void* context, MQTTAsync_disconnected* disconnected) +{ + int rc = MQTTASYNC_SUCCESS; + MQTTAsyncs* m = handle; + + FUNC_ENTRY; + MQTTAsync_lock_mutex(mqttasync_mutex); + + if (m == NULL || m->c->connect_state != NOT_IN_PROGRESS) + rc = MQTTASYNC_FAILURE; + else + { + m->disconnected_context = context; + m->disconnected = disconnected; + } + + MQTTAsync_unlock_mutex(mqttasync_mutex); + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTAsync_setConnected(MQTTAsync handle, void* context, MQTTAsync_connected* connected) +{ + int rc = MQTTASYNC_SUCCESS; + MQTTAsyncs* m = handle; + + FUNC_ENTRY; + MQTTAsync_lock_mutex(mqttasync_mutex); + + if (m == NULL || m->c->connect_state != NOT_IN_PROGRESS) + rc = MQTTASYNC_FAILURE; + else + { + m->connected_context = context; + m->connected = connected; + } + + MQTTAsync_unlock_mutex(mqttasync_mutex); + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTAsync_setUpdateConnectOptions(MQTTAsync handle, void* context, MQTTAsync_updateConnectOptions* updateOptions) +{ + int rc = MQTTASYNC_SUCCESS; + MQTTAsyncs* m = handle; + + FUNC_ENTRY; + MQTTAsync_lock_mutex(mqttasync_mutex); + + if (m == NULL) + rc = MQTTASYNC_FAILURE; + else + { + m->updateConnectOptions_context = context; + m->updateConnectOptions = updateOptions; + } + + MQTTAsync_unlock_mutex(mqttasync_mutex); + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTAsync_setBeforePersistenceWrite(MQTTAsync handle, void* context, MQTTPersistence_beforeWrite* co) +{ + int rc = MQTTASYNC_SUCCESS; + MQTTAsyncs* m = handle; + + FUNC_ENTRY; + MQTTAsync_lock_mutex(mqttasync_mutex); + + if (m == NULL) + rc = MQTTASYNC_FAILURE; + else + { + m->c->beforeWrite = co; + m->c->beforeWrite_context = context; + } + + MQTTAsync_unlock_mutex(mqttasync_mutex); + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTAsync_setAfterPersistenceRead(MQTTAsync handle, void* context, MQTTPersistence_afterRead* co) +{ + int rc = MQTTASYNC_SUCCESS; + MQTTAsyncs* m = handle; + + FUNC_ENTRY; + MQTTAsync_lock_mutex(mqttasync_mutex); + + if (m == NULL) + rc = MQTTASYNC_FAILURE; + else + { + m->c->afterRead = co; + m->c->afterRead_context = context; + } + + MQTTAsync_unlock_mutex(mqttasync_mutex); + FUNC_EXIT_RC(rc); + return rc; +} + void MQTTAsync_setTraceLevel(enum MQTTASYNC_TRACE_LEVELS level) { @@ -5015,3 +1770,48 @@ const char* MQTTAsync_strerror(int code) sprintf(buf, "Unknown error code %d", code); return buf; } + + +void MQTTAsync_freeMessage(MQTTAsync_message** message) +{ + FUNC_ENTRY; + MQTTProperties_free(&(*message)->properties); + free((*message)->payload); + free(*message); + *message = NULL; + FUNC_EXIT; +} + + +void MQTTAsync_free(void* memory) +{ + FUNC_ENTRY; + free(memory); + FUNC_EXIT; +} + + +void* MQTTAsync_malloc(size_t size) +{ + void* val; + int rc = 0; + + FUNC_ENTRY; + val = malloc(size); + rc = (val != NULL); + FUNC_EXIT_RC(rc); + return val; +} + + +static void MQTTAsync_freeServerURIs(MQTTAsyncs* m) +{ + int i; + + for (i = 0; i < m->serverURIcount; ++i) + free(m->serverURIs[i]); + m->serverURIcount = 0; + if (m->serverURIs) + free(m->serverURIs); + m->serverURIs = NULL; +} diff --git a/src/MQTTAsyncUtils.c b/src/MQTTAsyncUtils.c new file mode 100644 index 00000000..e9b14180 --- /dev/null +++ b/src/MQTTAsyncUtils.c @@ -0,0 +1,3082 @@ +/******************************************************************************* + * Copyright (c) 2009, 2020 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 + * 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: + * Ian Craggs - initial implementation and documentation + * + *******************************************************************************/ + +#include +#include +#if !defined(_WIN32) && !defined(_WIN64) + #include +#endif + +#if !defined(NO_PERSISTENCE) +#include "MQTTPersistence.h" +#endif +#include "MQTTAsync.h" +#include "MQTTAsyncUtils.h" +#include "utf-8.h" +#include "MQTTProtocol.h" +#include "MQTTProtocolOut.h" +#include "Thread.h" +#include "SocketBuffer.h" +#include "StackTrace.h" +#include "Heap.h" +#include "OsWrapper.h" +#include "WebSocket.h" + +static int clientSockCompare(void* a, void* b); +static int MQTTAsync_checkConn(MQTTAsync_command* command, MQTTAsyncs* client); +#if !defined(NO_PERSISTENCE) +static int MQTTAsync_unpersistCommand(MQTTAsync_queuedCommand* qcmd); +static int MQTTAsync_persistCommand(MQTTAsync_queuedCommand* qcmd); +static MQTTAsync_queuedCommand* MQTTAsync_restoreCommand(char* buffer, int buflen, int MQTTVersion, MQTTAsync_queuedCommand*); +#endif +static void MQTTAsync_startConnectRetry(MQTTAsyncs* m); +static void MQTTAsync_checkDisconnect(MQTTAsync handle, MQTTAsync_command* command); +static void MQTTProtocol_checkPendingWrites(void); +static void MQTTAsync_freeCommand1(MQTTAsync_queuedCommand *command); +static void MQTTAsync_freeCommand(MQTTAsync_queuedCommand *command); +static int MQTTAsync_processCommand(void); +static void MQTTAsync_checkTimeouts(void); +static int MQTTAsync_completeConnection(MQTTAsyncs* m, Connack* connack); +static void MQTTAsync_stop(void); +static void MQTTAsync_closeOnly(Clients* client, enum MQTTReasonCodes reasonCode, MQTTProperties* props); +static int clientStructCompare(void* a, void* b); +static int MQTTAsync_cleanSession(Clients* client); +static int MQTTAsync_deliverMessage(MQTTAsyncs* m, char* topicName, size_t topicLen, MQTTAsync_message* mm); +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 int MQTTAsync_connecting(MQTTAsyncs* m); + +extern MQTTProtocol state; /* defined in MQTTAsync.c */ +extern ClientStates* bstate; /* defined in MQTTAsync.c */ + +extern enum MQTTAsync_threadStates sendThread_state; +extern enum MQTTAsync_threadStates receiveThread_state; +extern thread_id_type sendThread_id, + receiveThread_id; + +extern volatile int global_initialized; +extern List* MQTTAsync_handles; +extern List* MQTTAsync_commands; +extern int MQTTAsync_tostop; + +#if defined(_WIN32) || defined(_WIN64) +extern mutex_type mqttasync_mutex; +extern mutex_type socket_mutex; +extern mutex_type mqttcommand_mutex; +extern sem_type send_sem; +#if !defined(NO_HEAP_TRACKING) +extern mutex_type stack_mutex; +extern mutex_type heap_mutex; +#endif +extern mutex_type log_mutex; +#else +extern mutex_type mqttasync_mutex; +extern mutex_type socket_mutex; +extern mutex_type mqttcommand_mutex; +extern cond_type send_cond; +#endif + +void MQTTAsync_sleep(long milliseconds) +{ + FUNC_ENTRY; +#if defined(_WIN32) || defined(_WIN64) + Sleep(milliseconds); +#else + usleep(milliseconds*1000); +#endif + FUNC_EXIT; +} + + +/* Add random amount of jitter for exponential backoff on retry + Jitter value will be +/- 20% of "base" interval, including max interval + https://www.awsarchitectureblog.com/2015/03/backoff.html + http://ee.lbl.gov/papers/sync_94.pdf */ +int MQTTAsync_randomJitter(int currentIntervalBase, int minInterval, int maxInterval) +{ + const int max_sleep = (int)(min(maxInterval, currentIntervalBase) * 1.2); // (e.g. 72 if base > 60) + const int min_sleep = (int)(max(minInterval, currentIntervalBase) / 1.2); // (e.g. 48 if base > 60) + + if (min_sleep >= max_sleep) // shouldn't happen, but just in case + { + return min_sleep; + } + + { + /* random_between(min_sleep, max_sleep) + http://stackoverflow.com/questions/2509679/how-to-generate-a-random-number-from-within-a-range */ + int r; + int range = max_sleep - min_sleep + 1; + const int buckets = RAND_MAX / range; + const int limit = buckets * range; + + /* Create equal size buckets all in a row, then fire randomly towards + * the buckets until you land in one of them. All buckets are equally + * likely. If you land off the end of the line of buckets, try again. */ + do + { + r = rand(); + } while (r >= limit); + + { + const int randResult = r / buckets; + return min_sleep + randResult; + } + } +} + + +/** + * List callback function for comparing clients by socket + * @param a first integer value + * @param b second integer value + * @return boolean indicating whether a and b are equal + */ +static int clientSockCompare(void* a, void* b) +{ + MQTTAsyncs* m = (MQTTAsyncs*)a; + return m->c->net.socket == *(int*)b; +} + + +void MQTTAsync_lock_mutex(mutex_type amutex) +{ + int rc = Thread_lock_mutex(amutex); + if (rc != 0) + Log(LOG_ERROR, 0, "Error %s locking mutex", strerror(rc)); +} + + +void MQTTAsync_unlock_mutex(mutex_type amutex) +{ + int rc = Thread_unlock_mutex(amutex); + if (rc != 0) + Log(LOG_ERROR, 0, "Error %s unlocking mutex", strerror(rc)); +} + + +/* + Check whether there are any more connect options. If not then we are finished + with connect attempts. +*/ +static int MQTTAsync_checkConn(MQTTAsync_command* command, MQTTAsyncs* client) +{ + int rc; + + FUNC_ENTRY; + rc = command->details.conn.currentURI + 1 < client->serverURIcount || + (command->details.conn.MQTTVersion == 4 && client->c->MQTTVersion == MQTTVERSION_DEFAULT); + FUNC_EXIT_RC(rc); + return rc; +} + + +void MQTTAsync_terminate(void) +{ + FUNC_ENTRY; + MQTTAsync_stop(); + if (global_initialized) + { + ListElement* elem = NULL; + ListFree(bstate->clients); + ListFree(MQTTAsync_handles); + while (ListNextElement(MQTTAsync_commands, &elem)) + MQTTAsync_freeCommand1((MQTTAsync_queuedCommand*)(elem->content)); + ListFree(MQTTAsync_commands); + MQTTAsync_handles = NULL; + WebSocket_terminate(); + #if !defined(NO_HEAP_TRACKING) + Heap_terminate(); + #endif + Log_terminate(); + global_initialized = 0; + } + FUNC_EXIT; +} + + +#if !defined(NO_PERSISTENCE) +static int MQTTAsync_unpersistCommand(MQTTAsync_queuedCommand* qcmd) +{ + int rc = 0; + char key[PERSISTENCE_MAX_KEY_LENGTH + 1]; + + FUNC_ENTRY; + if (qcmd->client->c->MQTTVersion >= MQTTVERSION_5) + sprintf(key, "%s%u", PERSISTENCE_V5_COMMAND_KEY, qcmd->seqno); + else + sprintf(key, "%s%u", PERSISTENCE_COMMAND_KEY, qcmd->seqno); + if ((rc = qcmd->client->c->persistence->premove(qcmd->client->c->phandle, key)) != 0) + Log(LOG_ERROR, 0, "Error %d removing command from persistence", rc); + FUNC_EXIT_RC(rc); + return rc; +} + + +static int MQTTAsync_persistCommand(MQTTAsync_queuedCommand* qcmd) +{ + int rc = 0; + MQTTAsyncs* aclient = qcmd->client; + MQTTAsync_command* command = &qcmd->command; + int* lens = NULL; + void** bufs = NULL; + int bufindex = 0, i, nbufs = 0; + char key[PERSISTENCE_MAX_KEY_LENGTH + 1]; + int props_allocated = 0; + int process = 1; + + FUNC_ENTRY; + switch (command->type) + { + case SUBSCRIBE: + nbufs = ((aclient->c->MQTTVersion >= MQTTVERSION_5) ? 4 : 3) + + (command->details.sub.count * 2); + + if (((lens = (int*)malloc(nbufs * sizeof(int))) == NULL) || + ((bufs = malloc(nbufs * sizeof(char *))) == NULL)) + { + rc = PAHO_MEMORY_ERROR; + goto exit; + } + bufs[bufindex] = &command->type; + lens[bufindex++] = sizeof(command->type); + + bufs[bufindex] = &command->token; + lens[bufindex++] = sizeof(command->token); + + bufs[bufindex] = &command->details.sub.count; + lens[bufindex++] = sizeof(command->details.sub.count); + + for (i = 0; i < command->details.sub.count; ++i) + { + 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 + { + if (command->details.sub.count == 1) + { + bufs[bufindex] = &command->details.sub.opts; + lens[bufindex++] = sizeof(command->details.sub.opts); + } + else + { + bufs[bufindex] = &command->details.sub.optlist[i]; + lens[bufindex++] = sizeof(command->details.sub.optlist[i]); + } + } + } + break; + + case UNSUBSCRIBE: + nbufs = ((aclient->c->MQTTVersion >= MQTTVERSION_5) ? 4 : 3) + + command->details.unsub.count; + + if (((lens = (int*)malloc(nbufs * sizeof(int))) == NULL) || + ((bufs = malloc(nbufs * sizeof(char *))) == NULL)) + { + rc = PAHO_MEMORY_ERROR; + goto exit; + } + + bufs[bufindex] = &command->type; + lens[bufindex++] = sizeof(command->type); + + bufs[bufindex] = &command->token; + lens[bufindex++] = sizeof(command->token); + + bufs[bufindex] = &command->details.unsub.count; + lens[bufindex++] = sizeof(command->details.unsub.count); + + for (i = 0; i < command->details.unsub.count; ++i) + { + bufs[bufindex] = command->details.unsub.topics[i]; + lens[bufindex++] = (int)strlen(command->details.unsub.topics[i]) + 1; + } + break; + + case PUBLISH: + nbufs = (aclient->c->MQTTVersion >= MQTTVERSION_5) ? 8 : 7; + + if (((lens = (int*)malloc(nbufs * sizeof(int))) == NULL) || + ((bufs = malloc(nbufs * sizeof(char *))) == NULL)) + { + rc = PAHO_MEMORY_ERROR; + goto exit; + } + + bufs[bufindex] = &command->type; + lens[bufindex++] = sizeof(command->type); + + bufs[bufindex] = &command->token; + lens[bufindex++] = sizeof(command->token); + + bufs[bufindex] = command->details.pub.destinationName; + lens[bufindex++] = (int)strlen(command->details.pub.destinationName) + 1; + + bufs[bufindex] = &command->details.pub.payloadlen; + lens[bufindex++] = sizeof(command->details.pub.payloadlen); + + bufs[bufindex] = command->details.pub.payload; + lens[bufindex++] = command->details.pub.payloadlen; + + bufs[bufindex] = &command->details.pub.qos; + lens[bufindex++] = sizeof(command->details.pub.qos); + + bufs[bufindex] = &command->details.pub.retained; + lens[bufindex++] = sizeof(command->details.pub.retained); + break; + + default: + process = 0; + break; + } + + /* + * Increment the command sequence number. Don't exceed the maximum value allowed + * by the value PERSISTENCE_MAX_KEY_LENGTH minus the max prefix string length + */ + if (++aclient->command_seqno == PERSISTENCE_SEQNO_LIMIT) + aclient->command_seqno = 0; + + + if (aclient->c->MQTTVersion >= MQTTVERSION_5 && process) /* persist properties */ + { + int temp_len = 0; + char* ptr = NULL; + + temp_len = MQTTProperties_len(&command->properties); + if ((ptr = bufs[bufindex] = malloc(temp_len)) == NULL) + { + rc = PAHO_MEMORY_ERROR; + goto exit; + } + props_allocated = bufindex; + rc = MQTTProperties_write(&ptr, &command->properties); + lens[bufindex++] = temp_len; + sprintf(key, "%s%u", PERSISTENCE_V5_COMMAND_KEY, aclient->command_seqno); + } + else + sprintf(key, "%s%u", PERSISTENCE_COMMAND_KEY, aclient->command_seqno); + + if (nbufs > 0) + { + if (aclient->c->beforeWrite) + rc = aclient->c->beforeWrite(aclient->c->beforeWrite_context, nbufs, (char**)bufs, lens); + + if ((rc = aclient->c->persistence->pput(aclient->c->phandle, key, nbufs, (char**)bufs, lens)) != 0) + Log(LOG_ERROR, 0, "Error persisting command, rc %d", rc); + qcmd->seqno = aclient->command_seqno; + } +exit: + if (props_allocated > 0) + free(bufs[props_allocated]); + if (lens) + free(lens); + if (bufs) + free(bufs); + FUNC_EXIT_RC(rc); + return rc; +} + + +static MQTTAsync_queuedCommand* MQTTAsync_restoreCommand(char* buffer, int buflen, int MQTTVersion, MQTTAsync_queuedCommand* qcommand) +{ + MQTTAsync_command* command = NULL; + char* ptr = buffer; + int i; + size_t data_size; + + FUNC_ENTRY; + if (qcommand == NULL) + { + if ((qcommand = malloc(sizeof(MQTTAsync_queuedCommand))) == NULL) + goto exit; + memset(qcommand, '\0', sizeof(MQTTAsync_queuedCommand)); + qcommand->not_restored = 1; /* don't restore all the command on the first call */ + } + else + qcommand->not_restored = 0; + + command = &qcommand->command; + + command->type = *(int*)ptr; + ptr += sizeof(int); + + command->token = *(MQTTAsync_token*)ptr; + ptr += sizeof(MQTTAsync_token); + + switch (command->type) + { + case SUBSCRIBE: + if (qcommand->not_restored == 0) + break; + command->details.sub.count = *(int*)ptr; + ptr += sizeof(int); + + if (command->details.sub.count > 0) + { + if ((command->details.sub.topics = (char **)malloc(sizeof(char *) * command->details.sub.count)) == NULL) + { + free(qcommand); + qcommand = NULL; + goto exit; + } + if (MQTTVersion < MQTTVERSION_5) + { + if ((command->details.sub.qoss = (int *)malloc(sizeof(int) * command->details.sub.count)) == NULL) + { + free(qcommand); + qcommand = NULL; + goto exit; + } + } + else 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) + { + free(qcommand); + qcommand = NULL; + goto exit; + } + } + } + + for (i = 0; i < command->details.sub.count; ++i) + { + data_size = strlen(ptr) + 1; + + if ((command->details.sub.topics[i] = malloc(data_size)) == NULL) + { + free(qcommand); + qcommand = NULL; + goto exit; + } + strcpy(command->details.sub.topics[i], ptr); + ptr += data_size; + + if (MQTTVersion < MQTTVERSION_5) + { + command->details.sub.qoss[i] = *(int*)ptr; + ptr += sizeof(int); + } + else + { + if (command->details.sub.count == 1) + { + command->details.sub.opts = *(MQTTSubscribe_options*)ptr; + ptr += sizeof(MQTTSubscribe_options); + } + else + { + command->details.sub.optlist[i] = *(MQTTSubscribe_options*)ptr; + ptr += sizeof(MQTTSubscribe_options); + } + } + } + break; + + case UNSUBSCRIBE: + if (qcommand->not_restored == 0) + break; + command->details.unsub.count = *(int*)ptr; + ptr += sizeof(int); + + if (command->details.unsub.count > 0) + { + command->details.unsub.topics = (char **)malloc(sizeof(char *) * command->details.unsub.count); + if (command->details.unsub.topics == NULL) + { + free(qcommand); + qcommand = NULL; + goto exit; + } + } + + for (i = 0; i < command->details.unsub.count; ++i) + { + data_size = strlen(ptr) + 1; + + if ((command->details.unsub.topics[i] = malloc(data_size)) == NULL) + { + free(qcommand); + qcommand = NULL; + goto exit; + } + strcpy(command->details.unsub.topics[i], ptr); + ptr += data_size; + } + break; + + case PUBLISH: + data_size = strlen(ptr) + 1; + if (qcommand->not_restored == 0) + { + if ((command->details.pub.destinationName = malloc(data_size)) == NULL) + { + free(qcommand); + qcommand = NULL; + goto exit; + } + strcpy(command->details.pub.destinationName, ptr); + } + ptr += data_size; + + command->details.pub.payloadlen = *(int*)ptr; + ptr += sizeof(int); + + data_size = command->details.pub.payloadlen; + if (qcommand->not_restored == 0) + { + if ((command->details.pub.payload = malloc(data_size)) == NULL) + { + free(qcommand); + qcommand = NULL; + goto exit; + } + memcpy(command->details.pub.payload, ptr, data_size); + } + ptr += data_size; + + command->details.pub.qos = *(int*)ptr; + ptr += sizeof(int); + + command->details.pub.retained = *(int*)ptr; + ptr += sizeof(int); + break; + + default: + free(qcommand); + qcommand = NULL; + + } + if (qcommand != NULL && qcommand->not_restored == 0 && MQTTVersion >= MQTTVERSION_5 && + MQTTProperties_read(&command->properties, &ptr, buffer + buflen) != 1) + { + Log(LOG_ERROR, -1, "Error restoring properties from persistence"); + free(qcommand); + qcommand = NULL; + } +exit: + FUNC_EXIT; + return qcommand; +} + +struct keyloc { + int seqno; + ListElement* elem; +}; + +/** + * Inserts the specified message into the list, maintaining command sequence no order. + * @param list the list to insert the message into. + * @param content the message to add. + * @param size size of the message. + */ +static void MQTTAsync_insertInOrder(List* list, void* content, int size, struct keyloc* keyloc_array, int array_size) +{ + ListElement* insert_point = NULL; + ListElement* inserted = NULL; + int seqno = ((MQTTAsync_queuedCommand*)content)->seqno; + int low = 0; + + FUNC_ENTRY; + /*printf("\nseqno %d array_size %d\n", seqno, array_size); + + int i; + for (i = 0; i < array_size; ++i) + printf("%d ", keyloc_array[i].seqno); + printf("\n"); */ + + /* look through keyloc array to find location to insert message - binary search by sequence number */ + if (array_size > 0 && seqno > keyloc_array[array_size - 1].seqno) + { + low = array_size - 1; + } + else if (array_size > 0) + { + int high = array_size - 1; + int divide_index = array_size / 2; + + //printf("divide index %d\n", divide_index); + //int count = 0; + while (high - low > 1) + { + if (seqno < keyloc_array[divide_index].seqno) + high = divide_index; + else + low = divide_index; + + divide_index = (high - low) / 2 + low; + /*printf("high %d low %d divide_index %d\n", high, low, divide_index); + if (++count == 10) + exit(99);*/ + } + } + //printf("low %d\n", low); + + insert_point = keyloc_array[low].elem; + if (insert_point) + insert_point = insert_point->next; + + //if (insert_point) + // printf("insert after %d %d\n", keyloc_array[low].seqno, ((MQTTAsync_queuedCommand*)insert_point->content)->seqno); + inserted = ListInsert(list, content, size, insert_point); + + if (array_size > 0 && low + 1 < array_size) + { + /* make space for new entry in array after low point*/ + //printf("array_size - low %d\n", array_size - low); + memmove(&keyloc_array[low+2], &keyloc_array[low+1], (array_size - low - 1) * sizeof(struct keyloc)); + } + keyloc_array[low+1].seqno = seqno; + keyloc_array[low+1].elem = inserted; + + /*if (array_size > 50) + exit(99);*/ + + FUNC_EXIT; +} + +static int cmpkeys(const void *p1, const void *p2) +{ + int key1 = atoi(strchr(*(char * const *)p1, '-') + 1); + int key2 = atoi(strchr(*(char * const *)p2, '-') + 1); + + return (key1 == key2) ? 0 : ((key1 < key2) ? -1 : 1); +} + + +int MQTTAsync_restoreCommands(MQTTAsyncs* client) +{ + int rc = 0; + char **msgkeys; + int nkeys; + int i = 0; + Clients* c = client->c; + int commands_restored = 0; + + FUNC_ENTRY; + if (c->persistence && (rc = c->persistence->pkeys(c->phandle, &msgkeys, &nkeys)) == 0 && nkeys > 0) + { + MQTTAsync_queuedCommand* sentinel = NULL; + /* keep track of location of nkeys key locations for fast insert */ + struct keyloc* keyloc_array = malloc(sizeof(struct keyloc) * (nkeys + 1)); + + /* let's have the sequence number array sorted */ + qsort(msgkeys, (size_t)nkeys, sizeof(char*), cmpkeys); + /*for (i = 0; i < nkeys; ++i) printf("%s ", msgkeys[i]); printf("\n");*/ + if (keyloc_array == NULL) + { + rc = PAHO_MEMORY_ERROR; + goto exit; + } + sentinel = malloc(sizeof(MQTTAsync_queuedCommand)); + if (sentinel == NULL) + { + free(keyloc_array); + rc = PAHO_MEMORY_ERROR; + goto exit; + } + sentinel->seqno = -1; + keyloc_array[0].seqno = -1; + keyloc_array[0].elem = ListAppend(MQTTAsync_commands, sentinel, sizeof(MQTTAsync_queuedCommand)); + + while (rc == 0 && i < nkeys) + { + char *buffer = NULL; + int buflen; + + if (strncmp(msgkeys[i], PERSISTENCE_COMMAND_KEY, strlen(PERSISTENCE_COMMAND_KEY)) != 0 && + strncmp(msgkeys[i], PERSISTENCE_V5_COMMAND_KEY, strlen(PERSISTENCE_V5_COMMAND_KEY)) != 0) + { + ; + } + else + { + MQTTAsync_queuedCommand* cmd = NULL; + if ((rc = c->persistence->pget(c->phandle, msgkeys[i], &buffer, &buflen)) == 0 && + (c->afterRead == NULL || (rc = c->afterRead(c->afterRead_context, &buffer, &buflen)) == 0)) + { + int MQTTVersion = (strncmp(msgkeys[i], PERSISTENCE_V5_COMMAND_KEY, strlen(PERSISTENCE_V5_COMMAND_KEY)) == 0) + ? MQTTVERSION_5 : MQTTVERSION_3_1_1; + cmd = MQTTAsync_restoreCommand(buffer, buflen, MQTTVersion, NULL); + /* As the entire command is not restored on the first read to save memory, we temporarily store + * the filename of the persisted command to be used when restoreCommand is called the second time. + */ + cmd->key = malloc(strlen(msgkeys[i])+1); + strcpy(cmd->key, msgkeys[i]); + } + + if (cmd) + { + cmd->client = client; + cmd->seqno = atoi(strchr(msgkeys[i], '-')+1); /* key format is tag'-'seqno */ + MQTTAsync_insertInOrder(MQTTAsync_commands, cmd, sizeof(MQTTAsync_queuedCommand), keyloc_array, i + 1); + if (buffer) + free(buffer); + client->command_seqno = max(client->command_seqno, cmd->seqno); + commands_restored++; + if (cmd->command.type == PUBLISH) + client->noBufferedMessages++; + } + } + if (msgkeys[i]) + free(msgkeys[i]); + i++; + } + if (msgkeys != NULL) + free(msgkeys); + + /*int j; for (j = 0; j < i + 1; ++j) printf("%d ", keyloc_array[j].seqno); printf("\n"); */ + ListRemoveHead(MQTTAsync_commands); /* remove sentinel */ + /*ListElement* pos = NULL; while (ListNextElement(MQTTAsync_commands, &pos)) printf("%d ", ((MQTTAsync_queuedCommand*)(pos->content))->seqno); printf("\n");*/ + + free(keyloc_array); + } +exit: + Log(TRACE_MINIMUM, -1, "%d commands restored for client %s", commands_restored, c->clientID); + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTAsync_unpersistCommandsAndMessages(Clients* c) +{ + int rc = 0; + char **msgkeys; + int nkeys; + int i = 0; + int messages_deleted = 0; + + FUNC_ENTRY; + if (c->persistence && (rc = c->persistence->pkeys(c->phandle, &msgkeys, &nkeys)) == 0) + { + while (rc == 0 && i < nkeys) + { + if (strncmp(msgkeys[i], PERSISTENCE_COMMAND_KEY, strlen(PERSISTENCE_COMMAND_KEY)) == 0 || + strncmp(msgkeys[i], PERSISTENCE_V5_COMMAND_KEY, strlen(PERSISTENCE_V5_COMMAND_KEY)) == 0 || + strncmp(msgkeys[i], PERSISTENCE_QUEUE_KEY, strlen(PERSISTENCE_QUEUE_KEY)) == 0 || + strncmp(msgkeys[i], PERSISTENCE_V5_QUEUE_KEY, strlen(PERSISTENCE_V5_QUEUE_KEY)) == 0) + { + if ((rc = c->persistence->premove(c->phandle, msgkeys[i])) == 0) + messages_deleted++; + else + Log(LOG_ERROR, 0, "Error %d removing queued message from persistence", rc); + } + if (msgkeys[i]) + free(msgkeys[i]); + i++; + } + if (msgkeys != NULL) + free(msgkeys); + } + Log(TRACE_MINIMUM, -1, "%d queued messages deleted for client %s", messages_deleted, c->clientID); + FUNC_EXIT_RC(rc); + return rc; +} + + +static int MQTTAsync_unpersistInflightMessages(Clients* c) +{ + int rc = 0; + char **msgkeys; + int nkeys; + int i = 0; + int messages_deleted = 0; + + FUNC_ENTRY; + if (c->persistence && (rc = c->persistence->pkeys(c->phandle, &msgkeys, &nkeys)) == 0) + { + while (rc == 0 && i < nkeys) + { + if (strncmp(msgkeys[i], PERSISTENCE_PUBLISH_SENT, strlen(PERSISTENCE_PUBLISH_SENT)) == 0 || + strncmp(msgkeys[i], PERSISTENCE_V5_PUBLISH_SENT, strlen(PERSISTENCE_V5_PUBLISH_SENT)) == 0 || + strncmp(msgkeys[i], PERSISTENCE_PUBREL, strlen(PERSISTENCE_PUBREL)) == 0 || + strncmp(msgkeys[i], PERSISTENCE_V5_PUBREL, strlen(PERSISTENCE_V5_PUBREL)) == 0 || + strncmp(msgkeys[i], PERSISTENCE_PUBLISH_RECEIVED, strlen(PERSISTENCE_PUBLISH_RECEIVED)) == 0 || + strncmp(msgkeys[i], PERSISTENCE_V5_PUBLISH_RECEIVED, strlen(PERSISTENCE_V5_PUBLISH_RECEIVED)) == 0) + { + if ((rc = c->persistence->premove(c->phandle, msgkeys[i])) == 0) + messages_deleted++; + else + Log(LOG_ERROR, 0, "Error %d removing inflight message from persistence", rc); + } + if (msgkeys[i]) + free(msgkeys[i]); + i++; + } + if (msgkeys != NULL) + free(msgkeys); + } + Log(TRACE_MINIMUM, -1, "%d inflight messages deleted for client %s", messages_deleted, c->clientID); + FUNC_EXIT_RC(rc); + return rc; +} +#endif + +/** + * List callback function for comparing client handles and command types being CONNECT or DISCONNECT + * @param a first MQTTAsync_queuedCommand pointer + * @param b second MQTTAsync_queuedCommand pointer + * @return boolean indicating whether a and b are equal + */ +static int clientCompareConnectCommand(void* a, void* b) +{ + MQTTAsync_queuedCommand* cmd1 = (MQTTAsync_queuedCommand*)a; + MQTTAsync_queuedCommand* cmd2 = (MQTTAsync_queuedCommand*)b; + if (cmd1->client == cmd2->client) + { + if (cmd1->command.type == cmd2->command.type) + { + if (cmd1->command.type == CONNECT || cmd1->command.type == DISCONNECT) + { + return 1; //Item found in the list + } + } + } + return 0; //Item NOT found in the list +} + + +int MQTTAsync_addCommand(MQTTAsync_queuedCommand* command, int command_size) +{ + int rc = MQTTASYNC_SUCCESS; + + FUNC_ENTRY; + MQTTAsync_lock_mutex(mqttcommand_mutex); + /* Don't set start time if the connect command is already in process #218 */ + if ((command->command.type != CONNECT) || (command->client->c->connect_state == NOT_IN_PROGRESS)) + command->command.start_time = MQTTTime_start_clock(); + if (command->command.type == CONNECT || + (command->command.type == DISCONNECT && command->command.details.dis.internal)) + { + MQTTAsync_queuedCommand* head = NULL; + + if (MQTTAsync_commands->first) + head = (MQTTAsync_queuedCommand*)(MQTTAsync_commands->first->content); + + if (head != NULL && head->client == command->client && head->command.type == command->command.type) + MQTTAsync_freeCommand(command); /* ignore duplicate connect or disconnect command */ + else + { + ListRemoveItem(MQTTAsync_commands, command, clientCompareConnectCommand); /* remove command from the list if already there */ + ListInsert(MQTTAsync_commands, command, command_size, MQTTAsync_commands->first); /* add to the head of the list */ + } + } + else + { + ListAppend(MQTTAsync_commands, command, command_size); +#if !defined(NO_PERSISTENCE) + if (command->client->c->persistence) + { + if (command->command.type == PUBLISH && + command->client->createOptions && command->client->createOptions->struct_version >= 2 && + command->client->createOptions->persistQoS0 == 0 && command->command.details.pub.qos == 0) + ; /* don't persist QoS0 if that create option is set to 0 */ + else + { + int rc = MQTTAsync_persistCommand(command); + if (command->command.type == PUBLISH && rc == 0) + { + char key[PERSISTENCE_MAX_KEY_LENGTH + 1]; + + command->not_restored = 1; + if (command->client->c->MQTTVersion >= MQTTVERSION_5) + sprintf(key, "%s%u", PERSISTENCE_V5_COMMAND_KEY, command->seqno); + else + sprintf(key, "%s%u", PERSISTENCE_COMMAND_KEY, command->seqno); + command->key = malloc(strlen(key+1)); + strcpy(command->key, key); + + free(command->command.details.pub.payload); + command->command.details.pub.payload = NULL; + free(command->command.details.pub.destinationName); + command->command.details.pub.destinationName = NULL; + MQTTProperties_free(&command->command.properties); + } + } + } +#endif + if (command->command.type == PUBLISH) + { + /* delete oldest message if buffer is full. We wouldn't be here if delete newest was in operation */ + if (command->client->createOptions && (command->client->noBufferedMessages >= command->client->createOptions->maxBufferedMessages)) + { + MQTTAsync_queuedCommand* first_publish = NULL; + ListElement* current = NULL; + + /* Find first publish command for this client and detach it */ + while (ListNextElement(MQTTAsync_commands, ¤t)) + { + MQTTAsync_queuedCommand* cmd = (MQTTAsync_queuedCommand*)(current->content); + + if (cmd->client == command->client && cmd->command.type == PUBLISH) + { + first_publish = cmd; + break; + } + } + if (first_publish) + { + ListDetach(MQTTAsync_commands, first_publish); + + MQTTAsync_freeCommand(first_publish); + #if !defined(NO_PERSISTENCE) + if (command->client->c->persistence) + MQTTAsync_unpersistCommand(first_publish); + #endif + } + } + else + command->client->noBufferedMessages++; + } + } + MQTTAsync_unlock_mutex(mqttcommand_mutex); +#if !defined(_WIN32) && !defined(_WIN64) + rc = Thread_signal_cond(send_cond); + if (rc != 0) + Log(LOG_ERROR, 0, "Error %d from signal cond", rc); +#else + rc = Thread_post_sem(send_sem); +#endif + FUNC_EXIT_RC(rc); + return rc; +} + + +void MQTTAsync_startConnectRetry(MQTTAsyncs* m) +{ + if (m->automaticReconnect && m->shouldBeConnected) + { + m->lastConnectionFailedTime = MQTTTime_start_clock(); + if (m->retrying) + { + m->currentIntervalBase = min(m->currentIntervalBase * 2, m->maxRetryInterval); + } + else + { + m->currentIntervalBase = m->minRetryInterval; + m->retrying = 1; + } + m->currentInterval = MQTTAsync_randomJitter(m->currentIntervalBase, m->minRetryInterval, m->maxRetryInterval); + } +} + + +void MQTTAsync_checkDisconnect(MQTTAsync handle, MQTTAsync_command* command) +{ + MQTTAsyncs* m = handle; + + FUNC_ENTRY; + /* wait for all inflight message flows to finish, up to timeout */; + if (m->c->outboundMsgs->count == 0 || MQTTTime_elapsed(command->start_time) >= (ELAPSED_TIME_TYPE)command->details.dis.timeout) + { + int was_connected = m->c->connected; + MQTTAsync_closeSession(m->c, command->details.dis.reasonCode, &command->properties); + if (command->details.dis.internal) + { + if (m->cl && was_connected) + { + Log(TRACE_MIN, -1, "Calling connectionLost for client %s", m->c->clientID); + (*(m->cl))(m->clContext, NULL); + } + MQTTAsync_startConnectRetry(m); + } + else if (command->onSuccess) + { + MQTTAsync_successData data; + + memset(&data, '\0', sizeof(data)); + Log(TRACE_MIN, -1, "Calling disconnect complete for client %s", m->c->clientID); + (*(command->onSuccess))(command->context, &data); + } + else if (command->onSuccess5) + { + MQTTAsync_successData5 data = MQTTAsync_successData5_initializer; + + data.reasonCode = MQTTASYNC_SUCCESS; + Log(TRACE_MIN, -1, "Calling disconnect complete for client %s", m->c->clientID); + (*(command->onSuccess5))(command->context, &data); + } + } + FUNC_EXIT; +} + + +/** + * 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) +{ + int rc; + Thread_lock_mutex(socket_mutex); + rc = Socket_noPendingWrites(socket); + Thread_unlock_mutex(socket_mutex); + return rc; +} + +/** + * See if any pending writes have been completed, and cleanup if so. + * Cleaning up means removing any publication data that was stored because the write did + * not originally complete. + */ +static void MQTTProtocol_checkPendingWrites(void) +{ + FUNC_ENTRY; + if (state.pending_writes.count > 0) + { + ListElement* le = state.pending_writes.first; + while (le) + { + if (Socket_noPendingWrites(((pending_write*)(le->content))->socket)) + { + MQTTProtocol_removePublication(((pending_write*)(le->content))->p); + state.pending_writes.current = le; + ListRemove(&(state.pending_writes), le->content); /* does NextElement itself */ + le = state.pending_writes.current; + } + else + ListNextElement(&(state.pending_writes), &le); + } + } + FUNC_EXIT; +} + + +static void MQTTAsync_freeCommand1(MQTTAsync_queuedCommand *command) +{ + if (command->command.type == SUBSCRIBE) + { + int i; + + for (i = 0; i < command->command.details.sub.count; i++) + free(command->command.details.sub.topics[i]); + + free(command->command.details.sub.topics); + command->command.details.sub.topics = NULL; + free(command->command.details.sub.qoss); + command->command.details.sub.qoss = NULL; + } + else if (command->command.type == UNSUBSCRIBE) + { + int i; + + for (i = 0; i < command->command.details.unsub.count; i++) + free(command->command.details.unsub.topics[i]); + + free(command->command.details.unsub.topics); + command->command.details.unsub.topics = NULL; + } + else if (command->command.type == PUBLISH) + { + /* qos 1 and 2 topics are freed in the protocol code when the flows are completed */ + if (command->command.details.pub.destinationName) + free(command->command.details.pub.destinationName); + command->command.details.pub.destinationName = NULL; + if (command->command.details.pub.payload) + free(command->command.details.pub.payload); + command->command.details.pub.payload = NULL; + } + MQTTProperties_free(&command->command.properties); + if (command->not_restored && command->key) + free(command->key); +} + +static void MQTTAsync_freeCommand(MQTTAsync_queuedCommand *command) +{ + MQTTAsync_freeCommand1(command); + free(command); +} + + +void MQTTAsync_writeComplete(int socket, int rc) +{ + ListElement* found = NULL; + + FUNC_ENTRY; + /* a partial write is now complete for a socket - this will be on a publish*/ + + MQTTProtocol_checkPendingWrites(); + + /* find the client using this socket */ + if ((found = ListFindItem(MQTTAsync_handles, &socket, clientSockCompare)) != NULL) + { + MQTTAsyncs* m = (MQTTAsyncs*)(found->content); + + m->c->net.lastSent = MQTTTime_now(); + + /* see if there is a pending write flagged */ + if (m->pending_write) + { + ListElement* cur_response = NULL; + MQTTAsync_command* command = m->pending_write; + MQTTAsync_queuedCommand* com = NULL; + + cur_response = NULL; + while (ListNextElement(m->responses, &cur_response)) + { + com = (MQTTAsync_queuedCommand*)(cur_response->content); + if (&com->command == m->pending_write) + break; + } + + if (cur_response) /* we found a response */ + { + if (command->type == PUBLISH) + { + if (rc == 1 && command->details.pub.qos == 0) + { + if (command->onSuccess) + { + MQTTAsync_successData data; + + data.token = command->token; + data.alt.pub.destinationName = command->details.pub.destinationName; + data.alt.pub.message.payload = command->details.pub.payload; + data.alt.pub.message.payloadlen = command->details.pub.payloadlen; + data.alt.pub.message.qos = command->details.pub.qos; + data.alt.pub.message.retained = command->details.pub.retained; + Log(TRACE_MIN, -1, "Calling publish success for client %s", m->c->clientID); + (*(command->onSuccess))(command->context, &data); + } + else if (command->onSuccess5) + { + MQTTAsync_successData5 data = MQTTAsync_successData5_initializer; + + data.token = command->token; + data.alt.pub.destinationName = command->details.pub.destinationName; + data.alt.pub.message.payload = command->details.pub.payload; + data.alt.pub.message.payloadlen = command->details.pub.payloadlen; + data.alt.pub.message.qos = command->details.pub.qos; + data.alt.pub.message.retained = command->details.pub.retained; + data.properties = command->properties; + Log(TRACE_MIN, -1, "Calling publish success for client %s", m->c->clientID); + (*(command->onSuccess5))(command->context, &data); + } + } + else if (rc == -1) + { + if (command->onFailure) + { + MQTTAsync_failureData data; + + data.token = command->token; + data.code = rc; + data.message = NULL; + Log(TRACE_MIN, -1, "Calling publish failure for client %s", m->c->clientID); + (*(command->onFailure))(command->context, &data); + } + else if (command->onFailure5) + { + MQTTAsync_failureData5 data; + + data.token = command->token; + data.code = rc; + data.message = NULL; + data.packet_type = PUBLISH; + Log(TRACE_MIN, -1, "Calling publish failure for client %s", m->c->clientID); + (*(command->onFailure5))(command->context, &data); + } + } + else + com = NULL; /* Don't delete response we haven't acknowledged */ + /* QoS 0 payloads are freed elsewhere after a write complete, + * so we should indicate that. + */ + if (command->details.pub.qos == 0) + command->details.pub.payload = NULL; + } + if (com) + { + Log(TRACE_PROTOCOL, -1, "writeComplete: Removing response for msgid %d", com->command.token); + ListDetach(m->responses, com); + MQTTAsync_freeCommand(com); + } + } /* if cur_response */ + m->pending_write = NULL; + } /* if pending_write */ + } + FUNC_EXIT; +} + + +static int MQTTAsync_processCommand(void) +{ + int rc = 0; + MQTTAsync_queuedCommand* command = NULL; + ListElement* cur_command = NULL; + List* ignored_clients = NULL; + + FUNC_ENTRY; + MQTTAsync_lock_mutex(mqttasync_mutex); + MQTTAsync_lock_mutex(mqttcommand_mutex); + + /* only the first command in the list must be processed for any particular client, so if we skip + a command for a client, we must skip all following commands for that client. Use a list of + ignored clients to keep track + */ + ignored_clients = ListInitialize(); + + /* don't try a command until there isn't a pending write for that client, and we are not connecting */ + while (ListNextElement(MQTTAsync_commands, &cur_command)) + { + MQTTAsync_queuedCommand* cmd = (MQTTAsync_queuedCommand*)(cur_command->content); + + if (ListFind(ignored_clients, cmd->client)) + continue; + + if (cmd->command.type == CONNECT || cmd->command.type == DISCONNECT || (cmd->client->c->connected && + cmd->client->c->connect_state == NOT_IN_PROGRESS && MQTTAsync_Socket_noPendingWrites(cmd->client->c->net.socket))) + { + if ((cmd->command.type == PUBLISH || cmd->command.type == SUBSCRIBE || cmd->command.type == UNSUBSCRIBE) && + cmd->client->c->outboundMsgs->count >= MAX_MSG_ID - 1) + { + ; /* no more message ids available */ + } + else if (((cmd->command.type == PUBLISH && cmd->command.details.pub.qos > 0) || + cmd->command.type == SUBSCRIBE || cmd->command.type == UNSUBSCRIBE) && + (cmd->client->c->outboundMsgs->count >= cmd->client->c->maxInflightMessages)) + { + Log(TRACE_MIN, -1, "Blocking on server receive maximum for client %s", + cmd->client->c->clientID); /* flow control */ + } + else + { + command = cmd; + break; + } + } + ListAppend(ignored_clients, cmd->client, sizeof(cmd->client)); + } + ListFreeNoContent(ignored_clients); + if (command) + { + if (command->command.type == PUBLISH) + command->client->noBufferedMessages--; + ListDetach(MQTTAsync_commands, command); +#if !defined(NO_PERSISTENCE) + /*printf("outboundmsgs count %d max inflight %d qos %d %d %d\n", command->client->c->outboundMsgs->count, command->client->c->maxInflightMessages, + command->command.details.pub.qos, command->client->c->MQTTVersion, command->command.type);*/ + if (command->client->c->persistence) + { + if (command->not_restored) + { + char* buffer = NULL; + int buflen = 0; + + if ((rc = command->client->c->persistence->pget(command->client->c->phandle, command->key, &buffer, &buflen)) == 0 + && (command->client->c->afterRead == NULL || + (rc = command->client->c->afterRead(command->client->c->afterRead_context, &buffer, &buflen)) == 0)) + { + int MQTTVersion = (strncmp(command->key, PERSISTENCE_V5_COMMAND_KEY, strlen(PERSISTENCE_V5_COMMAND_KEY)) == 0) + ? MQTTVERSION_5 : MQTTVERSION_3_1_1; + 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); + } + MQTTAsync_unpersistCommand(command); + } +#endif + } + MQTTAsync_unlock_mutex(mqttcommand_mutex); + + if (!command) + goto exit; /* nothing to do */ + + if (command->command.type == CONNECT) + { + if (command->client->c->connect_state != NOT_IN_PROGRESS || command->client->c->connected) + rc = 0; + else + { + char* serverURI = command->client->serverURI; + + if (command->client->serverURIcount > 0) + { + if (command->command.details.conn.currentURI < command->client->serverURIcount) + { + serverURI = command->client->serverURIs[command->command.details.conn.currentURI]; + + if (strncmp(URI_TCP, serverURI, strlen(URI_TCP)) == 0) + serverURI += strlen(URI_TCP); + else if (strncmp(URI_WS, serverURI, strlen(URI_WS)) == 0) + { + serverURI += strlen(URI_WS); + command->client->websocket = 1; + } +#if defined(OPENSSL) + else if (strncmp(URI_SSL, serverURI, strlen(URI_SSL)) == 0) + { + serverURI += strlen(URI_SSL); + command->client->ssl = 1; + } + else if (strncmp(URI_WSS, serverURI, strlen(URI_WSS)) == 0) + { + serverURI += strlen(URI_WSS); + command->client->ssl = 1; + command->client->websocket = 1; + } +#endif + } + } + + if (command->client->c->MQTTVersion == MQTTVERSION_DEFAULT) + { + if (command->command.details.conn.MQTTVersion == MQTTVERSION_DEFAULT) + command->command.details.conn.MQTTVersion = MQTTVERSION_3_1_1; + else if (command->command.details.conn.MQTTVersion == MQTTVERSION_3_1_1) + command->command.details.conn.MQTTVersion = MQTTVERSION_3_1; + } + else + command->command.details.conn.MQTTVersion = command->client->c->MQTTVersion; + + Log(TRACE_PROTOCOL, -1, "Connecting to serverURI %s with MQTT version %d", serverURI, command->command.details.conn.MQTTVersion); +#if defined(OPENSSL) +#if defined(__GNUC__) && defined(__linux__) + rc = MQTTProtocol_connect(serverURI, command->client->c, command->client->ssl, command->client->websocket, + command->command.details.conn.MQTTVersion, command->client->connectProps, command->client->willProps, 100); +#else + rc = MQTTProtocol_connect(serverURI, command->client->c, command->client->ssl, command->client->websocket, + command->command.details.conn.MQTTVersion, command->client->connectProps, command->client->willProps); +#endif +#else +#if defined(__GNUC__) && defined(__linux__) + rc = MQTTProtocol_connect(serverURI, command->client->c, command->client->websocket, + command->command.details.conn.MQTTVersion, command->client->connectProps, command->client->willProps, 100); +#else + rc = MQTTProtocol_connect(serverURI, command->client->c, command->client->websocket, + command->command.details.conn.MQTTVersion, command->client->connectProps, command->client->willProps); +#endif +#endif + + if (command->client->c->connect_state == NOT_IN_PROGRESS) + rc = SOCKET_ERROR; + + /* if the TCP connect is pending, then we must call select to determine when the connect has completed, + which is indicated by the socket being ready *either* for reading *or* writing. The next couple of lines + make sure we check for writeability as well as readability, otherwise we wait around longer than we need to + in Socket_getReadySocket() */ + if (rc == EINPROGRESS) + Socket_addPendingWrite(command->client->c->net.socket); + } + } + else if (command->command.type == SUBSCRIBE) + { + List* topics = ListInitialize(); + List* qoss = ListInitialize(); + MQTTProperties* props = NULL; + MQTTSubscribe_options* subopts = NULL; + int i; + + for (i = 0; i < command->command.details.sub.count; i++) + { + ListAppend(topics, command->command.details.sub.topics[i], strlen(command->command.details.sub.topics[i])); + ListAppend(qoss, &command->command.details.sub.qoss[i], sizeof(int)); + } + if (command->client->c->MQTTVersion >= MQTTVERSION_5) + { + props = &command->command.properties; + if (command->command.details.sub.count > 1) + subopts = command->command.details.sub.optlist; + else + subopts = &command->command.details.sub.opts; + } + rc = MQTTProtocol_subscribe(command->client->c, topics, qoss, command->command.token, subopts, props); + ListFreeNoContent(topics); + ListFreeNoContent(qoss); + if (command->client->c->MQTTVersion >= MQTTVERSION_5 && command->command.details.sub.count > 1) + free(command->command.details.sub.optlist); + } + else if (command->command.type == UNSUBSCRIBE) + { + List* topics = ListInitialize(); + MQTTProperties* props = NULL; + int i; + + for (i = 0; i < command->command.details.unsub.count; i++) + ListAppend(topics, command->command.details.unsub.topics[i], strlen(command->command.details.unsub.topics[i])); + + if (command->client->c->MQTTVersion >= MQTTVERSION_5) + props = &command->command.properties; + + rc = MQTTProtocol_unsubscribe(command->client->c, topics, command->command.token, props); + ListFreeNoContent(topics); + } + else if (command->command.type == PUBLISH) + { + Messages* msg = NULL; + Publish* p = NULL; + MQTTProperties initialized = MQTTProperties_initializer; + + if ((p = malloc(sizeof(Publish))) == NULL) + { + rc = PAHO_MEMORY_ERROR; + goto exit; + } + + p->payload = command->command.details.pub.payload; + p->payloadlen = command->command.details.pub.payloadlen; + p->topic = command->command.details.pub.destinationName; + p->msgId = command->command.token; + p->MQTTVersion = command->client->c->MQTTVersion; + p->properties = initialized; + if (p->MQTTVersion >= MQTTVERSION_5) + p->properties = command->command.properties; + + rc = MQTTProtocol_startPublish(command->client->c, p, command->command.details.pub.qos, command->command.details.pub.retained, &msg); + + if (command->command.details.pub.qos == 0) + { + if (rc == TCPSOCKET_COMPLETE) + { + if (command->command.onSuccess) + { + MQTTAsync_successData data; + + data.token = command->command.token; + data.alt.pub.destinationName = command->command.details.pub.destinationName; + data.alt.pub.message.payload = command->command.details.pub.payload; + data.alt.pub.message.payloadlen = command->command.details.pub.payloadlen; + data.alt.pub.message.qos = command->command.details.pub.qos; + data.alt.pub.message.retained = command->command.details.pub.retained; + Log(TRACE_MIN, -1, "Calling publish success for client %s", command->client->c->clientID); + (*(command->command.onSuccess))(command->command.context, &data); + } + else if (command->command.onSuccess5) + { + MQTTAsync_successData5 data = MQTTAsync_successData5_initializer; + + data.token = command->command.token; + data.alt.pub.destinationName = command->command.details.pub.destinationName; + data.alt.pub.message.payload = command->command.details.pub.payload; + data.alt.pub.message.payloadlen = command->command.details.pub.payloadlen; + data.alt.pub.message.qos = command->command.details.pub.qos; + data.alt.pub.message.retained = command->command.details.pub.retained; + data.properties = command->command.properties; + Log(TRACE_MIN, -1, "Calling publish success for client %s", command->client->c->clientID); + (*(command->command.onSuccess5))(command->command.context, &data); + } + } + else + { + if (rc != SOCKET_ERROR) + command->command.details.pub.destinationName = NULL; /* this will be freed by the protocol code */ + command->client->pending_write = &command->command; + } + } + else + { + 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 */ + } + free(p); /* should this be done if the write isn't complete? */ + } + else if (command->command.type == DISCONNECT) + { + if (command->client->c->connect_state != NOT_IN_PROGRESS || command->client->c->connected != 0) + { + if (command->client->c->connect_state != NOT_IN_PROGRESS) + { + if (command->client->connect.onFailure) + { + MQTTAsync_failureData data; + + data.token = 0; + data.code = MQTTASYNC_OPERATION_INCOMPLETE; + data.message = NULL; + Log(TRACE_MIN, -1, "Calling connect failure for client %s", command->client->c->clientID); + (*(command->client->connect.onFailure))(command->client->connect.context, &data); + /* Null out callback pointers so they aren't accidentally called again */ + command->client->connect.onFailure = NULL; + command->client->connect.onSuccess = NULL; + } + else if (command->client->connect.onFailure5) + { + MQTTAsync_failureData5 data; + + data.token = 0; + data.code = MQTTASYNC_OPERATION_INCOMPLETE; + data.message = NULL; + Log(TRACE_MIN, -1, "Calling connect failure for client %s", command->client->c->clientID); + (*(command->client->connect.onFailure5))(command->client->connect.context, &data); + /* Null out callback pointers so they aren't accidentally called again */ + command->client->connect.onFailure5 = NULL; + command->client->connect.onSuccess5 = NULL; + } + } + command->client->c->connect_state = DISCONNECTING; + MQTTAsync_checkDisconnect(command->client, &command->command); + } + } + + if (command->command.type == CONNECT && rc != SOCKET_ERROR && rc != MQTTASYNC_PERSISTENCE_ERROR) + { + command->client->connect = command->command; + MQTTAsync_freeCommand(command); + } + else if (command->command.type == DISCONNECT) + { + command->client->disconnect = command->command; + MQTTAsync_freeCommand(command); + } + else if (command->command.type == PUBLISH && command->command.details.pub.qos == 0 && + rc != SOCKET_ERROR && rc != MQTTASYNC_PERSISTENCE_ERROR) + { + if (rc == TCPSOCKET_INTERRUPTED) + ListAppend(command->client->responses, command, sizeof(command)); + else + MQTTAsync_freeCommand(command); + } + else if (rc == SOCKET_ERROR || rc == MQTTASYNC_PERSISTENCE_ERROR) + { + if (command->command.type == CONNECT) + { + MQTTAsync_disconnectOptions opts = MQTTAsync_disconnectOptions_initializer; + MQTTAsync_disconnect(command->client, &opts); /* not "internal" because we don't want to call connection lost */ + command->client->shouldBeConnected = 1; /* as above call is not "internal" we need to reset this */ + } + else + MQTTAsync_disconnect_internal(command->client, 0); + + if (command->command.type == CONNECT + && MQTTAsync_checkConn(&command->command, command->client)) + { + Log(TRACE_MIN, -1, "Connect failed, more to try"); + + if (command->client->c->MQTTVersion == MQTTVERSION_DEFAULT) + { + if (command->command.details.conn.MQTTVersion == MQTTVERSION_3_1) + { + command->command.details.conn.currentURI++; + command->command.details.conn.MQTTVersion = MQTTVERSION_DEFAULT; + } + } else + command->command.details.conn.currentURI++; /* Here currentURI becomes larger than command->client->serverURIcount. This needs to be handled to avoid segmentation faults! */ + + /* put the connect command back to the head of the command queue, using the next serverURI */ + rc = MQTTAsync_addCommand(command, + sizeof(command->command.details.conn)); + } else + { + if (command->command.onFailure) + { + MQTTAsync_failureData data; + + data.token = 0; + data.code = rc; + data.message = NULL; + Log(TRACE_MIN, -1, "Calling command failure for client %s", command->client->c->clientID); + (*(command->command.onFailure))(command->command.context, &data); + } + else if (command->command.onFailure5) + { + MQTTAsync_failureData5 data = MQTTAsync_failureData5_initializer; + + data.code = rc; + Log(TRACE_MIN, -1, "Calling command failure for client %s", command->client->c->clientID); + (*(command->command.onFailure5))(command->command.context, &data); + } + if (command->command.type == CONNECT) + { + command->client->connect = command->command; + MQTTAsync_startConnectRetry(command->client); + } + MQTTAsync_freeCommand(command); /* free up the command if necessary */ + } + } + else /* put the command into a waiting for response queue for each client, indexed by msgid */ + ListAppend(command->client->responses, command, sizeof(command)); + +exit: + MQTTAsync_unlock_mutex(mqttasync_mutex); + rc = (command != NULL); + FUNC_EXIT_RC(rc); + return rc; +} + + +static void nextOrClose(MQTTAsyncs* m, int rc, char* message) +{ + int was_connected = m->c->connected; + FUNC_ENTRY; + + if (MQTTAsync_checkConn(&m->connect, m)) + { + MQTTAsync_queuedCommand* conn; + + MQTTAsync_closeOnly(m->c, MQTTREASONCODE_SUCCESS, NULL); + if (m->cl && was_connected) + { + Log(TRACE_MIN, -1, "Calling connectionLost for client %s", m->c->clientID); + (*(m->cl))(m->clContext, NULL); + } + /* put the connect command back to the head of the command queue, using the next serverURI */ + if ((conn = malloc(sizeof(MQTTAsync_queuedCommand))) == NULL) + goto exit; + memset(conn, '\0', sizeof(MQTTAsync_queuedCommand)); + conn->client = m; + conn->command = m->connect; + Log(TRACE_MIN, -1, "Connect failed, more to try"); + + if (conn->client->c->MQTTVersion == MQTTVERSION_DEFAULT) + { + if (conn->command.details.conn.MQTTVersion == MQTTVERSION_3_1) + { + conn->command.details.conn.currentURI++; + conn->command.details.conn.MQTTVersion = MQTTVERSION_DEFAULT; + } + } + else + conn->command.details.conn.currentURI++; + + MQTTAsync_addCommand(conn, sizeof(m->connect)); + } + else + { + MQTTAsync_closeSession(m->c, MQTTREASONCODE_SUCCESS, NULL); + if (m->cl && was_connected) + { + Log(TRACE_MIN, -1, "Calling connectionLost for client %s", m->c->clientID); + (*(m->cl))(m->clContext, NULL); + } + if (m->connect.onFailure) + { + MQTTAsync_failureData data; + + data.token = 0; + data.code = rc; + data.message = message; + Log(TRACE_MIN, -1, "Calling connect failure for client %s", m->c->clientID); + (*(m->connect.onFailure))(m->connect.context, &data); + /* Null out callback pointers so they aren't accidentally called again */ + m->connect.onFailure = NULL; + m->connect.onSuccess = NULL; + } + else if (m->connect.onFailure5) + { + MQTTAsync_failureData5 data = MQTTAsync_failureData5_initializer; + + data.token = 0; + data.code = rc; + data.message = message; + Log(TRACE_MIN, -1, "Calling connect failure for client %s", m->c->clientID); + (*(m->connect.onFailure5))(m->connect.context, &data); + /* Null out callback pointers so they aren't accidentally called again */ + m->connect.onFailure5 = NULL; + m->connect.onSuccess5 = NULL; + } + MQTTAsync_startConnectRetry(m); + } +exit: + FUNC_EXIT; +} + + +static void MQTTAsync_checkTimeouts(void) +{ + ListElement* current = NULL; + static START_TIME_TYPE last = START_TIME_ZERO; + START_TIME_TYPE now; + + FUNC_ENTRY; + MQTTAsync_lock_mutex(mqttasync_mutex); + now = MQTTTime_now(); + if (MQTTTime_difftime(now, last) < (DIFF_TIME_TYPE)3000) + goto exit; + last = now; + while (ListNextElement(MQTTAsync_handles, ¤t)) /* for each client */ + { + MQTTAsyncs* m = (MQTTAsyncs*)(current->content); + + /* check disconnect timeout */ + if (m->c->connect_state == DISCONNECTING) + MQTTAsync_checkDisconnect(m, &m->disconnect); + + /* check connect timeout */ + if (m->c->connect_state != NOT_IN_PROGRESS && MQTTTime_elapsed(m->connect.start_time) > (ELAPSED_TIME_TYPE)(m->connectTimeout * 1000)) + { + nextOrClose(m, MQTTASYNC_FAILURE, "TCP connect timeout"); + continue; + } + + /* There was a section here that removed timed-out responses. But if the command had completed and + * there was a response, then we may as well report it, no? + * + * In any case, that section was disabled when automatic reconnect was implemented. + */ + + if (m->automaticReconnect && m->retrying) + { + if (m->reconnectNow || MQTTTime_elapsed(m->lastConnectionFailedTime) > (ELAPSED_TIME_TYPE)(m->currentInterval * 1000)) + { + /* to reconnect put the connect command to the head of the command queue */ + MQTTAsync_queuedCommand* conn = malloc(sizeof(MQTTAsync_queuedCommand)); + if (!conn) + goto exit; + memset(conn, '\0', sizeof(MQTTAsync_queuedCommand)); + conn->client = m; + conn->command = m->connect; + /* make sure that the version attempts are restarted */ + if (m->c->MQTTVersion == MQTTVERSION_DEFAULT) + conn->command.details.conn.MQTTVersion = 0; + if (m->updateConnectOptions) + { + MQTTAsync_connectData connectData = MQTTAsync_connectData_initializer; + int callback_rc = MQTTASYNC_SUCCESS; + + connectData.username = m->c->username; + connectData.binarypwd.data = m->c->password; + connectData.binarypwd.len = m->c->passwordlen; + Log(TRACE_MIN, -1, "Calling updateConnectOptions for client %s", m->c->clientID); + callback_rc = (*(m->updateConnectOptions))(m->updateConnectOptions_context, &connectData); + + if (callback_rc == 1) + { + if (connectData.username != m->c->username) + { + if (m->c->username) + free((void*)m->c->username); + if (connectData.username) + m->c->username = MQTTStrdup(connectData.username); + else + m->c->username = NULL; + } + if (connectData.binarypwd.data != m->c->password) + { + if (m->c->password) + free((void*)m->c->password); + if (connectData.binarypwd.data) + { + m->c->passwordlen = connectData.binarypwd.len; + if ((m->c->password = malloc(m->c->passwordlen))) + memcpy((void*)m->c->password, connectData.binarypwd.data, m->c->passwordlen); + } + else + { + m->c->password = NULL; + m->c->passwordlen = 0; + } + } + } + } + Log(TRACE_MIN, -1, "Automatically attempting to reconnect"); + MQTTAsync_addCommand(conn, sizeof(m->connect)); + m->reconnectNow = 0; + } + } + } +exit: + MQTTAsync_unlock_mutex(mqttasync_mutex); + FUNC_EXIT; +} + + +thread_return_type WINAPI MQTTAsync_sendThread(void* n) +{ + FUNC_ENTRY; + MQTTAsync_lock_mutex(mqttasync_mutex); + sendThread_state = RUNNING; + sendThread_id = Thread_getid(); + MQTTAsync_unlock_mutex(mqttasync_mutex); + while (!MQTTAsync_tostop) + { + int rc; + + while (MQTTAsync_commands->count > 0) + { + if (MQTTAsync_processCommand() == 0) + break; /* no commands were processed, so go into a wait */ + } +#if !defined(_WIN32) && !defined(_WIN64) + if ((rc = Thread_wait_cond(send_cond, 1)) != 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) + Log(LOG_ERROR, -1, "Error %d waiting for semaphore", rc); +#endif + + MQTTAsync_checkTimeouts(); + } + sendThread_state = STOPPING; + MQTTAsync_lock_mutex(mqttasync_mutex); + sendThread_state = STOPPED; + sendThread_id = 0; + MQTTAsync_unlock_mutex(mqttasync_mutex); + FUNC_EXIT; +#if defined(_WIN32) || defined(_WIN64) + ExitThread(0); +#endif + return 0; +} + + +void MQTTAsync_emptyMessageQueue(Clients* client) +{ + FUNC_ENTRY; + /* empty message queue */ + if (client->messageQueue->count > 0) + { + ListElement* current = NULL; + while (ListNextElement(client->messageQueue, ¤t)) + { + qEntry* qe = (qEntry*)(current->content); + free(qe->topicName); + free(qe->msg->payload); + free(qe->msg); + } + ListEmpty(client->messageQueue); + } + FUNC_EXIT; +} + + +void MQTTAsync_freeResponses(MQTTAsyncs* m) +{ + int count = 0; + + FUNC_ENTRY; + if (m->responses) + { + ListElement* cur_response = NULL; + + while (ListNextElement(m->responses, &cur_response)) + { + MQTTAsync_queuedCommand* command = (MQTTAsync_queuedCommand*)(cur_response->content); + + if (command->command.onFailure) + { + MQTTAsync_failureData data; + + data.token = command->command.token; + data.code = MQTTASYNC_OPERATION_INCOMPLETE; /* interrupted return code */ + data.message = NULL; + + Log(TRACE_MIN, -1, "Calling %s failure for client %s", + MQTTPacket_name(command->command.type), m->c->clientID); + (*(command->command.onFailure))(command->command.context, &data); + } + else if (command->command.onFailure5) + { + MQTTAsync_failureData5 data = MQTTAsync_failureData5_initializer; + + data.token = command->command.token; + data.code = MQTTASYNC_OPERATION_INCOMPLETE; /* interrupted return code */ + data.message = NULL; + + Log(TRACE_MIN, -1, "Calling %s failure for client %s", + MQTTPacket_name(command->command.type), m->c->clientID); + (*(command->command.onFailure5))(command->command.context, &data); + } + + MQTTAsync_freeCommand1(command); + count++; + } + ListEmpty(m->responses); + } + Log(TRACE_MINIMUM, -1, "%d responses removed for client %s", count, m->c->clientID); + FUNC_EXIT; +} + + +void MQTTAsync_freeCommands(MQTTAsyncs* m) +{ + int count = 0; + ListElement* current = NULL; + ListElement *next = NULL; + + FUNC_ENTRY; + /* remove commands in the command queue relating to this client */ + current = ListNextElement(MQTTAsync_commands, &next); + ListNextElement(MQTTAsync_commands, &next); + while (current) + { + MQTTAsync_queuedCommand* command = (MQTTAsync_queuedCommand*)(current->content); + + if (command->client == m) + { + ListDetach(MQTTAsync_commands, command); + + if (command->command.onFailure) + { + MQTTAsync_failureData data; + + data.token = command->command.token; + data.code = MQTTASYNC_OPERATION_INCOMPLETE; /* interrupted return code */ + data.message = NULL; + + Log(TRACE_MIN, -1, "Calling %s failure for client %s", + MQTTPacket_name(command->command.type), m->c->clientID); + (*(command->command.onFailure))(command->command.context, &data); + } + else if (command->command.onFailure5) + { + MQTTAsync_failureData5 data = MQTTAsync_failureData5_initializer; + + data.token = command->command.token; + data.code = MQTTASYNC_OPERATION_INCOMPLETE; /* interrupted return code */ + data.message = NULL; + + Log(TRACE_MIN, -1, "Calling %s failure for client %s", + MQTTPacket_name(command->command.type), m->c->clientID); + (*(command->command.onFailure5))(command->command.context, &data); + } + + MQTTAsync_freeCommand(command); + count++; + } + current = next; + ListNextElement(MQTTAsync_commands, &next); + } + Log(TRACE_MINIMUM, -1, "%d commands removed for client %s", count, m->c->clientID); + FUNC_EXIT; +} + + +static int MQTTAsync_completeConnection(MQTTAsyncs* m, Connack* connack) +{ + int rc = MQTTASYNC_FAILURE; + + FUNC_ENTRY; + if (m->c->connect_state == WAIT_FOR_CONNACK) /* MQTT connect sent - wait for CONNACK */ + { + Log(LOG_PROTOCOL, 1, NULL, m->c->net.socket, m->c->clientID, connack->rc); + if ((rc = connack->rc) == MQTTASYNC_SUCCESS) + { + m->retrying = 0; + m->c->connected = 1; + m->c->good = 1; + m->c->connect_state = NOT_IN_PROGRESS; + if (m->c->cleansession || m->c->cleanstart) + rc = MQTTAsync_cleanSession(m->c); + else if (m->c->MQTTVersion >= MQTTVERSION_3_1_1 && connack->flags.bits.sessionPresent == 0) + { + Log(LOG_PROTOCOL, -1, "Cleaning session state on connect because sessionPresent is 0"); + rc = MQTTAsync_cleanSession(m->c); + } + if (m->c->outboundMsgs->count > 0) + { + ListElement* outcurrent = NULL; + START_TIME_TYPE zero = START_TIME_ZERO; + + while (ListNextElement(m->c->outboundMsgs, &outcurrent)) + { + Messages* messages = (Messages*)(outcurrent->content); + memset(&messages->lastTouch, '\0', sizeof(messages->lastTouch)); + } + MQTTProtocol_retry(zero, 1, 1); + if (m->c->connected != 1) + rc = MQTTASYNC_DISCONNECTED; + } + } + m->pack = NULL; +#if !defined(_WIN32) && !defined(_WIN64) + Thread_signal_cond(send_cond); +#else + Thread_post_sem(send_sem); +#endif + } + FUNC_EXIT_RC(rc); + return rc; +} + + +/* This is the thread function that handles the calling of callback functions if set */ +thread_return_type WINAPI MQTTAsync_receiveThread(void* n) +{ + long timeout = 10L; /* first time in we have a small timeout. Gets things started more quickly */ + + FUNC_ENTRY; + MQTTAsync_lock_mutex(mqttasync_mutex); + receiveThread_state = RUNNING; + receiveThread_id = Thread_getid(); + while (!MQTTAsync_tostop) + { + int rc = SOCKET_ERROR; + int sock = -1; + MQTTAsyncs* m = NULL; + MQTTPacket* pack = NULL; + + MQTTAsync_unlock_mutex(mqttasync_mutex); + pack = MQTTAsync_cycle(&sock, timeout, &rc); + MQTTAsync_lock_mutex(mqttasync_mutex); + if (MQTTAsync_tostop) + break; + timeout = 1000L; + + if (sock == 0) + continue; + /* find client corresponding to socket */ + if (ListFindItem(MQTTAsync_handles, &sock, clientSockCompare) == NULL) + { + Log(TRACE_MINIMUM, -1, "Could not find client corresponding to socket %d", sock); + /* Socket_close(sock); - removing socket in this case is not necessary (Bug 442400) */ + continue; + } + m = (MQTTAsyncs*)(MQTTAsync_handles->current->content); + if (m == NULL) + { + Log(LOG_ERROR, -1, "Client structure was NULL for socket %d - removing socket", sock); + Socket_close(sock); + continue; + } + if (rc == SOCKET_ERROR) + { + Log(TRACE_MINIMUM, -1, "Error from MQTTAsync_cycle() - removing socket %d", sock); + nextOrClose(m, rc, "socket error"); + } + else + { + if (m->c->messageQueue->count > 0 && m->ma) + { + qEntry* qe = (qEntry*)(m->c->messageQueue->first->content); + int topicLen = qe->topicLen; + + if (strlen(qe->topicName) == topicLen) + topicLen = 0; + + if (MQTTAsync_deliverMessage(m, qe->topicName, topicLen, qe->msg)) + { +#if !defined(NO_PERSISTENCE) + if (m->c->persistence) + MQTTPersistence_unpersistQueueEntry(m->c, (MQTTPersistence_qEntry*)qe); +#endif + ListRemove(m->c->messageQueue, qe); /* qe is freed here */ + } + else + Log(TRACE_MIN, -1, "False returned from messageArrived for client %s, message remains on queue", + m->c->clientID); + } + if (pack) + { + if (pack->header.bits.type == CONNACK) + { + Connack* connack = (Connack*)pack; + int sessionPresent = connack->flags.bits.sessionPresent; + + rc = MQTTAsync_completeConnection(m, connack); + if (rc == MQTTASYNC_SUCCESS) + { + int onSuccess = 0; + if ((m->serverURIcount > 0) + && (m->connect.details.conn.currentURI < m->serverURIcount)) + { + Log(TRACE_MIN, -1, "Connect succeeded to %s", + m->serverURIs[m->connect.details.conn.currentURI]); + } + onSuccess = (m->connect.onSuccess != NULL || + m->connect.onSuccess5 != NULL); /* save setting of onSuccess callback */ + if (m->connect.onSuccess) + { + MQTTAsync_successData data; + memset(&data, '\0', sizeof(data)); + Log(TRACE_MIN, -1, "Calling connect success for client %s", m->c->clientID); + if ((m->serverURIcount > 0) + && (m->connect.details.conn.currentURI < m->serverURIcount)) + data.alt.connect.serverURI = m->serverURIs[m->connect.details.conn.currentURI]; + else + data.alt.connect.serverURI = m->serverURI; + data.alt.connect.MQTTVersion = m->connect.details.conn.MQTTVersion; + data.alt.connect.sessionPresent = sessionPresent; + (*(m->connect.onSuccess))(m->connect.context, &data); + /* Null out callback pointers so they aren't accidentally called again */ + m->connect.onSuccess = NULL; + m->connect.onFailure = NULL; + } + else if (m->connect.onSuccess5) + { + MQTTAsync_successData5 data = MQTTAsync_successData5_initializer; + Log(TRACE_MIN, -1, "Calling connect success for client %s", m->c->clientID); + if (m->serverURIcount > 0) + data.alt.connect.serverURI = m->serverURIs[m->connect.details.conn.currentURI]; + else + data.alt.connect.serverURI = m->serverURI; + data.alt.connect.MQTTVersion = m->connect.details.conn.MQTTVersion; + data.alt.connect.sessionPresent = sessionPresent; + data.properties = connack->properties; + data.reasonCode = connack->rc; + (*(m->connect.onSuccess5))(m->connect.context, &data); + /* Null out callback pointers so they aren't accidentally called again */ + m->connect.onSuccess5 = NULL; + m->connect.onFailure5 = NULL; + } + if (m->connected) + { + char* reason = (onSuccess) ? "connect onSuccess called" : "automatic reconnect"; + Log(TRACE_MIN, -1, "Calling connected for client %s", m->c->clientID); + (*(m->connected))(m->connected_context, reason); + } + if (m->c->MQTTVersion >= MQTTVERSION_5) + { + if (MQTTProperties_hasProperty(&connack->properties, MQTTPROPERTY_CODE_RECEIVE_MAXIMUM)) + { + int recv_max = MQTTProperties_getNumericValue(&connack->properties, MQTTPROPERTY_CODE_RECEIVE_MAXIMUM); + if (m->c->maxInflightMessages > recv_max) + m->c->maxInflightMessages = recv_max; + } + } + } + else + { + nextOrClose(m, rc, "CONNACK return code"); + } + MQTTPacket_freeConnack(connack); + } + else if (pack->header.bits.type == SUBACK) + { + ListElement* current = NULL; + + /* use the msgid to find the callback to be called */ + while (ListNextElement(m->responses, ¤t)) + { + MQTTAsync_queuedCommand* command = (MQTTAsync_queuedCommand*)(current->content); + if (command->command.token == ((Suback*)pack)->msgId) + { + Suback* sub = (Suback*)pack; + if (!ListDetach(m->responses, command)) /* remove the response from the list */ + Log(LOG_ERROR, -1, "Subscribe command not removed from command list"); + + /* Call the failure callback if there is one subscribe in the MQTT packet and + * the return code is 0x80 (failure). If the MQTT packet contains >1 subscription + * request, then we call onSuccess with the list of returned QoSs, which inelegantly, + * could include some failures, or worse, the whole list could have failed. + */ + if (m->c->MQTTVersion >= MQTTVERSION_5) + { + if (sub->qoss->count == 1 && *(int*)(sub->qoss->first->content) >= MQTTREASONCODE_UNSPECIFIED_ERROR) + { + if (command->command.onFailure5) + { + MQTTAsync_failureData5 data = MQTTAsync_failureData5_initializer; + + data.token = command->command.token; + data.reasonCode = *(int*)(sub->qoss->first->content); + data.message = NULL; + data.properties = sub->properties; + Log(TRACE_MIN, -1, "Calling subscribe failure for client %s", m->c->clientID); + (*(command->command.onFailure5))(command->command.context, &data); + } + } + else if (command->command.onSuccess5) + { + MQTTAsync_successData5 data; + enum MQTTReasonCodes* array = NULL; + + data.reasonCode = *(int*)(sub->qoss->first->content); + data.alt.sub.reasonCodeCount = sub->qoss->count; + if (sub->qoss->count > 1) + { + ListElement* cur_qos = NULL; + enum MQTTReasonCodes* element = array = data.alt.sub.reasonCodes = malloc(sub->qoss->count * sizeof(enum MQTTReasonCodes)); + if (array) + while (ListNextElement(sub->qoss, &cur_qos)) + *element++ = *(int*)(cur_qos->content); + } + data.token = command->command.token; + data.properties = sub->properties; + Log(TRACE_MIN, -1, "Calling subscribe success for client %s", m->c->clientID); + (*(command->command.onSuccess5))(command->command.context, &data); + if (array) + free(array); + } + } + else if (sub->qoss->count == 1 && *(int*)(sub->qoss->first->content) == MQTT_BAD_SUBSCRIBE) + { + if (command->command.onFailure) + { + MQTTAsync_failureData data; + + data.token = command->command.token; + data.code = *(int*)(sub->qoss->first->content); + data.message = NULL; + Log(TRACE_MIN, -1, "Calling subscribe failure for client %s", m->c->clientID); + (*(command->command.onFailure))(command->command.context, &data); + } + } + else if (command->command.onSuccess) + { + MQTTAsync_successData data; + int* array = NULL; + + if (sub->qoss->count == 1) + data.alt.qos = *(int*)(sub->qoss->first->content); + else if (sub->qoss->count > 1) + { + ListElement* cur_qos = NULL; + int* element = array = data.alt.qosList = malloc(sub->qoss->count * sizeof(int)); + if (array) + while (ListNextElement(sub->qoss, &cur_qos)) + *element++ = *(int*)(cur_qos->content); + } + data.token = command->command.token; + Log(TRACE_MIN, -1, "Calling subscribe success for client %s", m->c->clientID); + (*(command->command.onSuccess))(command->command.context, &data); + if (array) + free(array); + } + MQTTAsync_freeCommand(command); + break; + } + } + rc = MQTTProtocol_handleSubacks(pack, m->c->net.socket); + } + else if (pack->header.bits.type == UNSUBACK) + { + ListElement* current = NULL; + Unsuback* unsub = (Unsuback*)pack; + + /* use the msgid to find the callback to be called */ + while (ListNextElement(m->responses, ¤t)) + { + MQTTAsync_queuedCommand* command = (MQTTAsync_queuedCommand*)(current->content); + if (command->command.token == ((Unsuback*)pack)->msgId) + { + if (!ListDetach(m->responses, command)) /* remove the response from the list */ + Log(LOG_ERROR, -1, "Unsubscribe command not removed from command list"); + if (command->command.onSuccess || command->command.onSuccess5) + { + Log(TRACE_MIN, -1, "Calling unsubscribe success for client %s", m->c->clientID); + if (command->command.onSuccess) + { + MQTTAsync_successData data; + + memset(&data, '\0', sizeof(data)); + data.token = command->command.token; + (*(command->command.onSuccess))(command->command.context, &data); + } + else + { + MQTTAsync_successData5 data = MQTTAsync_successData5_initializer; + enum MQTTReasonCodes* array = NULL; + + data.reasonCode = *(enum MQTTReasonCodes*)(unsub->reasonCodes->first->content); + data.alt.unsub.reasonCodeCount = unsub->reasonCodes->count; + if (unsub->reasonCodes->count > 1) + { + ListElement* cur_rc = NULL; + enum MQTTReasonCodes* element = array = data.alt.unsub.reasonCodes = malloc(unsub->reasonCodes->count * sizeof(enum MQTTReasonCodes)); + if (array) + while (ListNextElement(unsub->reasonCodes, &cur_rc)) + *element++ = *(enum MQTTReasonCodes*)(cur_rc->content); + } + data.token = command->command.token; + data.properties = unsub->properties; + Log(TRACE_MIN, -1, "Calling unsubscribe success for client %s", m->c->clientID); + (*(command->command.onSuccess5))(command->command.context, &data); + if (array) + free(array); + } + } + MQTTAsync_freeCommand(command); + break; + } + } + rc = MQTTProtocol_handleUnsubacks(pack, m->c->net.socket); + } + else if (pack->header.bits.type == DISCONNECT) + { + Ack* disc = (Ack*)pack; + + 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); + } + } + } + } + receiveThread_state = STOPPED; + receiveThread_id = 0; + MQTTAsync_unlock_mutex(mqttasync_mutex); +#if !defined(_WIN32) && !defined(_WIN64) + if (sendThread_state != STOPPED) + Thread_signal_cond(send_cond); +#else + if (sendThread_state != STOPPED) + Thread_post_sem(send_sem); +#endif + FUNC_EXIT; +#if defined(_WIN32) || defined(_WIN64) + ExitThread(0); +#endif + return 0; +} + + +static void MQTTAsync_stop(void) +{ +#if !defined(NOSTACKTRACE) + int rc = 0; +#endif + + FUNC_ENTRY; + if (sendThread_state != STOPPED || receiveThread_state != STOPPED) + { + int conn_count = 0; + ListElement* current = NULL; + + if (MQTTAsync_handles != NULL) + { + /* find out how many handles are still connected */ + while (ListNextElement(MQTTAsync_handles, ¤t)) + { + if (((MQTTAsyncs*)(current->content))->c->connect_state > NOT_IN_PROGRESS || + ((MQTTAsyncs*)(current->content))->c->connected) + ++conn_count; + } + } + Log(TRACE_MIN, -1, "Conn_count is %d", conn_count); + /* stop the background thread, if we are the last one to be using it */ + if (conn_count == 0) + { + int count = 0; + MQTTAsync_tostop = 1; + while ((sendThread_state != STOPPED || receiveThread_state != STOPPED) && ++count < 100) + { + MQTTAsync_unlock_mutex(mqttasync_mutex); + Log(TRACE_MIN, -1, "sleeping"); + MQTTAsync_sleep(100L); + MQTTAsync_lock_mutex(mqttasync_mutex); + } +#if !defined(NOSTACKTRACE) + rc = 1; +#endif + MQTTAsync_tostop = 0; + } + } + FUNC_EXIT_RC(rc); +} + + + + + +static void MQTTAsync_closeOnly(Clients* client, enum MQTTReasonCodes reasonCode, MQTTProperties* props) +{ + FUNC_ENTRY; + client->good = 0; + client->ping_outstanding = 0; + if (client->net.socket > 0) + { + MQTTProtocol_checkPendingWrites(); + if (client->connected && Socket_noPendingWrites(client->net.socket)) + MQTTPacket_send_disconnect(client, reasonCode, props); + Thread_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 */ + client->session = NULL; /* show the session has been freed */ + SSLSocket_close(&client->net); +#endif + Socket_close(client->net.socket); + client->net.socket = 0; +#if defined(OPENSSL) + client->net.ssl = NULL; +#endif + Thread_unlock_mutex(socket_mutex); + } + client->connected = 0; + client->connect_state = NOT_IN_PROGRESS; + FUNC_EXIT; +} + + +void MQTTAsync_closeSession(Clients* client, enum MQTTReasonCodes reasonCode, MQTTProperties* props) +{ + FUNC_ENTRY; + MQTTAsync_closeOnly(client, reasonCode, props); + + if (client->cleansession || + (client->MQTTVersion >= MQTTVERSION_5 && client->sessionExpiry == 0)) + MQTTAsync_cleanSession(client); + + FUNC_EXIT; +} + + +/** + * List callback function for comparing clients by client structure + * @param a Async structure + * @param b Client structure + * @return boolean indicating whether a and b are equal + */ +static int clientStructCompare(void* a, void* b) +{ + MQTTAsyncs* m = (MQTTAsyncs*)a; + return m->c == (Clients*)b; +} + + +/** + * Clean the MQTT session data. This includes the MQTT inflight messages, because + * that is part of the MQTT state that will be cleared by the MQTT broker too. + * However, queued up messages, outgoing or incoming, need (should?) not be cleared + * as they are outside the scope of the MQTT session. + */ +static int MQTTAsync_cleanSession(Clients* client) +{ + int rc = 0; + ListElement* found = NULL; + + FUNC_ENTRY; +#if !defined(NO_PERSISTENCE) + rc = MQTTAsync_unpersistInflightMessages(client); +#endif + MQTTProtocol_emptyMessageList(client->inboundMsgs); + MQTTProtocol_emptyMessageList(client->outboundMsgs); + client->msgID = 0; + if ((found = ListFindItem(MQTTAsync_handles, client, clientStructCompare)) != NULL) + { + MQTTAsyncs* m = (MQTTAsyncs*)(found->content); + MQTTAsync_freeResponses(m); + } + else + Log(LOG_ERROR, -1, "cleanSession: did not find client structure in handles list"); + FUNC_EXIT_RC(rc); + return rc; +} + + +/* +* Deliver a message to the messageArrived callback +* @param m a client structure +* @param topicName the name of the topic on which the message is being delivered +* @param topicLen the length of the topic name string +* @param mm the message to be delivered +* @return boolean 1 means message has been delivered, 0 that it has not +*/ +static int MQTTAsync_deliverMessage(MQTTAsyncs* m, char* topicName, size_t topicLen, MQTTAsync_message* mm) +{ + int rc; + + Log(TRACE_MIN, -1, "Calling messageArrived for client %s, queue depth %d", + m->c->clientID, m->c->messageQueue->count); + rc = (*(m->ma))(m->maContext, topicName, (int)topicLen, mm); + /* if 0 (false) is returned by the callback then it failed, so we don't remove the message from + * the queue, and it will be retried later. If 1 is returned then the message data may have been freed, + * so we must be careful how we use it. + */ + return rc; +} + + +void Protocol_processPublication(Publish* publish, Clients* client, int allocatePayload) +{ + MQTTAsync_message* mm = NULL; + MQTTAsync_message initialized = MQTTAsync_message_initializer; + int rc = 0; + + FUNC_ENTRY; + if ((mm = malloc(sizeof(MQTTAsync_message))) == NULL) + goto exit; + memcpy(mm, &initialized, sizeof(MQTTAsync_message)); + + if (allocatePayload) + { + if ((mm->payload = malloc(publish->payloadlen)) == NULL) + { + free(mm); + goto exit; + } + memcpy(mm->payload, publish->payload, publish->payloadlen); + } else + mm->payload = publish->payload; + mm->payloadlen = publish->payloadlen; + mm->qos = publish->header.bits.qos; + mm->retained = publish->header.bits.retain; + if (publish->header.bits.qos == 2) + mm->dup = 0; /* ensure that a QoS2 message is not passed to the application with dup = 1 */ + else + mm->dup = publish->header.bits.dup; + mm->msgid = publish->msgId; + + if (publish->MQTTVersion >= MQTTVERSION_5) + mm->properties = MQTTProperties_copy(&publish->properties); + + if (client->messageQueue->count == 0 && client->connected) + { + ListElement* found = NULL; + + if ((found = ListFindItem(MQTTAsync_handles, client, clientStructCompare)) == NULL) + Log(LOG_ERROR, -1, "processPublication: did not find client structure in handles list"); + else + { + MQTTAsyncs* m = (MQTTAsyncs*)(found->content); + + if (m->ma) + rc = MQTTAsync_deliverMessage(m, publish->topic, publish->topiclen, mm); + } + } + + if (rc == 0) /* if message was not delivered, queue it up */ + { + qEntry* qe = malloc(sizeof(qEntry)); + + if (!qe) + goto exit; + qe->msg = mm; + qe->topicName = publish->topic; + qe->topicLen = publish->topiclen; + ListAppend(client->messageQueue, qe, sizeof(qe) + sizeof(mm) + mm->payloadlen + strlen(qe->topicName)+1); +#if !defined(NO_PERSISTENCE) + if (client->persistence) + MQTTPersistence_persistQueueEntry(client, (MQTTPersistence_qEntry*)qe); +#endif + } +exit: + publish->topic = NULL; + FUNC_EXIT; +} + + +static int retryLoopInterval = 5; + +void setRetryLoopInterval(int keepalive) +{ + int proposed = keepalive / 10; + + if (proposed < 1) + proposed = 1; + else if (proposed > 5) + proposed = 5; + if (proposed < retryLoopInterval) + retryLoopInterval = proposed; +} + + +int MQTTAsync_disconnect1(MQTTAsync handle, const MQTTAsync_disconnectOptions* options, int internal) +{ + MQTTAsyncs* m = handle; + int rc = MQTTASYNC_SUCCESS; + MQTTAsync_queuedCommand* dis; + + FUNC_ENTRY; + if (m == NULL || m->c == NULL) + { + rc = MQTTASYNC_FAILURE; + goto exit; + } + if (!internal) + m->shouldBeConnected = 0; + if (m->c->connected == 0) + { + rc = MQTTASYNC_DISCONNECTED; + goto exit; + } + + /* Add disconnect request to operation queue */ + if ((dis = malloc(sizeof(MQTTAsync_queuedCommand))) == NULL) + { + rc = PAHO_MEMORY_ERROR; + goto exit; + } + memset(dis, '\0', sizeof(MQTTAsync_queuedCommand)); + dis->client = m; + if (options) + { + dis->command.onSuccess = options->onSuccess; + dis->command.onFailure = options->onFailure; + dis->command.onSuccess5 = options->onSuccess5; + dis->command.onFailure5 = options->onFailure5; + dis->command.context = options->context; + dis->command.details.dis.timeout = options->timeout; + if (m->c->MQTTVersion >= MQTTVERSION_5 && options->struct_version >= 1) + { + dis->command.properties = MQTTProperties_copy(&options->properties); + dis->command.details.dis.reasonCode = options->reasonCode; + } + } + dis->command.type = DISCONNECT; + dis->command.details.dis.internal = internal; + rc = MQTTAsync_addCommand(dis, sizeof(dis)); + +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +static int MQTTAsync_disconnect_internal(MQTTAsync handle, int timeout) +{ + MQTTAsync_disconnectOptions options = MQTTAsync_disconnectOptions_initializer; + + options.timeout = timeout; + return MQTTAsync_disconnect1(handle, &options, 1); +} + + +void MQTTProtocol_closeSession(Clients* c, int sendwill) +{ + MQTTAsync_disconnect_internal((MQTTAsync)c->context, 0); +} + + +static int cmdMessageIDCompare(void* a, void* b) +{ + MQTTAsync_queuedCommand* cmd = (MQTTAsync_queuedCommand*)a; + return cmd->command.token == *(int*)b; +} + + +/** + * Assign a new message id for a client. Make sure it isn't already being used and does + * not exceed the maximum. + * @param m a client structure + * @return the next message id to use, or 0 if none available + */ +int MQTTAsync_assignMsgId(MQTTAsyncs* m) +{ + int start_msgid; + int msgid; + thread_id_type thread_id = 0; + int locked = 0; + + /* need to check: commands list and response list for a client */ + FUNC_ENTRY; + /* We might be called in a callback. In which case, this mutex will be already locked. */ + thread_id = Thread_getid(); + if (thread_id != sendThread_id && thread_id != receiveThread_id) + { + MQTTAsync_lock_mutex(mqttasync_mutex); + locked = 1; + } + + /* Fetch last message ID in locked state */ + start_msgid = m->c->msgID; + msgid = start_msgid; + + MQTTAsync_lock_mutex(mqttcommand_mutex); + msgid = (msgid == MAX_MSG_ID) ? 1 : msgid + 1; + while (ListFindItem(MQTTAsync_commands, &msgid, cmdMessageIDCompare) || + ListFindItem(m->c->outboundMsgs, &msgid, messageIDCompare) || + ListFindItem(m->responses, &msgid, cmdMessageIDCompare)) + { + msgid = (msgid == MAX_MSG_ID) ? 1 : msgid + 1; + if (msgid == start_msgid) + { /* we've tried them all - none free */ + msgid = 0; + break; + } + } + MQTTAsync_unlock_mutex(mqttcommand_mutex); + if (msgid != 0) + m->c->msgID = msgid; + if (locked) + MQTTAsync_unlock_mutex(mqttasync_mutex); + FUNC_EXIT_RC(msgid); + return msgid; +} + + +static void MQTTAsync_retry(void) +{ + static START_TIME_TYPE last = START_TIME_ZERO; + START_TIME_TYPE now; + + FUNC_ENTRY; + now = MQTTTime_now(); + if (MQTTTime_difftime(now, last) > (DIFF_TIME_TYPE)(retryLoopInterval * 1000)) + { + last = MQTTTime_now(); + MQTTProtocol_keepalive(now); + MQTTProtocol_retry(now, 1, 0); + } + else + MQTTProtocol_retry(now, 0, 0); + FUNC_EXIT; +} + + +static int MQTTAsync_connecting(MQTTAsyncs* m) +{ + int rc = -1; + char* serverURI = m->serverURI; +#if defined(OPENSSL) + int default_port = MQTT_DEFAULT_PORT; +#endif + + FUNC_ENTRY; + if (m->serverURIcount > 0) + { + serverURI = m->serverURIs[m->connect.details.conn.currentURI]; + + /* skip URI scheme */ + if (strncmp(URI_TCP, serverURI, strlen(URI_TCP)) == 0) + serverURI += strlen(URI_TCP); + else if (strncmp(URI_WS, serverURI, strlen(URI_WS)) == 0) + { + serverURI += strlen(URI_WS); +#if defined(OPENSSL) + default_port = WS_DEFAULT_PORT; +#endif + } +#if defined(OPENSSL) + else if (strncmp(URI_SSL, serverURI, strlen(URI_SSL)) == 0) + { + serverURI += strlen(URI_SSL); + default_port = SECURE_MQTT_DEFAULT_PORT; + } + else if (strncmp(URI_WSS, serverURI, strlen(URI_WSS)) == 0) + { + serverURI += strlen(URI_WSS); + default_port = WS_DEFAULT_PORT; + } +#endif + } + + if (m->c->connect_state == TCP_IN_PROGRESS) /* TCP connect started - check for completion */ + { + int error; + socklen_t len = sizeof(error); + + if ((rc = getsockopt(m->c->net.socket, SOL_SOCKET, SO_ERROR, (char*)&error, &len)) == 0) + rc = error; + + if (rc != 0) + goto exit; + + Socket_clearPendingWrite(m->c->net.socket); + +#if defined(OPENSSL) + if (m->ssl) + { + int port; + size_t hostname_len; + int setSocketForSSLrc = 0; + + if (m->websocket && 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 ) + goto exit; + } + + hostname_len = MQTTProtocol_addressPort(serverURI, &port, NULL, default_port); + setSocketForSSLrc = SSLSocket_setSocketForSSL(&m->c->net, m->c->sslopts, + serverURI, hostname_len); + + if (setSocketForSSLrc != MQTTASYNC_SUCCESS) + { + if (m->c->session != NULL) + if ((rc = SSL_set_session(m->c->net.ssl, m->c->session)) != 1) + Log(TRACE_MIN, -1, "Failed to set SSL session with stored data, non critical"); + rc = m->c->sslopts->struct_version >= 3 ? + SSLSocket_connect(m->c->net.ssl, m->c->net.socket, serverURI, + m->c->sslopts->verify, m->c->sslopts->ssl_error_cb, m->c->sslopts->ssl_error_context) : + SSLSocket_connect(m->c->net.ssl, m->c->net.socket, serverURI, + m->c->sslopts->verify, NULL, NULL); + if (rc == TCPSOCKET_INTERRUPTED) + { + rc = MQTTCLIENT_SUCCESS; /* the connect is still in progress */ + m->c->connect_state = SSL_IN_PROGRESS; + } + else if (rc == SSL_FATAL) + { + rc = SOCKET_ERROR; + goto exit; + } + else if (rc == 1) + { + if ( m->websocket ) + { + m->c->connect_state = WEBSOCKET_IN_PROGRESS; + if ((rc = WebSocket_connect(&m->c->net, serverURI)) == SOCKET_ERROR ) + goto exit; + } + else + { + rc = MQTTCLIENT_SUCCESS; + m->c->connect_state = WAIT_FOR_CONNACK; + if (MQTTPacket_send_connect(m->c, m->connect.details.conn.MQTTVersion, + m->connectProps, m->willProps) == SOCKET_ERROR) + { + rc = SOCKET_ERROR; + goto exit; + } + } + if (!m->c->cleansession && m->c->session == NULL) + m->c->session = SSL_get1_session(m->c->net.ssl); + } + } + else + { + rc = SOCKET_ERROR; + goto exit; + } + } + else + { +#endif + 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 ) + goto exit; + } + else + { + m->c->connect_state = WAIT_FOR_CONNACK; /* TCP/SSL connect completed, in which case send the MQTT connect packet */ + if ((rc = MQTTPacket_send_connect(m->c, m->connect.details.conn.MQTTVersion, + m->connectProps, m->willProps)) == SOCKET_ERROR) + goto exit; + } +#if defined(OPENSSL) + } +#endif + } +#if defined(OPENSSL) + else if (m->c->connect_state == SSL_IN_PROGRESS) /* SSL connect sent - wait for completion */ + { + rc = m->c->sslopts->struct_version >= 3 ? + SSLSocket_connect(m->c->net.ssl, m->c->net.socket, serverURI, + m->c->sslopts->verify, m->c->sslopts->ssl_error_cb, m->c->sslopts->ssl_error_context) : + SSLSocket_connect(m->c->net.ssl, m->c->net.socket, serverURI, + m->c->sslopts->verify, NULL, NULL); + if (rc != 1) + goto exit; + + if(!m->c->cleansession && m->c->session == NULL) + m->c->session = SSL_get1_session(m->c->net.ssl); + + if ( m->websocket ) + { + m->c->connect_state = WEBSOCKET_IN_PROGRESS; + if ((rc = WebSocket_connect(&m->c->net, serverURI)) == SOCKET_ERROR ) + goto exit; + } + else + { + m->c->connect_state = WAIT_FOR_CONNACK; /* SSL connect completed, in which case send the MQTT connect packet */ + if ((rc = MQTTPacket_send_connect(m->c, m->connect.details.conn.MQTTVersion, + m->connectProps, m->willProps)) == SOCKET_ERROR) + goto exit; + } + } +#endif + else if (m->c->connect_state == WEBSOCKET_IN_PROGRESS) /* Websocket connect sent - wait for completion */ + { + if ((rc = WebSocket_upgrade( &m->c->net ) ) == SOCKET_ERROR ) + goto exit; + else if (rc != TCPSOCKET_INTERRUPTED) + { + m->c->connect_state = WAIT_FOR_CONNACK; /* Websocket upgrade completed, in which case send the MQTT connect packet */ + if ((rc = MQTTPacket_send_connect(m->c, m->connect.details.conn.MQTTVersion, m->connectProps, m->willProps)) == SOCKET_ERROR) + goto exit; + } + } + +exit: + if ((rc != 0 && rc != TCPSOCKET_INTERRUPTED && (m->c->connect_state != SSL_IN_PROGRESS && m->c->connect_state != WEBSOCKET_IN_PROGRESS)) || (rc == SSL_FATAL)) + nextOrClose(m, MQTTASYNC_FAILURE, "TCP/TLS connect failure"); + + FUNC_EXIT_RC(rc); + return rc; +} + + +static MQTTPacket* MQTTAsync_cycle(int* sock, unsigned long timeout, int* rc) +{ + struct timeval tp = {0L, 0L}; + MQTTPacket* pack = NULL; + + 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 + /* 0 from getReadySocket indicates no work to do, rc -1 == error */ + *sock = Socket_getReadySocket(0, &tp,socket_mutex, &rc1); + *rc = rc1; + if (!MQTTAsync_tostop && *sock == 0 && (tp.tv_sec > 0L || tp.tv_usec > 0L)) + MQTTAsync_sleep(100L); +#if defined(OPENSSL) + } +#endif + MQTTAsync_lock_mutex(mqttasync_mutex); + if (*sock > 0 && rc1 == 0) + { + MQTTAsyncs* m = NULL; + if (ListFindItem(MQTTAsync_handles, sock, clientSockCompare) != NULL) + m = (MQTTAsync)(MQTTAsync_handles->current->content); + if (m != NULL) + { + Log(TRACE_MINIMUM, -1, "m->c->connect_state = %d", m->c->connect_state); + if (m->c->connect_state == TCP_IN_PROGRESS || m->c->connect_state == SSL_IN_PROGRESS || m->c->connect_state == WEBSOCKET_IN_PROGRESS) + *rc = MQTTAsync_connecting(m); + else + pack = MQTTPacket_Factory(m->c->MQTTVersion, &m->c->net, rc); + if (m->c->connect_state == WAIT_FOR_CONNACK && *rc == SOCKET_ERROR) + { + Log(TRACE_MINIMUM, -1, "CONNECT sent but MQTTPacket_Factory has returned SOCKET_ERROR"); + nextOrClose(m, MQTTASYNC_FAILURE, "TCP connect completion failure"); + } + } + if (pack) + { + int freed = 1; + + /* Note that these handle... functions free the packet structure that they are dealing with */ + if (pack->header.bits.type == PUBLISH) + *rc = MQTTProtocol_handlePublishes(pack, *sock); + else if (pack->header.bits.type == PUBACK || pack->header.bits.type == PUBCOMP || + pack->header.bits.type == PUBREC) + { + int msgid = 0, + msgtype = 0, + ackrc = 0, + mqttversion = 0; + MQTTProperties msgprops = MQTTProperties_initializer; + + /* This block is so that the ack variable is local and isn't accidentally reused */ + { + static Ack ack; + ack = *(Ack*)pack; + /* these values are stored because the packet structure is freed in the handle functions */ + msgid = ack.msgId; + msgtype = pack->header.bits.type; + if (ack.MQTTVersion >= MQTTVERSION_5) + { + ackrc = ack.rc; + msgprops = MQTTProperties_copy(&ack.properties); + mqttversion = ack.MQTTVersion; + } + } + + if (pack->header.bits.type == PUBCOMP) + *rc = MQTTProtocol_handlePubcomps(pack, *sock); + else if (pack->header.bits.type == PUBREC) + *rc = MQTTProtocol_handlePubrecs(pack, *sock); + else if (pack->header.bits.type == PUBACK) + *rc = MQTTProtocol_handlePubacks(pack, *sock); + if (!m) + Log(LOG_ERROR, -1, "PUBCOMP, PUBACK or PUBREC received for no client, msgid %d", msgid); + if (m && (msgtype != PUBREC || ackrc >= MQTTREASONCODE_UNSPECIFIED_ERROR)) + { + ListElement* current = NULL; + + if (m->dc) + { + Log(TRACE_MIN, -1, "Calling deliveryComplete for client %s, msgid %d", m->c->clientID, msgid); + (*(m->dc))(m->dcContext, msgid); + } + /* use the msgid to find the callback to be called */ + while (ListNextElement(m->responses, ¤t)) + { + MQTTAsync_queuedCommand* command = (MQTTAsync_queuedCommand*)(current->content); + if (command->command.token == msgid) + { + if (!ListDetach(m->responses, command)) /* then remove the response from the list */ + Log(LOG_ERROR, -1, "Publish command not removed from command list"); + if (command->command.onSuccess) + { + MQTTAsync_successData data; + + data.token = command->command.token; + data.alt.pub.destinationName = command->command.details.pub.destinationName; + data.alt.pub.message.payload = command->command.details.pub.payload; + data.alt.pub.message.payloadlen = command->command.details.pub.payloadlen; + data.alt.pub.message.qos = command->command.details.pub.qos; + data.alt.pub.message.retained = command->command.details.pub.retained; + Log(TRACE_MIN, -1, "Calling publish success for client %s", m->c->clientID); + (*(command->command.onSuccess))(command->command.context, &data); + } + else if (command->command.onSuccess5 && ackrc < MQTTREASONCODE_UNSPECIFIED_ERROR) + { + MQTTAsync_successData5 data = MQTTAsync_successData5_initializer; + + data.token = command->command.token; + data.alt.pub.destinationName = command->command.details.pub.destinationName; + data.alt.pub.message.payload = command->command.details.pub.payload; + data.alt.pub.message.payloadlen = command->command.details.pub.payloadlen; + data.alt.pub.message.qos = command->command.details.pub.qos; + data.alt.pub.message.retained = command->command.details.pub.retained; + data.properties = command->command.properties; + Log(TRACE_MIN, -1, "Calling publish success for client %s", m->c->clientID); + (*(command->command.onSuccess5))(command->command.context, &data); + } + else if (command->command.onFailure5 && ackrc >= MQTTREASONCODE_UNSPECIFIED_ERROR) + { + MQTTAsync_failureData5 data = MQTTAsync_failureData5_initializer; + + data.token = command->command.token; + data.reasonCode = ackrc; + data.properties = msgprops; + data.packet_type = pack->header.bits.type; + Log(TRACE_MIN, -1, "Calling publish failure for client %s", m->c->clientID); + (*(command->command.onFailure5))(command->command.context, &data); + } + MQTTAsync_freeCommand(command); + break; + } + } + if (mqttversion >= MQTTVERSION_5) + MQTTProperties_free(&msgprops); + } + } + else if (pack->header.bits.type == PUBREL) + *rc = MQTTProtocol_handlePubrels(pack, *sock); + else if (pack->header.bits.type == PINGRESP) + *rc = MQTTProtocol_handlePingresps(pack, *sock); + else + freed = 0; + if (freed) + pack = NULL; + } + } + MQTTAsync_retry(); + MQTTAsync_unlock_mutex(mqttasync_mutex); + FUNC_EXIT_RC(*rc); + return pack; +} + + +int MQTTAsync_getNoBufferedMessages(MQTTAsyncs* m) +{ + int count = 0; + + MQTTAsync_lock_mutex(mqttcommand_mutex); + count = m->noBufferedMessages; + MQTTAsync_unlock_mutex(mqttcommand_mutex); + return count; +} diff --git a/src/MQTTAsyncUtils.h b/src/MQTTAsyncUtils.h new file mode 100644 index 00000000..60f41b84 --- /dev/null +++ b/src/MQTTAsyncUtils.h @@ -0,0 +1,178 @@ +/******************************************************************************* + * Copyright (c) 2009, 2020 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 + * 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: + * Ian Craggs - initial implementation and documentation + *******************************************************************************/ + +#if !defined(MQTTASYNCUTILS_H_) +#define MQTTASYNCUTILS_H_ + +#include "MQTTPacket.h" +#include "Thread.h" + +#define URI_TCP "tcp://" +#define URI_WS "ws://" +#define URI_WSS "wss://" + +enum MQTTAsync_threadStates +{ + STOPPED, STARTING, RUNNING, STOPPING +}; + +typedef struct +{ + MQTTAsync_message* msg; + char* topicName; + int topicLen; + unsigned int seqno; /* only used on restore */ +} qEntry; + +typedef struct +{ + int type; + MQTTAsync_onSuccess* onSuccess; + MQTTAsync_onFailure* onFailure; + MQTTAsync_onSuccess5* onSuccess5; + MQTTAsync_onFailure5* onFailure5; + MQTTAsync_token token; + void* context; + START_TIME_TYPE start_time; + MQTTProperties properties; + union + { + struct + { + int count; + char** topics; + int* qoss; + MQTTSubscribe_options opts; + MQTTSubscribe_options* optlist; + } sub; + struct + { + int count; + char** topics; + } unsub; + struct + { + char* destinationName; + int payloadlen; + void* payload; + int qos; + int retained; + } pub; + struct + { + int internal; + int timeout; + enum MQTTReasonCodes reasonCode; + } dis; + struct + { + int currentURI; + int MQTTVersion; /**< current MQTT version being used to connect */ + } conn; + } details; +} MQTTAsync_command; + +typedef struct MQTTAsync_struct +{ + char* serverURI; + int ssl; + int websocket; + Clients* c; + + /* "Global", to the client, callback definitions */ + MQTTAsync_connectionLost* cl; + MQTTAsync_messageArrived* ma; + MQTTAsync_deliveryComplete* dc; + void* clContext; /* the context to be associated with the conn lost callback*/ + void* maContext; /* the context to be associated with the msg arrived callback*/ + void* dcContext; /* the context to be associated with the deliv complete callback*/ + + MQTTAsync_connected* connected; + void* connected_context; /* the context to be associated with the connected callback*/ + + MQTTAsync_disconnected* disconnected; + void* disconnected_context; /* the context to be associated with the disconnected callback*/ + + MQTTAsync_updateConnectOptions* updateConnectOptions; + void* updateConnectOptions_context; + + /* Each time connect is called, we store the options that were used. These are reused in + any call to reconnect, or an automatic reconnect attempt */ + MQTTAsync_command connect; /* Connect operation properties */ + MQTTAsync_command disconnect; /* Disconnect operation properties */ + MQTTAsync_command* pending_write; /* Is there a socket write pending? */ + + List* responses; + unsigned int command_seqno; + + MQTTPacket* pack; + + /* added for offline buffering */ + MQTTAsync_createOptions* createOptions; + int shouldBeConnected; + int noBufferedMessages; /* the current number of buffered (publish) messages for this client */ + + /* added for automatic reconnect */ + int automaticReconnect; + int minRetryInterval; + int maxRetryInterval; + int serverURIcount; + char** serverURIs; + int connectTimeout; + + int currentInterval; + int currentIntervalBase; + START_TIME_TYPE lastConnectionFailedTime; + int retrying; + int reconnectNow; + + /* MQTT V5 properties */ + MQTTProperties* connectProps; + MQTTProperties* willProps; + +} MQTTAsyncs; + +typedef struct +{ + MQTTAsync_command command; + MQTTAsyncs* client; + unsigned int seqno; /* only used on restore */ + int not_restored; + char* key; /* if not_restored, this holds the key */ +} MQTTAsync_queuedCommand; + +void MQTTAsync_lock_mutex(mutex_type amutex); +void MQTTAsync_unlock_mutex(mutex_type amutex); +void MQTTAsync_terminate(void); +#if !defined(NO_PERSISTENCE) +int MQTTAsync_restoreCommands(MQTTAsyncs* client); +#endif +int MQTTAsync_addCommand(MQTTAsync_queuedCommand* command, int command_size); +void MQTTAsync_emptyMessageQueue(Clients* client); +void MQTTAsync_freeResponses(MQTTAsyncs* m); +void MQTTAsync_freeCommands(MQTTAsyncs* m); +int MQTTAsync_unpersistCommandsAndMessages(Clients* c); +void MQTTAsync_closeSession(Clients* client, enum MQTTReasonCodes reasonCode, MQTTProperties* props); +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 setRetryLoopInterval(int keepalive); + +thread_return_type WINAPI MQTTAsync_sendThread(void* n); +thread_return_type WINAPI MQTTAsync_receiveThread(void* n); + +#endif /* MQTTASYNCUTILS_H_ */