From: rtel Date: Fri, 19 Jul 2019 00:37:33 +0000 (+0000) Subject: Add missing files so base MQTT project builds. X-Git-Tag: V10.3.0~133 X-Git-Url: https://git.sur5r.net/?a=commitdiff_plain;h=f5a3ee441ddbfee3a966e924fcf9e8adf624e45b;p=freertos Add missing files so base MQTT project builds. git-svn-id: https://svn.code.sf.net/p/freertos/code/trunk@2688 1d2547de-c912-0410-9cb9-b8ca96c0e9e2 --- diff --git a/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/common/logging/iot_logging.c b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/common/logging/iot_logging.c new file mode 100644 index 000000000..aac8d3181 --- /dev/null +++ b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/common/logging/iot_logging.c @@ -0,0 +1,454 @@ +/* + * Amazon FreeRTOS Common V1.0.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +/** + * @file iot_logging.c + * @brief Implementation of logging functions from iot_logging.h + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Standard includes. */ +#include +#include +#include + +/* Platform clock include. */ +#include "platform/iot_clock.h" + +/* Logging includes. */ +#include "private/iot_logging.h" + +/*-----------------------------------------------------------*/ + +/* This implementation assumes the following values for the log level constants. + * Ensure that the values have not been modified. */ +#if IOT_LOG_NONE != 0 + #error "IOT_LOG_NONE must be 0." +#endif +#if IOT_LOG_ERROR != 1 + #error "IOT_LOG_ERROR must be 1." +#endif +#if IOT_LOG_WARN != 2 + #error "IOT_LOG_WARN must be 2." +#endif +#if IOT_LOG_INFO != 3 + #error "IOT_LOG_INFO must be 3." +#endif +#if IOT_LOG_DEBUG != 4 + #error "IOT_LOG_DEBUG must be 4." +#endif + +/** + * @def IotLogging_Puts( message ) + * @brief Function the logging library uses to print a line. + * + * This function can be set by using a define. By default, the standard library + * [puts](http://pubs.opengroup.org/onlinepubs/9699919799/functions/puts.html) + * function is used. + */ +#ifndef IotLogging_Puts + #define IotLogging_Puts puts +#endif + +/* + * Provide default values for undefined memory allocation functions based on + * the usage of dynamic memory allocation. + */ +#if IOT_STATIC_MEMORY_ONLY == 1 + /* Static memory allocation header. */ + #include "private/iot_static_memory.h" + +/** + * @brief Allocate a new logging buffer. This function must have the same + * signature as [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + #ifndef IotLogging_Malloc + #define IotLogging_Malloc Iot_MallocMessageBuffer + #endif + +/** + * @brief Free a logging buffer. This function must have the same signature + * as [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + #ifndef IotLogging_Free + #define IotLogging_Free Iot_FreeMessageBuffer + #endif + +/** + * @brief Get the size of a logging buffer. Statically-allocated buffers + * should all have the same size. + */ + #ifndef IotLogging_StaticBufferSize + #define IotLogging_StaticBufferSize Iot_MessageBufferSize + #endif +#else /* if IOT_STATIC_MEMORY_ONLY == 1 */ + #ifndef IotLogging_Malloc + #include + #define IotLogging_Malloc malloc + #endif + + #ifndef IotLogging_Free + #include + #define IotLogging_Free free + #endif +#endif /* if IOT_STATIC_MEMORY_ONLY == 1 */ + +/** + * @brief A guess of the maximum length of a timestring. + * + * There's no way for this logging library to know the length of a timestring + * before it's generated. Therefore, the logging library will assume a maximum + * length of any timestring it may get. This value should be generous enough + * to accommodate the vast majority of timestrings. + * + * @see @ref platform_clock_function_gettimestring + */ +#define MAX_TIMESTRING_LENGTH ( 64 ) + +/** + * @brief The longest string in #_pLogLevelStrings (below), plus 3 to accommodate + * `[]` and a null-terminator. + */ +#define MAX_LOG_LEVEL_LENGTH ( 8 ) + +/** + * @brief How many bytes @ref logging_function_genericprintbuffer should output on + * each line. + */ +#define BYTES_PER_LINE ( 16 ) + +/*-----------------------------------------------------------*/ + +/** + * @brief Lookup table for log levels. + * + * Converts one of the @ref logging_constants_levels to a string. + */ +static const char * const _pLogLevelStrings[ 5 ] = +{ + "", /* IOT_LOG_NONE */ + "ERROR", /* IOT_LOG_ERROR */ + "WARN ", /* IOT_LOG_WARN */ + "INFO ", /* IOT_LOG_INFO */ + "DEBUG" /* IOT_LOG_DEBUG */ +}; + +/*-----------------------------------------------------------*/ + +#if !defined( IOT_STATIC_MEMORY_ONLY ) || ( IOT_STATIC_MEMORY_ONLY == 0 ) + static bool _reallocLoggingBuffer( void ** pOldBuffer, + size_t newSize, + size_t oldSize ) + { + bool status = false; + + /* Allocate a new, larger buffer. */ + void * pNewBuffer = IotLogging_Malloc( newSize ); + + /* Ensure that memory allocation succeeded. */ + if( pNewBuffer != NULL ) + { + /* Copy the data from the old buffer to the new buffer. */ + ( void ) memcpy( pNewBuffer, *pOldBuffer, oldSize ); + + /* Free the old buffer and update the pointer. */ + IotLogging_Free( *pOldBuffer ); + *pOldBuffer = pNewBuffer; + + status = true; + } + + return status; + } +#endif /* if !defined( IOT_STATIC_MEMORY_ONLY ) || ( IOT_STATIC_MEMORY_ONLY == 0 ) */ + +/*-----------------------------------------------------------*/ + +void IotLog_Generic( int libraryLogSetting, + const char * const pLibraryName, + int messageLevel, + const IotLogConfig_t * const pLogConfig, + const char * const pFormat, + ... ) +{ + int requiredMessageSize = 0; + size_t bufferSize = 0, + bufferPosition = 0, timestringLength = 0; + char * pLoggingBuffer = NULL; + va_list args; + + /* If the library's log level setting is lower than the message level, + * return without doing anything. */ + if( ( messageLevel == 0 ) || ( messageLevel > libraryLogSetting ) ) + { + return; + } + + if( ( pLogConfig == NULL ) || ( pLogConfig->hideLogLevel == false ) ) + { + /* Add length of log level if requested. */ + bufferSize += MAX_LOG_LEVEL_LENGTH; + } + + /* Estimate the amount of buffer needed for this log message. */ + if( ( pLogConfig == NULL ) || ( pLogConfig->hideLibraryName == false ) ) + { + /* Add size of library name if requested. Add 2 to accommodate "[]". */ + bufferSize += strlen( pLibraryName ) + 2; + } + + if( ( pLogConfig == NULL ) || ( pLogConfig->hideTimestring == false ) ) + { + /* Add length of timestring if requested. */ + bufferSize += MAX_TIMESTRING_LENGTH; + } + + /* Add 64 as an initial (arbitrary) guess for the length of the message. */ + bufferSize += 64; + + /* In static memory mode, check that the log message will fit in the a + * static buffer. */ + #if IOT_STATIC_MEMORY_ONLY == 1 + if( bufferSize >= IotLogging_StaticBufferSize() ) + { + /* If the static buffers are likely too small to fit the log message, + * return. */ + return; + } + + /* Otherwise, update the buffer size to the size of a static buffer. */ + bufferSize = IotLogging_StaticBufferSize(); + #endif + + /* Allocate memory for the logging buffer. */ + pLoggingBuffer = ( char * ) IotLogging_Malloc( bufferSize ); + + if( pLoggingBuffer == NULL ) + { + return; + } + + /* Print the message log level if requested. */ + if( ( pLogConfig == NULL ) || ( pLogConfig->hideLogLevel == false ) ) + { + /* Ensure that message level is valid. */ + if( ( messageLevel >= IOT_LOG_NONE ) && ( messageLevel <= IOT_LOG_DEBUG ) ) + { + /* Add the log level string to the logging buffer. */ + requiredMessageSize = snprintf( pLoggingBuffer + bufferPosition, + bufferSize - bufferPosition, + "[%s]", + _pLogLevelStrings[ messageLevel ] ); + + /* Check for encoding errors. */ + if( requiredMessageSize <= 0 ) + { + IotLogging_Free( pLoggingBuffer ); + + return; + } + + /* Update the buffer position. */ + bufferPosition += ( size_t ) requiredMessageSize; + } + } + + /* Print the library name if requested. */ + if( ( pLogConfig == NULL ) || ( pLogConfig->hideLibraryName == false ) ) + { + /* Add the library name to the logging buffer. */ + requiredMessageSize = snprintf( pLoggingBuffer + bufferPosition, + bufferSize - bufferPosition, + "[%s]", + pLibraryName ); + + /* Check for encoding errors. */ + if( requiredMessageSize <= 0 ) + { + IotLogging_Free( pLoggingBuffer ); + + return; + } + + /* Update the buffer position. */ + bufferPosition += ( size_t ) requiredMessageSize; + } + + /* Print the timestring if requested. */ + if( ( pLogConfig == NULL ) || ( pLogConfig->hideTimestring == false ) ) + { + /* Add the opening '[' enclosing the timestring. */ + pLoggingBuffer[ bufferPosition ] = '['; + bufferPosition++; + + /* Generate the timestring and add it to the buffer. */ + if( IotClock_GetTimestring( pLoggingBuffer + bufferPosition, + bufferSize - bufferPosition, + ×tringLength ) == true ) + { + /* If the timestring was successfully generated, add the closing "]". */ + bufferPosition += timestringLength; + pLoggingBuffer[ bufferPosition ] = ']'; + bufferPosition++; + } + else + { + /* Sufficient memory for a timestring should have been allocated. A timestring + * probably failed to generate due to a clock read error; remove the opening '[' + * from the logging buffer. */ + bufferPosition--; + pLoggingBuffer[ bufferPosition ] = '\0'; + } + } + + /* Add a padding space between the last closing ']' and the message, unless + * the logging buffer is empty. */ + if( bufferPosition > 0 ) + { + pLoggingBuffer[ bufferPosition ] = ' '; + bufferPosition++; + } + + va_start( args, pFormat ); + + /* Add the log message to the logging buffer. */ + requiredMessageSize = vsnprintf( pLoggingBuffer + bufferPosition, + bufferSize - bufferPosition, + pFormat, + args ); + + va_end( args ); + + /* If the logging buffer was too small to fit the log message, reallocate + * a larger logging buffer. */ + if( ( size_t ) requiredMessageSize >= bufferSize - bufferPosition ) + { + #if IOT_STATIC_MEMORY_ONLY == 1 + + /* There's no point trying to allocate a larger static buffer. Return + * immediately. */ + IotLogging_Free( pLoggingBuffer ); + + return; + #else + if( _reallocLoggingBuffer( ( void ** ) &pLoggingBuffer, + ( size_t ) requiredMessageSize + bufferPosition + 1, + bufferSize ) == false ) + { + /* If buffer reallocation failed, return. */ + IotLogging_Free( pLoggingBuffer ); + + return; + } + + /* Reallocation successful, update buffer size. */ + bufferSize = ( size_t ) requiredMessageSize + bufferPosition + 1; + + /* Add the log message to the buffer. Now that the buffer has been + * reallocated, this should succeed. */ + va_start( args, pFormat ); + requiredMessageSize = vsnprintf( pLoggingBuffer + bufferPosition, + bufferSize - bufferPosition, + pFormat, + args ); + va_end( args ); + #endif /* if IOT_STATIC_MEMORY_ONLY == 1 */ + } + + /* Check for encoding errors. */ + if( requiredMessageSize <= 0 ) + { + IotLogging_Free( pLoggingBuffer ); + + return; + } + + /* Print the logging buffer to stdout. */ + IotLogging_Puts( pLoggingBuffer ); + + /* Free the logging buffer. */ + IotLogging_Free( pLoggingBuffer ); +} + +/*-----------------------------------------------------------*/ + +void IotLog_GenericPrintBuffer( const char * const pLibraryName, + const char * const pHeader, + const uint8_t * const pBuffer, + size_t bufferSize ) +{ + size_t i = 0, offset = 0; + + /* Allocate memory to hold each line of the log message. Since each byte + * of pBuffer is printed in 4 characters (2 digits, a space, and a null- + * terminator), the size of each line is 4 * BYTES_PER_LINE. */ + char * pMessageBuffer = IotLogging_Malloc( 4 * BYTES_PER_LINE ); + + /* Exit if no memory is available. */ + if( pMessageBuffer == NULL ) + { + return; + } + + /* Print pHeader before printing pBuffer. */ + if( pHeader != NULL ) + { + IotLog_Generic( IOT_LOG_DEBUG, + pLibraryName, + IOT_LOG_DEBUG, + NULL, + pHeader ); + } + + /* Print each byte in pBuffer. */ + for( i = 0; i < bufferSize; i++ ) + { + /* Print a line if BYTES_PER_LINE is reached. But don't print a line + * at the beginning (when i=0). */ + if( ( i % BYTES_PER_LINE == 0 ) && ( i != 0 ) ) + { + IotLogging_Puts( pMessageBuffer ); + + /* Reset offset so that pMessageBuffer is filled from the beginning. */ + offset = 0; + } + + /* Print a single byte into pMessageBuffer. */ + ( void ) snprintf( pMessageBuffer + offset, 4, "%02x ", pBuffer[ i ] ); + + /* Move the offset where the next character is printed. */ + offset += 3; + } + + /* Print the final line of bytes. This line isn't printed by the for-loop above. */ + IotLogging_Puts( pMessageBuffer ); + + /* Free memory used by this function. */ + IotLogging_Free( pMessageBuffer ); +} + +/*-----------------------------------------------------------*/ diff --git a/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/common/logging/iot_logging_task_dynamic_buffers.c b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/common/logging/iot_logging_task_dynamic_buffers.c new file mode 100644 index 000000000..ac08e2271 --- /dev/null +++ b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/common/logging/iot_logging_task_dynamic_buffers.c @@ -0,0 +1,250 @@ +/* + * Amazon FreeRTOS Common V1.0.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +/* FreeRTOS includes. */ +#include "FreeRTOS.h" +#include "task.h" +#include "queue.h" + +/* Logging includes. */ +#include "iot_logging_task.h" + +/* Standard includes. */ +#include +#include +#include + +/* Sanity check all the definitions required by this file are set. */ +#ifndef configPRINT_STRING + #error configPRINT_STRING( x ) must be defined in FreeRTOSConfig.h to use this logging file. Set configPRINT_STRING( x ) to a function that outputs a string, where X is the string. For example, #define configPRINT_STRING( x ) MyUARTWriteString( X ) +#endif + +#ifndef configLOGGING_MAX_MESSAGE_LENGTH + #error configLOGGING_MAX_MESSAGE_LENGTH must be defined in FreeRTOSConfig.h to use this logging file. configLOGGING_MAX_MESSAGE_LENGTH sets the size of the buffer into which formatted text is written, so also sets the maximum log message length. +#endif + +#ifndef configLOGGING_INCLUDE_TIME_AND_TASK_NAME + #error configLOGGING_INCLUDE_TIME_AND_TASK_NAME must be defined in FreeRTOSConfig.h to use this logging file. Set configLOGGING_INCLUDE_TIME_AND_TASK_NAME to 1 to prepend a time stamp, message number and the name of the calling task to each logged message. Otherwise set to 0. +#endif + +/* A block time of 0 just means don't block. */ +#define loggingDONT_BLOCK 0 + +/*-----------------------------------------------------------*/ + +/* + * The task that actually performs the print output. Using a separate task + * enables the use of slow output, such as as a UART, without the task that is + * outputting the log message having to wait for the message to be completely + * written. Using a separate task also serializes access to the output port. + * + * The structure of this task is very simple; it blocks on a queue to wait for + * a pointer to a string, sending any received strings to a macro that performs + * the actual output. The macro is port specific, so implemented outside of + * this file. This version uses dynamic memory, so the buffer that contained + * the log message is freed after it has been output. + */ +static void prvLoggingTask( void * pvParameters ); + +/*-----------------------------------------------------------*/ + +/* + * The queue used to pass pointers to log messages from the task that created + * the message to the task that will performs the output. + */ +static QueueHandle_t xQueue = NULL; + +/*-----------------------------------------------------------*/ + +BaseType_t xLoggingTaskInitialize( uint16_t usStackSize, + UBaseType_t uxPriority, + UBaseType_t uxQueueLength ) +{ + BaseType_t xReturn = pdFAIL; + + /* Ensure the logging task has not been created already. */ + if( xQueue == NULL ) + { + /* Create the queue used to pass pointers to strings to the logging task. */ + xQueue = xQueueCreate( uxQueueLength, sizeof( char ** ) ); + + if( xQueue != NULL ) + { + if( xTaskCreate( prvLoggingTask, "Logging", usStackSize, NULL, uxPriority, NULL ) == pdPASS ) + { + xReturn = pdPASS; + } + else + { + /* Could not create the task, so delete the queue again. */ + vQueueDelete( xQueue ); + } + } + } + + return xReturn; +} +/*-----------------------------------------------------------*/ + +static void prvLoggingTask( void * pvParameters ) +{ + char * pcReceivedString = NULL; + + for( ; ; ) + { + /* Block to wait for the next string to print. */ + if( xQueueReceive( xQueue, &pcReceivedString, portMAX_DELAY ) == pdPASS ) + { + configPRINT_STRING( pcReceivedString ); + vPortFree( ( void * ) pcReceivedString ); + } + } +} +/*-----------------------------------------------------------*/ + +/*! + * \brief Formats a string to be printed and sends it + * to the print queue. + * + * Appends the message number, time (in ticks), and task + * that called vLoggingPrintf to the beginning of each + * print statement. + * + */ +void vLoggingPrintf( const char * pcFormat, + ... ) +{ + size_t xLength = 0; + int32_t xLength2 = 0; + va_list args; + char * pcPrintString = NULL; + + /* The queue is created by xLoggingTaskInitialize(). Check + * xLoggingTaskInitialize() has been called. */ + configASSERT( xQueue ); + + /* Allocate a buffer to hold the log message. */ + pcPrintString = pvPortMalloc( configLOGGING_MAX_MESSAGE_LENGTH ); + + if( pcPrintString != NULL ) + { + /* There are a variable number of parameters. */ + va_start( args, pcFormat ); + + if( strcmp( pcFormat, "\n" ) != 0 ) + { + #if ( configLOGGING_INCLUDE_TIME_AND_TASK_NAME == 1 ) + { + const char * pcTaskName; + const char * pcNoTask = "None"; + static BaseType_t xMessageNumber = 0; + + /* Add a time stamp and the name of the calling task to the + * start of the log. */ + if( xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED ) + { + pcTaskName = pcTaskGetName( NULL ); + } + else + { + pcTaskName = pcNoTask; + } + + xLength = snprintf( pcPrintString, configLOGGING_MAX_MESSAGE_LENGTH, "%lu %lu [%s] ", + ( unsigned long ) xMessageNumber++, + ( unsigned long ) xTaskGetTickCount(), + pcTaskName ); + } + #else /* if ( configLOGGING_INCLUDE_TIME_AND_TASK_NAME == 1 ) */ + { + xLength = 0; + } + #endif /* if ( configLOGGING_INCLUDE_TIME_AND_TASK_NAME == 1 ) */ + } + + xLength2 = vsnprintf( pcPrintString + xLength, configLOGGING_MAX_MESSAGE_LENGTH - xLength, pcFormat, args ); + + if( xLength2 < 0 ) + { + /* vsnprintf() failed. Restore the terminating NULL + * character of the first part. Note that the first + * part of the buffer may be empty if the value of + * configLOGGING_INCLUDE_TIME_AND_TASK_NAME is not + * 1 and as a result, the whole buffer may be empty. + * That's the reason we have a check for xLength > 0 + * before sending the buffer to the logging task. + */ + xLength2 = 0; + pcPrintString[ xLength ] = '\0'; + } + + xLength += ( size_t ) xLength2; + va_end( args ); + + /* Only send the buffer to the logging task if it is + * not empty. */ + if( xLength > 0 ) + { + /* Send the string to the logging task for IO. */ + if( xQueueSend( xQueue, &pcPrintString, loggingDONT_BLOCK ) != pdPASS ) + { + /* The buffer was not sent so must be freed again. */ + vPortFree( ( void * ) pcPrintString ); + } + } + else + { + /* The buffer was not sent, so it must be + * freed. */ + vPortFree( ( void * ) pcPrintString ); + } + } +} +/*-----------------------------------------------------------*/ + +void vLoggingPrint( const char * pcMessage ) +{ + char * pcPrintString = NULL; + size_t xLength = 0; + + /* The queue is created by xLoggingTaskInitialize(). Check + * xLoggingTaskInitialize() has been called. */ + configASSERT( xQueue ); + + xLength = strlen( pcMessage ) + 1; + pcPrintString = pvPortMalloc( xLength ); + + if( pcPrintString != NULL ) + { + strncpy( pcPrintString, pcMessage, xLength ); + + /* Send the string to the logging task for IO. */ + if( xQueueSend( xQueue, &pcPrintString, loggingDONT_BLOCK ) != pdPASS ) + { + /* The buffer was not sent so must be freed again. */ + vPortFree( ( void * ) pcPrintString ); + } + } +} diff --git a/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/include/iot_mqtt.h b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/include/iot_mqtt.h new file mode 100644 index 000000000..327784a3e --- /dev/null +++ b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/include/iot_mqtt.h @@ -0,0 +1,823 @@ +/* + * Amazon FreeRTOS MQTT V2.0.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +/** + * @file iot_mqtt.h + * @brief User-facing functions of the MQTT 3.1.1 library. + */ + +#ifndef IOT_MQTT_H_ +#define IOT_MQTT_H_ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* MQTT types include. */ +#include "types/iot_mqtt_types.h" + +/*------------------------- MQTT library functions --------------------------*/ + +/** + * @functionspage{mqtt,MQTT library} + * - @functionname{mqtt_function_init} + * - @functionname{mqtt_function_cleanup} + * - @functionname{mqtt_function_receivecallback} + * - @functionname{mqtt_function_connect} + * - @functionname{mqtt_function_disconnect} + * - @functionname{mqtt_function_subscribe} + * - @functionname{mqtt_function_timedsubscribe} + * - @functionname{mqtt_function_unsubscribe} + * - @functionname{mqtt_function_timedunsubscribe} + * - @functionname{mqtt_function_publish} + * - @functionname{mqtt_function_timedpublish} + * - @functionname{mqtt_function_wait} + * - @functionname{mqtt_function_strerror} + * - @functionname{mqtt_function_operationtype} + * - @functionname{mqtt_function_issubscribed} + */ + +/** + * @functionpage{IotMqtt_Init,mqtt,init} + * @functionpage{IotMqtt_Cleanup,mqtt,cleanup} + * @functionpage{IotMqtt_ReceiveCallback,mqtt,receivecallback} + * @functionpage{IotMqtt_Connect,mqtt,connect} + * @functionpage{IotMqtt_Disconnect,mqtt,disconnect} + * @functionpage{IotMqtt_Subscribe,mqtt,subscribe} + * @functionpage{IotMqtt_TimedSubscribe,mqtt,timedsubscribe} + * @functionpage{IotMqtt_Unsubscribe,mqtt,unsubscribe} + * @functionpage{IotMqtt_TimedUnsubscribe,mqtt,timedunsubscribe} + * @functionpage{IotMqtt_Publish,mqtt,publish} + * @functionpage{IotMqtt_TimedPublish,mqtt,timedpublish} + * @functionpage{IotMqtt_Wait,mqtt,wait} + * @functionpage{IotMqtt_strerror,mqtt,strerror} + * @functionpage{IotMqtt_OperationType,mqtt,operationtype} + * @functionpage{IotMqtt_IsSubscribed,mqtt,issubscribed} + */ + +/** + * @brief One-time initialization function for the MQTT library. + * + * This function performs setup of the MQTT library. It must be called + * once (and only once) before calling any other MQTT function. Calling this + * function more than once without first calling @ref mqtt_function_cleanup + * may result in a crash. + * + * @return One of the following: + * - #IOT_MQTT_SUCCESS + * - #IOT_MQTT_INIT_FAILED + * + * @warning No thread-safety guarantees are provided for this function. + * + * @see @ref mqtt_function_cleanup + */ +/* @[declare_mqtt_init] */ +IotMqttError_t IotMqtt_Init( void ); +/* @[declare_mqtt_init] */ + +/** + * @brief One-time deinitialization function for the MQTT library. + * + * This function frees resources taken in @ref mqtt_function_init. It should be + * called after [closing all MQTT connections](@ref mqtt_function_disconnect) to + * clean up the MQTT library. After this function returns, @ref mqtt_function_init + * must be called again before calling any other MQTT function. + * + * @warning No thread-safety guarantees are provided for this function. Do not + * call this function if any MQTT connections are open! + * + * @see @ref mqtt_function_init + */ +/* @[declare_mqtt_cleanup] */ +void IotMqtt_Cleanup( void ); +/* @[declare_mqtt_cleanup] */ + +/** + * @brief Network receive callback for the MQTT library. + * + * This function should be called by the system whenever data is available for + * the MQTT library. + * + * @param[in] pNetworkConnection The network connection associated with the MQTT + * connection, passed by the network stack. + * @param[in] pReceiveContext A pointer to the MQTT connection handle for which + * the packet was received. + */ +/* @[declare_mqtt_receivecallback] */ +void IotMqtt_ReceiveCallback( void * pNetworkConnection, + void * pReceiveContext ); +/* @[declare_mqtt_receivecallback] */ + +/** + * @brief Establish a new MQTT connection. + * + * This function opens a connection between a new MQTT client and an MQTT server + * (also called a broker). MQTT connections are established on top of transport + * layer protocols (such as TCP/IP), and optionally, application layer security + * protocols (such as TLS). The MQTT packet that establishes a connection is called + * the MQTT CONNECT packet. After @ref mqtt_function_init, this function must be + * called before any other MQTT library function. + * + * If [pConnectInfo->cleanSession](@ref IotMqttConnectInfo_t.cleanSession) is `true`, + * this function establishes a clean MQTT session. Subscriptions and unacknowledged + * PUBLISH messages will be discarded when the connection is closed. + * + * If [pConnectInfo->cleanSession](@ref IotMqttConnectInfo_t.cleanSession) is `false`, + * this function establishes (or re-establishes) a persistent MQTT session. The parameters + * [pConnectInfo->pPreviousSubscriptions](@ref IotMqttConnectInfo_t.pPreviousSubscriptions) + * and [pConnectInfo->previousSubscriptionCount](@ref IotMqttConnectInfo_t.previousSubscriptionCount) + * may be used to restore subscriptions present in a re-established persistent session. + * Any restored subscriptions MUST have been present in the persistent session; + * this function does not send an MQTT SUBSCRIBE packet! + * + * This MQTT library is network agnostic, meaning it has no knowledge of the + * underlying network protocol carrying the MQTT packets. It interacts with the + * network through a network abstraction layer, allowing it to be used with many + * different network stacks. The network abstraction layer is established + * per-connection, allowing every #IotMqttConnection_t to use a different network + * stack. The parameter `pNetworkInterface` sets up the network abstraction layer + * for an MQTT connection; see the documentation on #IotMqttNetworkInfo_t for details + * on its members. + * + * The `pConnectInfo` parameter provides the contents of the MQTT CONNECT packet. + * Most members [are defined by the MQTT spec.](@ref IotMqttConnectInfo_t). The + * [pConnectInfo->pWillInfo](@ref IotMqttConnectInfo_t.pWillInfo) member provides + * information on a Last Will and Testament (LWT) message to be published if the + * MQTT connection is closed without [sending a DISCONNECT packet] + * (@ref mqtt_function_disconnect). Unlike other PUBLISH + * messages, a LWT message payload is limited to 65535 bytes in length. Additionally, + * the retry [interval](@ref IotMqttPublishInfo_t.retryMs) and [limit] + * (@ref IotMqttPublishInfo_t.retryLimit) members of #IotMqttPublishInfo_t + * are ignored for LWT messages. The LWT message is optional; `pWillInfo` may be NULL. + * + * Unlike @ref mqtt_function_publish, @ref mqtt_function_subscribe, and + * @ref mqtt_function_unsubscribe, this function is always blocking. Additionally, + * because the MQTT connection acknowledgement packet (CONNACK packet) does not + * contain any information on which CONNECT packet it acknowledges, only one + * CONNECT operation may be in progress at any time. This means that parallel + * threads making calls to @ref mqtt_function_connect will be serialized to send + * their CONNECT packets one-by-one. + * + * @param[in] pNetworkInfo Information on the transport-layer network connection + * to use with the MQTT connection. + * @param[in] pConnectInfo MQTT connection setup parameters. + * @param[in] timeoutMs If the MQTT server does not accept the connection within + * this timeout, this function returns #IOT_MQTT_TIMEOUT. + * @param[out] pMqttConnection Set to a newly-initialized MQTT connection handle + * if this function succeeds. + * + * @return One of the following: + * - #IOT_MQTT_SUCCESS + * - #IOT_MQTT_BAD_PARAMETER + * - #IOT_MQTT_NO_MEMORY + * - #IOT_MQTT_NETWORK_ERROR + * - #IOT_MQTT_SCHEDULING_ERROR + * - #IOT_MQTT_BAD_RESPONSE + * - #IOT_MQTT_TIMEOUT + * - #IOT_MQTT_SERVER_REFUSED + * + * Example + * @code{c} + * // An initialized and connected network connection. + * IotNetworkConnection_t pNetworkConnection; + * + * // Parameters to MQTT connect. + * IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + * IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; + * IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; + * IotMqttPublishInfo_t willInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; + * + * // Example network abstraction types. + * IotNetworkServerInfo_t serverInfo = { ... }; + * IotNetworkCredentialInfo_t credentialInfo = { ... }; + * IotNetworkInterface_t networkInterface = { ... }; + * + * // Example using a generic network implementation. + * networkInfo.createNetworkConnection = true; + * networkInfo.pNetworkServerInfo = &serverInfo; + * networkInfo.pNetworkCredentialInfo = &credentialInfo; + * networkInfo.pNetworkInterface = &networkInterface; + * + * // Set the members of the connection info (password and username not used). + * connectInfo.cleanSession = true; + * connectInfo.keepAliveSeconds = 30; + * connectInfo.pClientIdentifier = "uniqueclientidentifier"; + * connectInfo.clientIdentifierLength = 22; + * + * // Set the members of the will info (retain and retry not used). + * willInfo.qos = IOT_MQTT_QOS_1; + * willInfo.pTopicName = "will/topic/name"; + * willInfo.topicNameLength = 15; + * willInfo.pPayload = "MQTT client unexpectedly disconnected."; + * willInfo.payloadLength = 38; + * + * // Set the pointer to the will info. + * connectInfo.pWillInfo = &willInfo; + * + * // Call CONNECT with a 5 second block time. Should return + * // IOT_MQTT_SUCCESS when successful. + * IotMqttError_t result = IotMqtt_Connect( &networkInfo, + * &connectInfo, + * 5000, + * &mqttConnection ); + * + * if( result == IOT_MQTT_SUCCESS ) + * { + * // Do something with the MQTT connection... + * + * // Clean up and close the MQTT connection once it's no longer needed. + * IotMqtt_Disconnect( mqttConnection, 0 ); + * } + * @endcode + */ +/* @[declare_mqtt_connect] */ +IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, + const IotMqttConnectInfo_t * pConnectInfo, + uint32_t timeoutMs, + IotMqttConnection_t * const pMqttConnection ); +/* @[declare_mqtt_connect] */ + +/** + * @brief Closes an MQTT connection and frees resources. + * + * This function closes an MQTT connection and should only be called once + * the MQTT connection is no longer needed. Its exact behavior depends on the + * `flags` parameter. + * + * Normally, `flags` should be `0`. This gracefully shuts down an MQTT + * connection by sending an MQTT DISCONNECT packet. Any [network close function] + * (@ref IotNetworkInterface_t::close) provided [when the connection was established] + * (@ref mqtt_function_connect) will also be called. Note that because the MQTT server + * will not acknowledge a DISCONNECT packet, the client has no way of knowing if + * the server received the DISCONNECT packet. In the case where the DISCONNECT + * packet is lost in transport, any Last Will and Testament (LWT) message established + * with the connection may be published. However, if the DISCONNECT reaches the + * MQTT server, the LWT message will be discarded and not published. + * + * Should the underlying network connection become unusable, this function should + * be called with `flags` set to #IOT_MQTT_FLAG_CLEANUP_ONLY. In this case, no + * DISCONNECT packet will be sent, though the [network close function](@ref IotNetworkInterface_t::close) + * will still be called. This function will only free the resources used by the MQTT + * connection; it still must be called even if the network is offline to avoid leaking + * resources. + * + * Once this function is called, its parameter `mqttConnection` should no longer + * be used. + * + * @param[in] mqttConnection The MQTT connection to close and clean up. + * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags. + */ +/* @[declare_mqtt_disconnect] */ +void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, + uint32_t flags ); +/* @[declare_mqtt_disconnect] */ + +/** + * @brief Subscribes to the given array of topic filters and receive an asynchronous + * notification when the subscribe completes. + * + * This function transmits an MQTT SUBSCRIBE packet to the server. A SUBSCRIBE + * packet notifies the server to send any matching PUBLISH messages to this client. + * A single SUBSCRIBE packet may carry more than one topic filter, hence the + * parameters to this function include an array of [subscriptions] + * (@ref IotMqttSubscription_t). + * + * An MQTT subscription has two pieces: + * 1. The subscription topic filter registered with the MQTT server. The MQTT + * SUBSCRIBE packet sent from this client to server notifies the server to send + * messages matching the given topic filters to this client. + * 2. The [callback function](@ref IotMqttCallbackInfo_t.function) that this + * client will invoke when an incoming message is received. The callback function + * notifies applications of an incoming PUBLISH message. + * + * The helper function @ref mqtt_function_issubscribed can be used to check if a + * [callback function](@ref IotMqttCallbackInfo_t.function) is registered for + * a particular topic filter. + * + * To modify an already-registered subscription callback, call this function with + * a new `pSubscriptionList`. Any topic filters in `pSubscriptionList` that already + * have a registered callback will be replaced with the new values in `pSubscriptionList`. + * + * @attention QoS 2 subscriptions are currently unsupported. Only 0 or 1 are valid + * for subscription QoS. + * + * @param[in] mqttConnection The MQTT connection to use for the subscription. + * @param[in] pSubscriptionList Pointer to the first element in the array of + * subscriptions. + * @param[in] subscriptionCount The number of elements in pSubscriptionList. + * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags. + * @param[in] pCallbackInfo Asynchronous notification of this function's completion. + * @param[out] pSubscribeOperation Set to a handle by which this operation may be + * referenced after this function returns. This reference is invalidated once + * the subscription operation completes. + * + * @return This function will return #IOT_MQTT_STATUS_PENDING upon success. + * @return Upon completion of the subscription (either through an + * #IotMqttCallbackInfo_t or @ref mqtt_function_wait), the status will be one of: + * - #IOT_MQTT_SUCCESS + * - #IOT_MQTT_NETWORK_ERROR + * - #IOT_MQTT_SCHEDULING_ERROR + * - #IOT_MQTT_BAD_RESPONSE + * - #IOT_MQTT_SERVER_REFUSED + * @return If this function fails before queuing a subscribe operation, it will return + * one of: + * - #IOT_MQTT_BAD_PARAMETER + * - #IOT_MQTT_NO_MEMORY + * + * @see @ref mqtt_function_timedsubscribe for a blocking variant of this function. + * @see @ref mqtt_function_unsubscribe for the function that removes subscriptions. + * + * Example + * @code{c} + * #define NUMBER_OF_SUBSCRIPTIONS ... + * + * // Subscription callback function. + * void subscriptionCallback( void * pArgument, IotMqttCallbackParam_t * pPublish ); + * + * // An initialized and connected MQTT connection. + * IotMqttConnection_t mqttConnection; + * + * // Subscription information. + * pSubscriptions[ NUMBER_OF_SUBSCRIPTIONS ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; + * IotMqttOperation_t lastOperation = IOT_MQTT_OPERATION_INITIALIZER; + * + * // Set the subscription information. + * for( int i = 0; i < NUMBER_OF_SUBSCRIPTIONS; i++ ) + * { + * pSubscriptions[ i ].qos = IOT_MQTT_QOS_1; + * pSubscriptions[ i ].pTopicFilter = "some/topic/filter"; + * pSubscriptions[ i ].topicLength = ( uint16_t ) strlen( pSubscriptions[ i ].pTopicFilter ); + * pSubscriptions[ i ].callback.function = subscriptionCallback; + * } + * + * IotMqttError_t result = IotMqtt_Subscribe( mqttConnection, + * pSubscriptions, + * NUMBER_OF_SUBSCRIPTIONS, + * IOT_MQTT_FLAG_WAITABLE, + * NULL, + * &lastOperation ); + * + * // Subscribe returns IOT_MQTT_STATUS_PENDING when successful. Wait up to + * // 5 seconds for the operation to complete. + * if( result == IOT_MQTT_STATUS_PENDING ) + * { + * result = IotMqtt_Wait( subscriptionRef, 5000 ); + * } + * + * // Check that the subscriptions were successful. + * if( result == IOT_MQTT_SUCCESS ) + * { + * // Wait for messages on the subscription topic filters... + * + * // Unsubscribe once the subscriptions are no longer needed. + * result = IotMqtt_Unsubscribe( mqttConnection, + * pSubscriptions, + * NUMBER_OF_SUBSCRIPTIONS, + * IOT_MQTT_FLAG_WAITABLE, + * NULL, + * &lastOperation ); + * + * // UNSUBSCRIBE returns IOT_MQTT_STATUS_PENDING when successful. + * // Wait up to 5 seconds for the operation to complete. + * if( result == IOT_MQTT_STATUS_PENDING ) + * { + * result = IotMqtt_Wait( lastOperation, 5000 ); + * } + * } + * // Check which subscriptions were rejected by the server. + * else if( result == IOT_MQTT_SERVER_REFUSED ) + * { + * for( int i = 0; i < NUMBER_OF_SUBSCRIPTIONS; i++ ) + * { + * if( IotMqtt_IsSubscribed( mqttConnection, + * pSubscriptions[ i ].pTopicFilter, + * pSubscriptions[ i ].topicFilterLength, + * NULL ) == false ) + * { + * // This subscription was rejected. + * } + * } + * } + * @endcode + */ +/* @[declare_mqtt_subscribe] */ +IotMqttError_t IotMqtt_Subscribe( IotMqttConnection_t mqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + const IotMqttCallbackInfo_t * pCallbackInfo, + IotMqttOperation_t * pSubscribeOperation ); +/* @[declare_mqtt_subscribe] */ + +/** + * @brief Subscribes to the given array of topic filters with a timeout. + * + * This function transmits an MQTT SUBSCRIBE packet to the server, then waits for + * a server response to the packet. Internally, this function is a call to @ref + * mqtt_function_subscribe followed by @ref mqtt_function_wait. See @ref + * mqtt_function_subscribe for more information about the MQTT SUBSCRIBE operation. + * + * @attention QoS 2 subscriptions are currently unsupported. Only 0 or 1 are valid + * for subscription QoS. + * + * @param[in] mqttConnection The MQTT connection to use for the subscription. + * @param[in] pSubscriptionList Pointer to the first element in the array of + * subscriptions. + * @param[in] subscriptionCount The number of elements in pSubscriptionList. + * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags. + * Currently, flags are ignored by this function; this parameter is for + * future-compatibility. + * @param[in] timeoutMs If the MQTT server does not acknowledge the subscriptions within + * this timeout, this function returns #IOT_MQTT_TIMEOUT. + * + * @return One of the following: + * - #IOT_MQTT_SUCCESS + * - #IOT_MQTT_BAD_PARAMETER + * - #IOT_MQTT_NO_MEMORY + * - #IOT_MQTT_NETWORK_ERROR + * - #IOT_MQTT_SCHEDULING_ERROR + * - #IOT_MQTT_BAD_RESPONSE + * - #IOT_MQTT_TIMEOUT + * - #IOT_MQTT_SERVER_REFUSED + */ +/* @[declare_mqtt_timedsubscribe] */ +IotMqttError_t IotMqtt_TimedSubscribe( IotMqttConnection_t mqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + uint32_t timeoutMs ); +/* @[declare_mqtt_timedsubscribe] */ + +/** + * @brief Unsubscribes from the given array of topic filters and receive an asynchronous + * notification when the unsubscribe completes. + * + * This function transmits an MQTT UNSUBSCRIBE packet to the server. An UNSUBSCRIBE + * packet removes registered topic filters from the server. After unsubscribing, + * the server will no longer send messages on these topic filters to the client. + * + * Corresponding [subscription callback functions](@ref IotMqttCallbackInfo_t.function) + * are also removed from the MQTT connection. These subscription callback functions + * will be removed even if the MQTT UNSUBSCRIBE packet fails to send. + * + * @param[in] mqttConnection The MQTT connection used for the subscription. + * @param[in] pSubscriptionList Pointer to the first element in the array of + * subscriptions. + * @param[in] subscriptionCount The number of elements in pSubscriptionList. + * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags. + * @param[in] pCallbackInfo Asynchronous notification of this function's completion. + * @param[out] pUnsubscribeOperation Set to a handle by which this operation may be + * referenced after this function returns. This reference is invalidated once + * the unsubscribe operation completes. + * + * @return This function will return #IOT_MQTT_STATUS_PENDING upon success. + * @return Upon completion of the unsubscribe (either through an + * #IotMqttCallbackInfo_t or @ref mqtt_function_wait), the status will be one of: + * - #IOT_MQTT_SUCCESS + * - #IOT_MQTT_NETWORK_ERROR + * - #IOT_MQTT_SCHEDULING_ERROR + * - #IOT_MQTT_BAD_RESPONSE + * @return If this function fails before queuing an unsubscribe operation, it will return + * one of: + * - #IOT_MQTT_BAD_PARAMETER + * - #IOT_MQTT_NO_MEMORY + * + * @see @ref mqtt_function_timedsubscribe for a blocking variant of this function. + * @see @ref mqtt_function_subscribe for the function that adds subscriptions. + */ +/* @[declare_mqtt_unsubscribe] */ +IotMqttError_t IotMqtt_Unsubscribe( IotMqttConnection_t mqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + const IotMqttCallbackInfo_t * pCallbackInfo, + IotMqttOperation_t * pUnsubscribeOperation ); +/* @[declare_mqtt_unsubscribe] */ + +/** + * @brief Unsubscribes from a given array of topic filters with a timeout. + * + * This function transmits an MQTT UNSUBSCRIBE packet to the server, then waits + * for a server response to the packet. Internally, this function is a call to + * @ref mqtt_function_unsubscribe followed by @ref mqtt_function_wait. See @ref + * mqtt_function_unsubscribe for more information about the MQTT UNSUBSCRIBE + * operation. + * + * @param[in] mqttConnection The MQTT connection used for the subscription. + * @param[in] pSubscriptionList Pointer to the first element in the array of + * subscriptions. + * @param[in] subscriptionCount The number of elements in pSubscriptionList. + * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags. + * Currently, flags are ignored by this function; this parameter is for + * future-compatibility. + * @param[in] timeoutMs If the MQTT server does not acknowledge the UNSUBSCRIBE within + * this timeout, this function returns #IOT_MQTT_TIMEOUT. + * + * @return One of the following: + * - #IOT_MQTT_SUCCESS + * - #IOT_MQTT_BAD_PARAMETER + * - #IOT_MQTT_NO_MEMORY + * - #IOT_MQTT_NETWORK_ERROR + * - #IOT_MQTT_SCHEDULING_ERROR + * - #IOT_MQTT_BAD_RESPONSE + */ +/* @[declare_mqtt_timedunsubscribe] */ +IotMqttError_t IotMqtt_TimedUnsubscribe( IotMqttConnection_t mqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + uint32_t timeoutMs ); +/* @[declare_mqtt_timedunsubscribe] */ + +/** + * @brief Publishes a message to the given topic name and receive an asynchronous + * notification when the publish completes. + * + * This function transmits an MQTT PUBLISH packet to the server. A PUBLISH packet + * contains a payload and a topic name. Any clients with a subscription on a + * topic filter matching the PUBLISH topic name will receive a copy of the + * PUBLISH packet from the server. + * + * If a PUBLISH packet fails to reach the server and it is not a QoS 0 message, + * it will be retransmitted. See #IotMqttPublishInfo_t for a description + * of the retransmission strategy. + * + * @attention QoS 2 messages are currently unsupported. Only 0 or 1 are valid + * for message QoS. + * + * @param[in] mqttConnection The MQTT connection to use for the publish. + * @param[in] pPublishInfo MQTT publish parameters. + * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags. + * @param[in] pCallbackInfo Asynchronous notification of this function's completion. + * @param[out] pPublishOperation Set to a handle by which this operation may be + * referenced after this function returns. This reference is invalidated once + * the publish operation completes. + * + * @return This function will return #IOT_MQTT_STATUS_PENDING upon success for + * QoS 1 publishes. For a QoS 0 publish it returns #IOT_MQTT_SUCCESS upon + * success. + * @return Upon completion of a QoS 1 publish (either through an + * #IotMqttCallbackInfo_t or @ref mqtt_function_wait), the status will be one of: + * - #IOT_MQTT_SUCCESS + * - #IOT_MQTT_NETWORK_ERROR + * - #IOT_MQTT_SCHEDULING_ERROR + * - #IOT_MQTT_BAD_RESPONSE + * - #IOT_MQTT_RETRY_NO_RESPONSE (if [pPublishInfo->retryMs](@ref IotMqttPublishInfo_t.retryMs) + * and [pPublishInfo->retryLimit](@ref IotMqttPublishInfo_t.retryLimit) were set). + * @return If this function fails before queuing an publish operation (regardless + * of QoS), it will return one of: + * - #IOT_MQTT_BAD_PARAMETER + * - #IOT_MQTT_NO_MEMORY + * + * @note The parameters `pCallbackInfo` and `pPublishOperation` should only be used for QoS + * 1 publishes. For QoS 0, they should both be `NULL`. + * + * @see @ref mqtt_function_timedpublish for a blocking variant of this function. + * + * Example + * @code{c} + * // An initialized and connected MQTT connection. + * IotMqttConnection_t mqttConnection; + * + * // Publish information. + * IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; + * + * // Set the publish information. QoS 0 example (retain not used): + * publishInfo.qos = IOT_MQTT_QOS_0; + * publishInfo.pTopicName = "some/topic/name"; + * publishInfo.topicNameLength = 15; + * publishInfo.pPayload = "payload"; + * publishInfo.payloadLength = 8; + * + * // QoS 0 publish should return IOT_MQTT_SUCCESS upon success. + * IotMqttError_t qos0Result = IotMqtt_Publish( mqttConnection, + * &publishInfo, + * 0, + * NULL, + * NULL ); + * + * // QoS 1 with retry example (using same topic name and payload as QoS 0 example): + * IotMqttOperation_t qos1Operation = IOT_MQTT_OPERATION_INITIALIZER; + * publishInfo.qos = IOT_MQTT_QOS_1; + * publishInfo.retryMs = 1000; // Retry if no response is received in 1 second. + * publishInfo.retryLimit = 5; // Retry up to 5 times. + * + * // QoS 1 publish should return IOT_MQTT_STATUS_PENDING upon success. + * IotMqttError_t qos1Result = IotMqtt_Publish( mqttConnection, + * &publishInfo, + * IOT_MQTT_FLAG_WAITABLE, + * NULL, + * &qos1Operation ); + * + * // Wait up to 5 seconds for the publish to complete. + * if( qos1Result == IOT_MQTT_STATUS_PENDING ) + * { + * qos1Result = IotMqtt_Wait( qos1Operation, 5000 ); + * } + * @endcode + */ +/* @[declare_mqtt_publish] */ +IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, + const IotMqttPublishInfo_t * pPublishInfo, + uint32_t flags, + const IotMqttCallbackInfo_t * pCallbackInfo, + IotMqttOperation_t * pPublishOperation ); +/* @[declare_mqtt_publish] */ + +/** + * @brief Publish a message to the given topic name with a timeout. + * + * This function transmits an MQTT PUBLISH packet to the server, then waits for + * a server response to the packet. Internally, this function is a call to @ref + * mqtt_function_publish followed by @ref mqtt_function_wait. See @ref + * mqtt_function_publish for more information about the MQTT PUBLISH operation. + * + * @attention QoS 2 messages are currently unsupported. Only 0 or 1 are valid + * for message QoS. + * + * @param[in] mqttConnection The MQTT connection to use for the publish. + * @param[in] pPublishInfo MQTT publish parameters. + * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags. + * Currently, flags are ignored by this function; this parameter is for + * future-compatibility. + * @param[in] timeoutMs If the MQTT server does not acknowledge a QoS 1 PUBLISH + * within this timeout, this function returns #IOT_MQTT_TIMEOUT. This parameter + * is ignored for QoS 0 PUBLISH messages. + * + * @return One of the following: + * - #IOT_MQTT_SUCCESS + * - #IOT_MQTT_BAD_PARAMETER + * - #IOT_MQTT_NO_MEMORY + * - #IOT_MQTT_NETWORK_ERROR + * - #IOT_MQTT_SCHEDULING_ERROR + * - #IOT_MQTT_BAD_RESPONSE + * - #IOT_MQTT_RETRY_NO_RESPONSE (if [pPublishInfo->retryMs](@ref IotMqttPublishInfo_t.retryMs) + * and [pPublishInfo->retryLimit](@ref IotMqttPublishInfo_t.retryLimit) were set). + */ +/* @[declare_mqtt_timedpublish] */ +IotMqttError_t IotMqtt_TimedPublish( IotMqttConnection_t mqttConnection, + const IotMqttPublishInfo_t * pPublishInfo, + uint32_t flags, + uint32_t timeoutMs ); +/* @[declare_mqtt_timedpublish] */ + +/** + * @brief Waits for an operation to complete. + * + * This function blocks to wait for a [subscribe](@ref mqtt_function_subscribe), + * [unsubscribe](@ref mqtt_function_unsubscribe), or [publish] + * (@ref mqtt_function_publish) to complete. These operations are by default + * asynchronous; the function calls queue an operation for processing, and a + * callback is invoked once the operation is complete. + * + * To use this function, the flag #IOT_MQTT_FLAG_WAITABLE must have been + * set in the operation's function call. Additionally, this function must always + * be called with any waitable operation to clean up resources. + * + * Regardless of its return value, this function always clean up resources used + * by the waitable operation. This means `reference` is invalidated as soon as + * this function returns, even if it returns #IOT_MQTT_TIMEOUT or another error. + * + * @param[in] operation Reference to the operation to wait for. The flag + * #IOT_MQTT_FLAG_WAITABLE must have been set for this operation. + * @param[in] timeoutMs How long to wait before returning #IOT_MQTT_TIMEOUT. + * + * @return The return value of this function depends on the MQTT operation associated + * with `reference`. See #IotMqttError_t for possible return values. + * + * Example + * @code{c} + * // Operation reference and timeout. + * IotMqttOperation_t publishOperation = IOT_MQTT_OPERATION_INITIALIZER; + * uint32_t timeoutMs = 5000; // 5 seconds + * + * // MQTT operation to wait for. + * IotMqttError_t result = IotMqtt_Publish( mqttConnection, + * &publishInfo, + * IOT_MQTT_FLAG_WAITABLE, + * NULL, + * &publishOperation ); + * + * // Publish should have returned IOT_MQTT_STATUS_PENDING. The call to wait + * // returns once the result of the publish is available or the timeout expires. + * if( result == IOT_MQTT_STATUS_PENDING ) + * { + * result = IotMqtt_Wait( publishOperation, timeoutMs ); + * + * // After the call to wait, the result of the publish is known + * // (not IOT_MQTT_STATUS_PENDING). + * assert( result != IOT_MQTT_STATUS_PENDING ); + * } + * @endcode + */ +/* @[declare_mqtt_wait] */ +IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, + uint32_t timeoutMs ); +/* @[declare_mqtt_wait] */ + +/*-------------------------- MQTT helper functions --------------------------*/ + +/** + * @brief Returns a string that describes an #IotMqttError_t. + * + * Like the POSIX's `strerror`, this function returns a string describing a + * return code. In this case, the return code is an MQTT library error code, + * `status`. + * + * The string returned by this function MUST be treated as read-only: any + * attempt to modify its contents may result in a crash. Therefore, this function + * is limited to usage in logging. + * + * @param[in] status The status to describe. + * + * @return A read-only string that describes `status`. + * + * @warning The string returned by this function must never be modified. + */ +/* @[declare_mqtt_strerror] */ +const char * IotMqtt_strerror( IotMqttError_t status ); +/* @[declare_mqtt_strerror] */ + +/** + * @brief Returns a string that describes an #IotMqttOperationType_t. + * + * This function returns a string describing an MQTT operation type, `operation`. + * + * The string returned by this function MUST be treated as read-only: any + * attempt to modify its contents may result in a crash. Therefore, this function + * is limited to usage in logging. + * + * @param[in] operation The operation to describe. + * + * @return A read-only string that describes `operation`. + * + * @warning The string returned by this function must never be modified. + */ +/* @[declare_mqtt_operationtype] */ +const char * IotMqtt_OperationType( IotMqttOperationType_t operation ); +/* @[declare_mqtt_operationtype] */ + +/** + * @brief Check if an MQTT connection has a subscription for a topic filter. + * + * This function checks whether an MQTT connection `mqttConnection` has a + * subscription callback registered for a topic filter `pTopicFilter`. If a + * subscription callback is found, its details are copied into the output parameter + * `pCurrentSubscription`. This subscription callback will be invoked for incoming + * PUBLISH messages on `pTopicFilter`. + * + * The check for a matching subscription is only performed client-side; + * therefore, this function should not be relied upon for perfect accuracy. For + * example, this function may return an incorrect result if the MQTT server + * crashes and drops subscriptions without informing the client. + * + * Note that an MQTT connection's subscriptions might change between the time this + * function checks the subscription list and its caller tests the return value. + * This function certainly should not be used concurrently with any pending SUBSCRIBE + * or UNSUBSCRIBE operations. + * + * One suitable use of this function is to check which subscriptions were rejected + * if @ref mqtt_function_subscribe returns #IOT_MQTT_SERVER_REFUSED; that return + * code only means that at least one subscription was rejected. + * + * @param[in] mqttConnection The MQTT connection to check. + * @param[in] pTopicFilter The topic filter to check. + * @param[in] topicFilterLength Length of `pTopicFilter`. + * @param[out] pCurrentSubscription If a subscription is found, its details are + * copied here. This output parameter is only valid if this function returns `true`. + * Pass `NULL` to ignore. + * + * @return `true` if a subscription was found; `false` otherwise. + * + * @note The subscription QoS is not stored by the MQTT library; therefore, + * `pCurrentSubscription->qos` will always be set to #IOT_MQTT_QOS_0. + */ +/* @[declare_mqtt_issubscribed] */ +bool IotMqtt_IsSubscribed( IotMqttConnection_t mqttConnection, + const char * pTopicFilter, + uint16_t topicFilterLength, + IotMqttSubscription_t * pCurrentSubscription ); +/* @[declare_mqtt_issubscribed] */ + +#endif /* ifndef IOT_MQTT_H_ */ diff --git a/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/include/iot_mqtt_agent.h b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/include/iot_mqtt_agent.h new file mode 100644 index 000000000..fbaaeb7fb --- /dev/null +++ b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/include/iot_mqtt_agent.h @@ -0,0 +1,358 @@ +/* + * Amazon FreeRTOS MQTT V2.0.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +/** + * @file iot_mqtt_agent.h + * @brief MQTT Agent Interface. + */ + +#ifndef _AWS_MQTT_AGENT_H_ +#define _AWS_MQTT_AGENT_H_ + +/* FreeRTOS includes. */ +#include "FreeRTOS.h" + +/* MQTT lib includes. */ +#include "iot_mqtt_lib.h" + +/* Library initialization definition include */ +#include "iot_lib_init.h" + +/** + * @brief Opaque handle to represent an MQTT client. + * + * The MQTT library is capable of creating multiple MQTT clients, maximum number of which + * is controlled by mqttconfigMAX_BROKERS macro. Each client is identified by an opaque + * handle which is returned by the MQTT_AGENT_Create API call and later used in all + * the subsequent API calls. + */ +typedef void * MQTTAgentHandle_t; + +/** + * @brief Return codes. + * + * Each API returns a value of this type. + */ +typedef enum +{ + eMQTTAgentSuccess, /**< The operation was successful. */ + eMQTTAgentFailure, /**< The operation failed. */ + eMQTTAgentTimeout, /**< The operation timed out. */ + eMQTTAgentAPICalledFromCallback /**< The MQTT agent APIs must not be called from MQTT callbacks as callbacks run + * in the context of MQTT agent task and therefore can result in deadlock. This + * error code is returned if any MQTT agent API is invoked from any callback. */ +} MQTTAgentReturnCode_t; + +/** + * @brief Various events reported by the library in the callback. + * + * The user can register an optional callback with the MQTT library to + * get notified of various events including Publish messages received + * from the broker. This enum identifies the event received in the + * callback. + */ +typedef enum +{ + eMQTTAgentPublish, /**< A Publish message was received from the broker. */ + eMQTTAgentDisconnect /**< The connection to the broker got disconnected. */ +} MQTTAgentEvent_t; + +/** + * @brief Passed by the library in the callback to inform the user of various events. + * + * If the user has registered a callback to get notified of various events, a pointer + * to this structure is passed in the callback function. + * @see MQTTAgentEvent_t. + */ +typedef struct MQTTAgentCallbackParams +{ + MQTTAgentEvent_t xMQTTEvent; /**< Type of the event received. */ + /* This union is here for future support. */ + union + { + MQTTPublishData_t xPublishData; /**< Publish data. Meaningful only in case of eMQTTAgentPublish event. */ + } u; +} MQTTAgentCallbackParams_t; + +/** + * @brief Signature of the callback registered by the user to get notified of various events. + * + * The user can register an optional callback to get notified of various events. + * + * @param[in] pvUserData The user data as provided in the connect parameters while connecting. + * @param[in] pxCallbackParams The event and related data. + * + * @return The return value is ignored in all other cases except publish (i.e. eMQTTAgentPublish + * event): + * 1. If pdTRUE is returned - The ownership of the buffer passed in the callback (xBuffer + * in MQTTPublishData_t) lies with the user. + * 2. If pdFALSE is returned - The ownership of the buffer passed in the callback (xBuffer + * in MQTTPublishData_t) remains with the library and it is recycled as soon as + * the callback returns.
+ * The user should take the ownership of the buffer containing the received message from the + * broker by returning pdTRUE from the callback if the user wants to use the buffer after + * the callback is over. The user should return the buffer whenever done by calling the + * MQTT_AGENT_ReturnBuffer API. + * + * @see MQTTAgentCallbackParams_t. + */ +typedef BaseType_t ( * MQTTAgentCallback_t )( void * pvUserData, + const MQTTAgentCallbackParams_t * const pxCallbackParams ); + +/** + * @brief Flags for the MQTT agent connect params. + */ +#define mqttagentURL_IS_IP_ADDRESS 0x00000001 /**< Set this bit in xFlags if the provided URL is an IP address. */ +#define mqttagentREQUIRE_TLS 0x00000002 /**< Set this bit in xFlags to use TLS. */ +#define mqttagentUSE_AWS_IOT_ALPN_443 0x00000004 /**< Set this bit in xFlags to use AWS IoT support for MQTT over TLS port 443. */ + +/** + * @brief Parameters passed to the MQTT_AGENT_Connect API. + */ +typedef struct MQTTAgentConnectParams +{ + const char * pcURL; /**< The URL of the MQTT broker to connect to. */ + BaseType_t xFlags; /**< Flags to control the behavior of MQTT connect. */ + BaseType_t xURLIsIPAddress; /**< Deprecated. Set the mqttagentURL_IS_IP_ADDRESS bit in xFlags instead. */ + uint16_t usPort; /**< Port number at which MQTT broker is listening. This field is ignored if the mqttagentUSE_AWS_IOT_ALPN_443 flag is set. */ + const uint8_t * pucClientId; /**< Client Identifier of the MQTT client. It should be unique per broker. */ + uint16_t usClientIdLength; /**< The length of the client Id. */ + BaseType_t xSecuredConnection; /**< Deprecated. Set the mqttagentREQUIRE_TLS bit in xFlags instead. */ + void * pvUserData; /**< User data supplied back as it is in the callback. Can be NULL. */ + MQTTAgentCallback_t pxCallback; /**< Callback used to report various events. In addition to other events, this callback is invoked for the publish + * messages received on the topics for which the user has not registered any subscription callback. Can be NULL. */ + char * pcCertificate; /**< Certificate used for secure connection. Can be NULL. If it is NULL, the one specified in the aws_credential_keys.h is used. */ + uint32_t ulCertificateSize; /**< Size of certificate used for secure connection. */ +} MQTTAgentConnectParams_t; + +/** + * @brief Parameters passed to the MQTT_AGENT_Subscribe API. + */ +typedef struct MQTTAgentSubscribeParams +{ + const uint8_t * pucTopic; /**< The topic to subscribe to. This can be a topic filter containing wild cards as permitted by the MQTT protocol. */ + uint16_t usTopicLength; /**< The length of the topic. */ + MQTTQoS_t xQoS; /**< Requested Quality of Service. */ + #if ( mqttconfigENABLE_SUBSCRIPTION_MANAGEMENT == 1 ) + void * pvPublishCallbackContext; /**< Passed as it is in the publish callback. Can be NULL. */ + MQTTPublishCallback_t pxPublishCallback; /**< Callback function to be called whenever a publish message is received on this topic or on a topic which matches this + * topic filter. If a publish message is received on a topic which matches more than one topic filters, the order in which + * the callbacks are invoked is undefined. This can be NULL if the user does not want to register a topic specific callback, + * in which case the generic callback ( if registered during connect ) is invoked. */ + #endif /* mqttconfigENABLE_SUBSCRIPTION_MANAGEMENT */ +} MQTTAgentSubscribeParams_t; + +/** + * @brief Parameters passed to the MQTT_AGENT_Unsubscribe API. + */ +typedef struct MQTTAgentUnsubscribeParams +{ + const uint8_t * pucTopic; /**< The topic to unsubscribe from. */ + uint16_t usTopicLength; /**< The length of the topic. */ +} MQTTAgentUnsubscribeParams_t; + +/** + * @brief Parameters passed to the MQTT_AGENT_Publish API. + */ +typedef struct MQTTAgentPublishParams +{ + const uint8_t * pucTopic; /**< The topic string on which the message should be published. */ + uint16_t usTopicLength; /**< The length of the topic. */ + MQTTQoS_t xQoS; /**< Quality of Service (qos). */ + const void * pvData; /**< The data to publish. This data is copied into the MQTT buffers and therefore the user can free the buffer after the MQTT_AGENT_Publish call returns. */ + uint32_t ulDataLength; /**< Length of the data. */ +} MQTTAgentPublishParams_t; + +/** + * @brief MQTT library Init function. + * + * This function does general initialization and setup. It must be called once + * and only once before calling any other function. + * + * @return pdPASS if everything succeeds, pdFAIL otherwise. + */ +lib_initDECLARE_LIB_INIT( MQTT_AGENT_Init ); + +/** + * @brief Creates a new MQTT client. + * + * The MQTT library is capable of creating multiple MQTT clients, maximum number of which + * is controlled by mqttconfigMAX_BROKERS macro. If mqttconfigMAX_BROKERS clients are already + * in use, this function will fail immediately. Otherwise a new client is setup and the handle + * to the created client is returned in the pxMQTTHandle parameter which should be used in all + * the subsequent API calls. Note that the returned handled is only valid if the return value + * of the API is eMQTTAgentSuccess. + * + * @param[out] pxMQTTHandle Output parameter to return the opaque client handle. + * + * @return eMQTTAgentSuccess if a new client is successfully created, otherwise an error code + * explaining the reason of the failure is returned. + */ +MQTTAgentReturnCode_t MQTT_AGENT_Create( MQTTAgentHandle_t * const pxMQTTHandle ); + +/** + * @brief Deletes the already created MQTT client. + * + * This function just frees up the internal resources and does not disconnect. The user must + * call MQTT_AGENT_Disconnect API to make sure that the client is disconnected before + * deleting it. + * + * @param[in] xMQTTHandle The opaque handle as returned from MQTT_AGENT_Create. + * + * @return eMQTTAgentSuccess if the client is successfully deleted, otherwise an + * error code explaining the reason of the failure is returned. + */ +MQTTAgentReturnCode_t MQTT_AGENT_Delete( MQTTAgentHandle_t xMQTTHandle ); + +/** + * @brief Establishes a connection with the MQTT broker. + * + * @note This function alters the calling task's notification state and value. If xTimeoutTicks + * is short the calling task's notification state and value may be updated after MQTT_AGENT_Connect() + * has returned. + * + * @param[in] xMQTTHandle The opaque handle as returned from MQTT_AGENT_Create. + * @param[in] pxConnectParams Connect parameters. + * @param[in] xTimeoutTicks Maximum time in ticks after which the operation should fail. Use pdMS_TO_TICKS + * macro to convert milliseconds to ticks. + * + * @return eMQTTAgentSuccess if the connect operation succeeds, otherwise an error code explaining the + * reason of the failure is returned. + */ +MQTTAgentReturnCode_t MQTT_AGENT_Connect( MQTTAgentHandle_t xMQTTHandle, + const MQTTAgentConnectParams_t * const pxConnectParams, + TickType_t xTimeoutTicks ); + +/** + * @brief Disconnects the connection with the MQTT broker. + * + * @note This function alters the calling task's notification state and value. If xTimeoutTicks + * is short the calling task's notification state and value may be updated after MQTT_AGENT_Disconnect() + * has returned. + * + * @param[in] xMQTTHandle The opaque handle as returned from MQTT_AGENT_Create. + * @param[in] xTimeoutTicks Maximum time in ticks after which the operation should fail. Use pdMS_TO_TICKS + * macro to convert milliseconds to ticks. + * + * @return eMQTTAgentSuccess if the disconnect operation succeeds, otherwise an error code explaining + * the reason of the failure is returned. + */ +MQTTAgentReturnCode_t MQTT_AGENT_Disconnect( MQTTAgentHandle_t xMQTTHandle, + TickType_t xTimeoutTicks ); + +/** + * @brief Subscribes to a given topic. + * + * @note This function alters the calling task's notification state and value. If xTimeoutTicks + * is short the calling task's notification state and value may be updated after MQTT_AGENT_Subscribe() + * has returned. + * + * Whenever a publish message is received on a topic, the registered callbacks are invoked + * in the following order: + * * If we have an exact matching entry in the subscription manager, the corresponding + * callback is invoked. + * * Then the wild card topic filters are checked for match and the corresponding callbacks + * are invoked for the ones which match the topic. + * + * @note If a publish message is received on a topic which matches more than one topic + * filters, the order in which the registered callbacks are invoked is undefined. + * + * @warning If the user takes the ownership of the MQTT buffer by returning eMQTTTrue from the + * callback, no further callbacks are invoked. The user should make sure not to take the ownership + * of the MQTT buffer if they want all the callbacks to get invoked. For example: + * * Subscriptions: a/b/c, a/b/#, a/b/+ + * * Publish message received on topic: a/b/c --> First the callback corresponding to a/b/c + * subscription is invoked. Then the callbacks for topic filters a/b/# and a/b/+ are invoked + * in no particular order. If the user decides to take the ownership of the MQTT buffer in + * any of the callback by returning eMQTTTrue, no further callbacks are invoked. + * + * @param[in] xMQTTHandle The opaque handle as returned from MQTT_AGENT_Create. + * @param[in] pxSubscribeParams Subscribe parameters. + * @param[in] xTimeoutTicks Maximum time in ticks after which the operation should fail. Use pdMS_TO_TICKS + * macro to convert milliseconds to ticks. + * + * @return eMQTTAgentSuccess if the subscribe operation succeeds, otherwise an error code explaining + * the reason of the failure is returned. + */ +MQTTAgentReturnCode_t MQTT_AGENT_Subscribe( MQTTAgentHandle_t xMQTTHandle, + const MQTTAgentSubscribeParams_t * const pxSubscribeParams, + TickType_t xTimeoutTicks ); + +/** + * @brief Unsubscribes from a given topic. + * + * @note This function alters the calling task's notification state and value. If xTimeoutTicks + * is short the calling task's notification state and value may be updated after MQTT_AGENT_Unsubscribe() + * has returned. + * + * @param[in] xMQTTHandle The opaque handle as returned from MQTT_AGENT_Create. + * @param[in] pxUnsubscribeParams Unsubscribe parameters. + * @param[in] xTimeoutTicks Maximum time in ticks after which the operation should fail. Use pdMS_TO_TICKS + * macro to convert milliseconds to ticks. + * + * @return eMQTTAgentSuccess if the unsubscribe operation succeeds, otherwise an error code explaining + * the reason of the failure is returned. + */ +MQTTAgentReturnCode_t MQTT_AGENT_Unsubscribe( MQTTAgentHandle_t xMQTTHandle, + const MQTTAgentUnsubscribeParams_t * const pxUnsubscribeParams, + TickType_t xTimeoutTicks ); + +/** + * @brief Publishes a message to a given topic. + * + * @note This function alters the calling task's notification state and value. If xTimeoutTicks + * is short the calling task's notification state and value may be updated after MQTT_AGENT_Publish() + * has returned. + * + * @param[in] xMQTTHandle The opaque handle as returned from MQTT_AGENT_Create. + * @param[in] pxPublishParams Publish parameters. + * @param[in] xTimeoutTicks Maximum time in ticks after which the operation should fail. Use pdMS_TO_TICKS + * macro to convert milliseconds to ticks. + * + * @return eMQTTAgentSuccess if the publish operation succeeds, otherwise an error code explaining + * the reason of the failure is returned. + */ +MQTTAgentReturnCode_t MQTT_AGENT_Publish( MQTTAgentHandle_t xMQTTHandle, + const MQTTAgentPublishParams_t * const pxPublishParams, + TickType_t xTimeoutTicks ); + +/** + * @brief Returns the buffer provided in the publish callback. + * + * When a publish message is received from the broker, the buffer containing the message + * is returned in the user supplied callback (xBuffer in MQTTPublishData_t) and the user + * can take the ownership by returning pdTRUE from the callback. The user should later + * return the buffer whenever done by calling the MQTT_AGENT_ReturnBuffer API. + * + * @param[in] xMQTTHandle The opaque handle as returned from MQTT_AGENT_Create. + * @param[in] xBufferHandle The buffer to return. + * + * @return eMQTTAgentSuccess if the return buffer operation succeeds, otherwise an error + * code explaining the reason of the failure is returned. + */ +MQTTAgentReturnCode_t MQTT_AGENT_ReturnBuffer( MQTTAgentHandle_t xMQTTHandle, + MQTTBufferHandle_t xBufferHandle ); + +#endif /* _AWS_MQTT_AGENT_H_ */ diff --git a/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/include/iot_mqtt_agent_config_defaults.h b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/include/iot_mqtt_agent_config_defaults.h new file mode 100644 index 000000000..1ac77800d --- /dev/null +++ b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/include/iot_mqtt_agent_config_defaults.h @@ -0,0 +1,180 @@ +/* + * Amazon FreeRTOS MQTT V2.0.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +/** + * @file iot_mqtt_agent_config_defaults.h + * @brief MQTT agent default config options. + * + * Ensures that the config options for MQTT agent are set to sensible + * default values if the user does not provide one. + */ + +#ifndef _AWS_MQTT_AGENT_CONFIG_DEFAULTS_H_ +#define _AWS_MQTT_AGENT_CONFIG_DEFAULTS_H_ + +/* FreeRTOS includes. */ +#include "FreeRTOS.h" +#include "task.h" + +/** + * @brief Controls whether or not to report usage metrics to the + * AWS IoT broker. + * + * If mqttconfigENABLE_METRICS is set to 1, a string containing + * metric information will be included in the "username" field of + * the MQTT connect messages. + */ +#ifndef mqttconfigENABLE_METRICS + #define mqttconfigENABLE_METRICS ( 1 ) +#endif + +/** + * @brief The maximum time interval in seconds allowed to elapse between 2 consecutive + * control packets. + */ +#ifndef mqttconfigKEEP_ALIVE_INTERVAL_SECONDS + #define mqttconfigKEEP_ALIVE_INTERVAL_SECONDS ( 1200 ) +#endif + +/** + * @brief Defines the frequency at which the client should send Keep Alive messages. + * + * Even though the maximum time allowed between 2 consecutive control packets + * is defined by the mqttconfigKEEP_ALIVE_INTERVAL_SECONDS macro, the user + * can and should send Keep Alive messages at a slightly faster rate to ensure + * that the connection is not closed by the server because of network delays. + * This macro defines the interval of inactivity after which a keep alive messages + * is sent. + */ +#ifndef mqttconfigKEEP_ALIVE_ACTUAL_INTERVAL_TICKS + #define mqttconfigKEEP_ALIVE_ACTUAL_INTERVAL_TICKS ( 5000 ) +#endif + +/** + * @brief The maximum interval in ticks to wait for PINGRESP. + * + * If PINGRESP is not received within this much time after sending PINGREQ, + * the client assumes that the PINGREQ timed out. + */ +#ifndef mqttconfigKEEP_ALIVE_TIMEOUT_TICKS + #define mqttconfigKEEP_ALIVE_TIMEOUT_TICKS ( 1000 ) +#endif + +/** + * @brief The maximum time in ticks for which the MQTT task is permitted to block. + * + * The MQTT task blocks until the user initiates any action or until it receives + * any data from the broker. This macro controls the maximum time the MQTT task can + * block. It should be set to a small number for the platforms which do not have any + * mechanism to wake up the MQTT task whenever data is received on a connected socket. + * This ensures that the MQTT task keeps waking up frequently and processes the publish + * messages received from the broker, if any. + * + * If the platform's secure_sockets layer supports SOCKETS_SO_WAKEUP_CALLBACK i.e. + * the MQTT task can wake up whenever data is received on a connected socket, this + * value should be set to maximum value: + * #define #define mqttconfigMQTT_TASK_MAX_BLOCK_TICKS ( ~( ( uint32_t ) 0 ) ) + * + * If the platform's secure_sockets layer does not support SOCKETS_SO_WAKEUP_CALLBACK + * i.e. the MQTT task cannot wake up whenever data is received on a connected socket, + * this value should be set to a small number: + * #define mqttconfigMQTT_TASK_MAX_BLOCK_TICKS ( 100 ) + */ +#ifndef mqttconfigMQTT_TASK_MAX_BLOCK_TICKS + #error "mqttconfigMQTT_TASK_MAX_BLOCK_TICKS must be defined in iot_mqtt_agent_config.h." +#endif + +/** + * @defgroup MQTTTask MQTT task configuration parameters. + */ +/** @{ */ +#ifndef mqttconfigMQTT_TASK_STACK_DEPTH + #define mqttconfigMQTT_TASK_STACK_DEPTH ( configMINIMAL_STACK_SIZE * 4 ) +#endif + +#ifndef mqttconfigMQTT_TASK_PRIORITY + #define mqttconfigMQTT_TASK_PRIORITY ( tskIDLE_PRIORITY ) +#endif +/** @} */ + +/** + * @brief Maximum number of MQTT clients that can exist simultaneously. + */ +#ifndef mqttconfigMAX_BROKERS + #define mqttconfigMAX_BROKERS ( 1 ) +#endif + +/** + * @brief Maximum number of parallel operations per client. + */ +#ifndef mqttconfigMAX_PARALLEL_OPS + #define mqttconfigMAX_PARALLEL_OPS ( 5 ) +#endif + +/** + * @brief Time in milliseconds after which the TCP send operation should timeout. + */ +#ifndef mqttconfigTCP_SEND_TIMEOUT_MS + #define mqttconfigTCP_SEND_TIMEOUT_MS ( 2000 ) +#endif + +/** + * @brief Length of the buffer used to receive data. + */ +#ifndef mqttconfigRX_BUFFER_SIZE + #define mqttconfigRX_BUFFER_SIZE ( 1024 ) +#endif + +/** + * @defgroup BufferPoolInterface The functions used by the MQTT client to get and return buffers. + * + * The MQTT client needs buffers for both transmitting and receiving messages. + * Whenever it needs a buffer, it invokes mqttconfigGET_FREE_BUFFER_FXN function to get + * a buffer and after it is done it invokes mqttconfigRETURN_BUFFER_FXN to return the + * buffer. By default, BUFFERPOOL_GetFreeBuffer and BUFFERPOOL_ReturnBuffer functions are + * used to get and return buffers from the central buffer pool. The user can change the + * buffer management functions for MQTT client by defining mqttconfigGET_FREE_BUFFER_FXN + * and mqttconfigRETURN_BUFFER_FXN macros. The user should implement the two functions + * having signatures same as BUFFERPOOL_GetFreeBuffer and BUFFERPOOL_ReturnBuffer and then + * define the macros in BufferPoolConfig.h: + * @code + * uint8_t* UserDefined_GetFreeBuffer( uint32_t *pulBufferLength ); + * void UserDefined_ReturnBuffer( uint8_t * const pucBuffer ); + * + * #define mqttconfigGET_FREE_BUFFER_FXN UserDefined_GetFreeBuffer + * #define mqttconfigRETURN_BUFFER_FXN UserDefined_ReturnBuffer + * @endcode + */ +/** @{ */ +#ifndef mqttconfigGET_FREE_BUFFER_FXN + #define mqttconfigGET_FREE_BUFFER_FXN BUFFERPOOL_GetFreeBuffer +#endif + +#ifndef mqttconfigRETURN_BUFFER_FXN + #define mqttconfigRETURN_BUFFER_FXN BUFFERPOOL_ReturnBuffer +#endif +/** @} */ + +#endif /* _AWS_MQTT_AGENT_CONFIG_DEFAULTS_H_ */ diff --git a/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/include/iot_mqtt_config_defaults.h b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/include/iot_mqtt_config_defaults.h new file mode 100644 index 000000000..08bcd41d4 --- /dev/null +++ b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/include/iot_mqtt_config_defaults.h @@ -0,0 +1,115 @@ +/* + * Amazon FreeRTOS MQTT V2.0.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +/** + * @file iot_mqtt_config_defaults.h + * @brief MQTT default config options. + * + * Ensures that the config options for MQTT are set to sensible default + * values if the user does not provide one. + */ + +#ifndef _AWS_MQTT_CONFIG_DEFAULTS_H_ +#define _AWS_MQTT_CONFIG_DEFAULTS_H_ + +/** + * @brief Enable subscription management. + * + * Subscription management allows the user to register per subscription + * callback. + */ +#ifndef mqttconfigENABLE_SUBSCRIPTION_MANAGEMENT + #define mqttconfigENABLE_SUBSCRIPTION_MANAGEMENT ( 1 ) +#endif + +/** + * @brief Maximum length of the topic which can be stored in subscription + * manager. + * + * If the user has enabled subscription management (by defining the macro + * mqttconfigENABLE_SUBSCRIPTION_MANAGEMENT to 1), then this macro must be defined + * to accommodate the maximum length topic which the user is going to subscribe. + * The subscribe operation will fail if the user tries to subscribe to a topic + * of length more than the maximum specified here. + */ +#ifndef mqttconfigSUBSCRIPTION_MANAGER_MAX_TOPIC_LENGTH + #define mqttconfigSUBSCRIPTION_MANAGER_MAX_TOPIC_LENGTH ( 128 ) +#endif + +/** + * @brief Maximum number of subscriptions which can be stored in subscription + * manager. + * + * If the user has enabled subscription management (by defining the macro + * mqttconfigENABLE_SUBSCRIPTION_MANAGEMENT to 1), then this macro must be defined + * to the maximum number of topics which the user is going to subscribe + * simultaneously. The subscribe operation will fail is the user tries to + * subscribe to more topics than the maximum specified here. + */ +#ifndef mqttconfigSUBSCRIPTION_MANAGER_MAX_SUBSCRIPTIONS + #define mqttconfigSUBSCRIPTION_MANAGER_MAX_SUBSCRIPTIONS ( 8 ) +#endif + +/** + * @brief Define mqttconfigASSERT to enable asserts. + * + * mqttconfigASSERT should be defined to match the semantics of standard + * C assert() macro i.e. an assertion should trigger if the parameter + * passed is zero. If the standard C assert is available, the user might + * do the following: + * @code + * #define mqttconfigASSERT( x ) assert( x ) + * @endcode + * + * Otherwise, a user can choose to implement a function which should be + * called when an assertion triggers and then define the mqttconfigASSERT + * to that function: + * @code + * extern void vAssertCalled( const char *pcFile, uint32_t ulLine ); + * #define mqttconfigASSERT( x ) if( ( x ) == 0 ) vAssertCalled( __FILE__, __LINE__ ) + * @endcode + */ +#ifndef mqttconfigASSERT + #define mqttconfigASSERT( x ) +#endif + +/** + * @brief Define mqttconfigENABLE_DEBUG_LOGS macro to 1 for enabling debug logs. + * + * If you choose to enable debug logs, the following function must be implemented + * which is called to print logs: + * @code + * void vLoggingPrintf( const char *pcFormatString, ... ); + * @endcode + */ +#if ( mqttconfigENABLE_DEBUG_LOGS == 1 ) + extern void vLoggingPrintf( const char * pcFormatString, + ... ); + #define mqttconfigDEBUG_LOG( x ) vLoggingPrintf x +#else + #define mqttconfigDEBUG_LOG( x ) +#endif + +#endif /* _AWS_MQTT_CONFIG_DEFAULTS_H_ */ diff --git a/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/include/iot_mqtt_lib.h b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/include/iot_mqtt_lib.h new file mode 100644 index 000000000..ae3e2638e --- /dev/null +++ b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/include/iot_mqtt_lib.h @@ -0,0 +1,113 @@ +/* + * Amazon FreeRTOS MQTT V2.0.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +/** + * @file iot_mqtt_lib.h + * @brief MQTT Core Library interface. + */ + +#ifndef _AWS_MQTT_LIB_H_ +#define _AWS_MQTT_LIB_H_ + +/* This ifndef enables the core MQTT library to be used without + * providing MQTTConfig.h. All the config values in this case are + * taken from MQTTConfigDefaults.h. */ +#ifndef mqttDO_NOT_USE_CUSTOM_CONFIG + #include "aws_mqtt_config.h" +#endif +#include "iot_mqtt_config_defaults.h" + +#include "iot_doubly_linked_list.h" + + /** + * @brief Opaque handle to represent an MQTT buffer. + */ +typedef void * MQTTBufferHandle_t; + +/** + * @brief Boolean type. + */ +typedef enum +{ + eMQTTFalse = 0, /**< Boolean False. */ + eMQTTTrue = 1 /**< Boolean True. */ +} MQTTBool_t; + +/** + * @brief Quality of Service (qos). + */ +typedef enum +{ + eMQTTQoS0 = 0, /**< Quality of Service 0 - Fire and Forget. No ACK. */ + eMQTTQoS1 = 1, /**< Quality of Service 1 - Wait till ACK or Timeout. */ + eMQTTQoS2 = 2 /**< Quality of Service 2 - Not supported. */ +} MQTTQoS_t; + +/** + * @brief The data sent by the MQTT library in the user supplied callback + * when a publish message from the broker is received. + */ +typedef struct MQTTPublishData +{ + MQTTQoS_t xQos; /**< Quality of Service (qos). */ + const uint8_t * pucTopic; /**< The topic on which the message is received. */ + uint16_t usTopicLength; /**< Length of the topic. */ + const void * pvData; /**< The received message. */ + uint32_t ulDataLength; /**< Length of the message. */ + MQTTBufferHandle_t xBuffer; /**< The buffer containing the whole MQTT message. Both pcTopic and pvData are pointers to the locations in this buffer. */ +} MQTTPublishData_t; + +/** + * @brief Signature of the user supplied topic specific publish callback which gets called + * whenever a publish message is received on the topic this callback is registered for. + * + * The user can choose to register this optional topic specific callback while subscribing to + * a topic. Whenever a publish message is received on the topic, this callback is invoked. If + * the user chooses not to enable subscription management or chooses not to register a topic + * specific callback, the generic callback supplied during Init is invoked. + * + * @param[in] pvPublishCallbackContext The callback context as supplied by the user in the + * subscribe parameters. + * @param[in] pxPublishData The publish data. + * + * @return The return value is interpreted as follows: + * 1. If eMQTTTrue is returned - the ownership of the buffer passed in the callback (xBuffer + * in MQTTPublishData_t) lies with the user. + * 2. If eMQTTFalse is returned - the ownership of the buffer passed in the callback (xBuffer + * in MQTTPublishData_t) remains with the library and it is recycled as soon as the callback + * returns.
+ * The user should take the ownership of the buffer containing the received message from the + * broker by returning eMQTTTrue from the callback if the user wants to use the buffer after + * the callback is over. The user should return the buffer whenever done by calling the + * MQTT_ReturnBuffer API. + */ +#if ( mqttconfigENABLE_SUBSCRIPTION_MANAGEMENT == 1 ) + + typedef MQTTBool_t ( * MQTTPublishCallback_t )( void * pvPublishCallbackContext, + const MQTTPublishData_t * const pxPublishData ); + +#endif /* mqttconfigENABLE_SUBSCRIPTION_MANAGEMENT */ + +#endif /* _AWS_MQTT_LIB_H_ */ diff --git a/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/include/types/iot_mqtt_types.h b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/include/types/iot_mqtt_types.h new file mode 100644 index 000000000..c4bea3105 --- /dev/null +++ b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/include/types/iot_mqtt_types.h @@ -0,0 +1,1087 @@ +/* + * Amazon FreeRTOS MQTT V2.0.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +/** + * @file iot_mqtt_types.h + * @brief Types of the MQTT library. + */ + +#ifndef IOT_MQTT_TYPES_H_ +#define IOT_MQTT_TYPES_H_ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Standard includes. */ +#include +#include +#include + +/* Type includes. */ +#include "types/iot_platform_types.h" +#include "types/iot_taskpool_types.h" + +/* Platform network include. */ +#include "platform/iot_network.h" + +/*---------------------------- MQTT handle types ----------------------------*/ + +/** + * @handles{mqtt,MQTT library} + */ + +/** + * @ingroup mqtt_datatypes_handles + * @brief Opaque handle of an MQTT connection. + * + * This type identifies an MQTT connection, which is valid after a successful call + * to @ref mqtt_function_connect. A variable of this type is passed as the first + * argument to [MQTT library functions](@ref mqtt_functions) to identify which + * connection that function acts on. + * + * A call to @ref mqtt_function_disconnect makes a connection handle invalid. Once + * @ref mqtt_function_disconnect returns, the connection handle should no longer + * be used. + * + * @initializer{IotMqttConnection_t,IOT_MQTT_CONNECTION_INITIALIZER} + */ +typedef struct _mqttConnection * IotMqttConnection_t; + +/** + * @ingroup mqtt_datatypes_handles + * @brief Opaque handle that references an in-progress MQTT operation. + * + * Set as an output parameter of @ref mqtt_function_publish, @ref mqtt_function_subscribe, + * and @ref mqtt_function_unsubscribe. These functions queue an MQTT operation; the result + * of the operation is unknown until a response from the MQTT server is received. Therefore, + * this handle serves as a reference to MQTT operations awaiting MQTT server response. + * + * This reference will be valid from the successful return of @ref mqtt_function_publish, + * @ref mqtt_function_subscribe, or @ref mqtt_function_unsubscribe. The reference becomes + * invalid once the [completion callback](@ref IotMqttCallbackInfo_t) is invoked, or + * @ref mqtt_function_wait returns. + * + * @initializer{IotMqttOperation_t,IOT_MQTT_OPERATION_INITIALIZER} + * + * @see @ref mqtt_function_wait and #IOT_MQTT_FLAG_WAITABLE for waiting on a reference. + * #IotMqttCallbackInfo_t and #IotMqttCallbackParam_t for an asynchronous notification + * of completion. + */ +typedef struct _mqttOperation * IotMqttOperation_t; + +/*-------------------------- MQTT enumerated types --------------------------*/ + +/** + * @enums{mqtt,MQTT library} + */ + +/** + * @ingroup mqtt_datatypes_enums + * @brief Return codes of [MQTT functions](@ref mqtt_functions). + * + * The function @ref mqtt_function_strerror can be used to get a return code's + * description. + */ +typedef enum IotMqttError +{ + /** + * @brief MQTT operation completed successfully. + * + * Functions that may return this value: + * - @ref mqtt_function_connect + * - @ref mqtt_function_publish with QoS 0 parameter + * - @ref mqtt_function_wait + * - @ref mqtt_function_timedsubscribe + * - @ref mqtt_function_timedunsubscribe + * - @ref mqtt_function_timedpublish + * + * Will also be the value of an operation completion callback's + * #IotMqttCallbackParam_t.result when successful. + */ + IOT_MQTT_SUCCESS = 0, + + /** + * @brief MQTT operation queued, awaiting result. + * + * Functions that may return this value: + * - @ref mqtt_function_subscribe + * - @ref mqtt_function_unsubscribe + * - @ref mqtt_function_publish with QoS 1 parameter + */ + IOT_MQTT_STATUS_PENDING, + + /** + * @brief Initialization failed. + * + * Functions that may return this value: + * - @ref mqtt_function_init + */ + IOT_MQTT_INIT_FAILED, + + /** + * @brief At least one parameter is invalid. + * + * Functions that may return this value: + * - @ref mqtt_function_connect + * - @ref mqtt_function_subscribe and @ref mqtt_function_timedsubscribe + * - @ref mqtt_function_unsubscribe and @ref mqtt_function_timedunsubscribe + * - @ref mqtt_function_publish and @ref mqtt_function_timedpublish + * - @ref mqtt_function_wait + */ + IOT_MQTT_BAD_PARAMETER, + + /** + * @brief MQTT operation failed because of memory allocation failure. + * + * Functions that may return this value: + * - @ref mqtt_function_connect + * - @ref mqtt_function_subscribe and @ref mqtt_function_timedsubscribe + * - @ref mqtt_function_unsubscribe and @ref mqtt_function_timedunsubscribe + * - @ref mqtt_function_publish and @ref mqtt_function_timedpublish + */ + IOT_MQTT_NO_MEMORY, + + /** + * @brief MQTT operation failed because the network was unusable. + * + * This return value may indicate that the network is disconnected. + * + * Functions that may return this value: + * - @ref mqtt_function_connect + * - @ref mqtt_function_wait + * - @ref mqtt_function_timedsubscribe + * - @ref mqtt_function_timedunsubscribe + * - @ref mqtt_function_timedpublish + * + * May also be the value of an operation completion callback's + * #IotMqttCallbackParam_t.result. + */ + IOT_MQTT_NETWORK_ERROR, + + /** + * @brief MQTT operation could not be scheduled, i.e. enqueued for sending. + * + * Functions that may return this value: + * - @ref mqtt_function_connect + * - @ref mqtt_function_subscribe and @ref mqtt_function_timedsubscribe + * - @ref mqtt_function_unsubscribe and @ref mqtt_function_timedunsubscribe + * - @ref mqtt_function_publish and @ref mqtt_function_timedpublish + */ + IOT_MQTT_SCHEDULING_ERROR, + + /** + * @brief MQTT response packet received from the network is malformed. + * + * Functions that may return this value: + * - @ref mqtt_function_connect + * - @ref mqtt_function_wait + * - @ref mqtt_function_timedsubscribe + * - @ref mqtt_function_timedunsubscribe + * - @ref mqtt_function_timedpublish + * + * May also be the value of an operation completion callback's + * #IotMqttCallbackParam_t.result. + * + * @note If this value is received, the network connection has been closed. + */ + IOT_MQTT_BAD_RESPONSE, + + /** + * @brief A blocking MQTT operation timed out. + * + * Functions that may return this value: + * - @ref mqtt_function_connect + * - @ref mqtt_function_wait + * - @ref mqtt_function_timedsubscribe + * - @ref mqtt_function_timedunsubscribe + * - @ref mqtt_function_timedpublish + */ + IOT_MQTT_TIMEOUT, + + /** + * @brief A CONNECT or at least one subscription was refused by the server. + * + * Functions that may return this value: + * - @ref mqtt_function_connect + * - @ref mqtt_function_wait, but only when its #IotMqttOperation_t parameter + * is associated with a SUBSCRIBE operation. + * - @ref mqtt_function_timedsubscribe + * + * May also be the value of an operation completion callback's + * #IotMqttCallbackParam_t.result for a SUBSCRIBE. + * + * @note If this value is returned and multiple subscriptions were passed to + * @ref mqtt_function_subscribe (or @ref mqtt_function_timedsubscribe), it's + * still possible that some of the subscriptions succeeded. This value only + * signifies that AT LEAST ONE subscription was rejected. The function @ref + * mqtt_function_issubscribed can be used to determine which subscriptions + * were accepted or rejected. + */ + IOT_MQTT_SERVER_REFUSED, + + /** + * @brief A QoS 1 PUBLISH received no response and [the retry limit] + * (#IotMqttPublishInfo_t.retryLimit) was reached. + * + * Functions that may return this value: + * - @ref mqtt_function_wait, but only when its #IotMqttOperation_t parameter + * is associated with a QoS 1 PUBLISH operation + * - @ref mqtt_function_timedpublish + * + * May also be the value of an operation completion callback's + * #IotMqttCallbackParam_t.result for a QoS 1 PUBLISH. + */ + IOT_MQTT_RETRY_NO_RESPONSE +} IotMqttError_t; + +/** + * @ingroup mqtt_datatypes_enums + * @brief Types of MQTT operations. + * + * The function @ref mqtt_function_operationtype can be used to get an operation + * type's description. + */ +typedef enum IotMqttOperationType +{ + IOT_MQTT_CONNECT, /**< Client-to-server CONNECT. */ + IOT_MQTT_PUBLISH_TO_SERVER, /**< Client-to-server PUBLISH. */ + IOT_MQTT_PUBACK, /**< Client-to-server PUBACK. */ + IOT_MQTT_SUBSCRIBE, /**< Client-to-server SUBSCRIBE. */ + IOT_MQTT_UNSUBSCRIBE, /**< Client-to-server UNSUBSCRIBE. */ + IOT_MQTT_PINGREQ, /**< Client-to-server PINGREQ. */ + IOT_MQTT_DISCONNECT /**< Client-to-server DISCONNECT. */ +} IotMqttOperationType_t; + +/** + * @ingroup mqtt_datatypes_enums + * @brief Quality of service levels for MQTT PUBLISH messages. + * + * All MQTT PUBLISH messages, including Last Will and Testament and messages + * received on subscription filters, have an associated Quality of Service, + * which defines any delivery guarantees for that message. + * - QoS 0 messages will be delivered at most once. This is a "best effort" + * transmission with no retransmissions. + * - QoS 1 messages will be delivered at least once. See #IotMqttPublishInfo_t + * for the retransmission strategy this library uses to redeliver messages + * assumed to be lost. + * + * @attention QoS 2 is not supported by this library and should not be used. + */ +typedef enum IotMqttQos +{ + IOT_MQTT_QOS_0 = 0, /**< Delivery at most once. */ + IOT_MQTT_QOS_1 = 1, /**< Delivery at least once. See #IotMqttPublishInfo_t for client-side retry strategy. */ + IOT_MQTT_QOS_2 = 2 /**< Delivery exactly once. Unsupported, but enumerated for completeness. */ +} IotMqttQos_t; + +/** + * @ingroup mqtt_datatypes_enums + * @brief The reason that an MQTT connection (and its associated network connection) + * was disconnected. + * + * When an MQTT connection is closed, its associated [disconnect callback] + * (@ref IotMqttNetworkInfo_t::disconnectCallback) will be invoked. This type + * is passed inside of an #IotMqttCallbackParam_t to provide a reason for the + * disconnect. + */ +typedef enum IotMqttDisconnectReason +{ + IOT_MQTT_DISCONNECT_CALLED, /**< @ref mqtt_function_disconnect was invoked. */ + IOT_MQTT_BAD_PACKET_RECEIVED, /**< An invalid packet was received from the network. */ + IOT_MQTT_KEEP_ALIVE_TIMEOUT /**< Keep-alive response was not received within @ref IOT_MQTT_RESPONSE_WAIT_MS. */ +} IotMqttDisconnectReason_t; + +/*------------------------- MQTT parameter structs --------------------------*/ + +/** + * @paramstructs{mqtt,MQTT} + */ + +/** + * @ingroup mqtt_datatypes_paramstructs + * @brief Information on a PUBLISH message. + * + * @paramfor @ref mqtt_function_connect, @ref mqtt_function_publish + * + * Passed to @ref mqtt_function_publish as the message to publish and @ref + * mqtt_function_connect as the Last Will and Testament (LWT) message. + * + * @initializer{IotMqttPublishInfo_t,IOT_MQTT_PUBLISH_INFO_INITIALIZER} + * + * #IotMqttPublishInfo_t.retryMs and #IotMqttPublishInfo_t.retryLimit are only + * relevant to QoS 1 PUBLISH messages. They are ignored for QoS 0 PUBLISH + * messages and LWT messages. These members control retransmissions of QoS 1 + * messages under the following rules: + * - Retransmission is disabled when #IotMqttPublishInfo_t.retryLimit is 0. + * After sending the PUBLISH, the library will wait indefinitely for a PUBACK. + * - If #IotMqttPublishInfo_t.retryLimit is greater than 0, then QoS 1 publishes + * that do not receive a PUBACK within #IotMqttPublishInfo_t.retryMs will be + * retransmitted, up to #IotMqttPublishInfo_t.retryLimit times. + * + * Retransmission follows a truncated exponential backoff strategy. The constant + * @ref IOT_MQTT_RETRY_MS_CEILING controls the maximum time between retransmissions. + * + * After #IotMqttPublishInfo_t.retryLimit retransmissions are sent, the MQTT + * library will wait @ref IOT_MQTT_RESPONSE_WAIT_MS before a final check + * for a PUBACK. If no PUBACK was received within this time, the QoS 1 PUBLISH + * fails with the code #IOT_MQTT_RETRY_NO_RESPONSE. + * + * @note The lengths of the strings in this struct should not include the NULL + * terminator. Strings in this struct do not need to be NULL-terminated. + * + * @note The AWS IoT MQTT server does not support the DUP bit. When + * [using this library with the AWS IoT MQTT server](@ref IotMqttConnectInfo_t.awsIotMqttMode), + * retransmissions will instead be sent with a new packet identifier in the PUBLISH + * packet. This is a nonstandard workaround. Note that this workaround has some + * flaws, including the following: + * - The previous packet identifier is forgotten, so if a PUBACK arrives for that + * packet identifier, it will be ignored. On an exceptionally busy network, this + * may cause excessive retransmissions when too many PUBACKS arrive after the + * PUBLISH packet identifier is changed. However, the exponential backoff + * retransmission strategy should mitigate this problem. + * - Log messages will be printed using the new packet identifier; the old packet + * identifier is not saved. + * + * Example + * + * Consider a situation where + * - @ref IOT_MQTT_RETRY_MS_CEILING is 60000 + * - #IotMqttPublishInfo_t.retryMs is 2000 + * - #IotMqttPublishInfo_t.retryLimit is 20 + * + * A PUBLISH message will be retransmitted at the following times after the initial + * transmission if no PUBACK is received: + * - 2000 ms (2000 ms after previous transmission) + * - 6000 ms (4000 ms after previous transmission) + * - 14000 ms (8000 ms after previous transmission) + * - 30000 ms (16000 ms after previous transmission) + * - 62000 ms (32000 ms after previous transmission) + * - 122000 ms, 182000 ms, 242000 ms... (every 60000 ms until 20 transmissions have been sent) + * + * After the 20th retransmission, the MQTT library will wait + * @ref IOT_MQTT_RESPONSE_WAIT_MS before checking a final time for a PUBACK. + */ +typedef struct IotMqttPublishInfo +{ + IotMqttQos_t qos; /**< @brief QoS of message. Must be 0 or 1. */ + bool retain; /**< @brief MQTT message retain flag. */ + + const char * pTopicName; /**< @brief Topic name of PUBLISH. */ + uint16_t topicNameLength; /**< @brief Length of #IotMqttPublishInfo_t.pTopicName. */ + + const void * pPayload; /**< @brief Payload of PUBLISH. */ + size_t payloadLength; /**< @brief Length of #IotMqttPublishInfo_t.pPayload. For LWT messages, this is limited to 65535. */ + + uint32_t retryMs; /**< @brief If no response is received within this time, the message is retransmitted. */ + uint32_t retryLimit; /**< @brief How many times to attempt retransmission. */ +} IotMqttPublishInfo_t; + +/** + * @ingroup mqtt_datatypes_paramstructs + * @brief Parameter to an MQTT callback function. + * + * @paramfor MQTT callback functions + * + * The MQTT library passes this struct to registered callback whenever an + * operation completes, a message is received on a topic filter, or an MQTT + * connection is disconnected. + * + * The members of this struct are different based on the callback trigger. If the + * callback function was triggered for completed operation, the `operation` + * member is valid. Otherwise, if the callback was triggered because of a + * server-to-client PUBLISH, the `message` member is valid. Finally, if the callback + * was triggered because of a disconnect, the `disconnectReason` member is valid. + * + * For an incoming PUBLISH, the `message.pTopicFilter` parameter provides the + * subscription topic filter that matched the topic name in the PUBLISH. Because + * topic filters may use MQTT wildcards, the topic filter may be different from the + * topic name. This pointer must be treated as read-only; the topic filter must not + * be modified. Additionally, the topic filter may go out of scope as soon as the + * callback function returns, so it must be copied if it is needed at a later time. + * + * @attention Any pointers in this callback parameter may be freed as soon as + * the [callback function](@ref IotMqttCallbackInfo_t.function) returns. + * Therefore, data must be copied if it is needed after the callback function + * returns. + * @attention The MQTT library may set strings that are not NULL-terminated. + * + * @see #IotMqttCallbackInfo_t for the signature of a callback function. + */ +typedef struct IotMqttCallbackParam +{ + /** + * @brief The MQTT connection associated with this completed operation, + * incoming PUBLISH, or disconnect. + * + * [MQTT API functions](@ref mqtt_functions) are safe to call from a callback + * for completed operations or incoming PUBLISH messages. However, blocking + * function calls (including @ref mqtt_function_wait) are not recommended + * (though still safe). Do not call any API functions from a disconnect + * callback. + */ + IotMqttConnection_t mqttConnection; + + union + { + /* Valid for completed operations. */ + struct + { + IotMqttOperationType_t type; /**< @brief Type of operation that completed. */ + IotMqttOperation_t reference; /**< @brief Reference to the operation that completed. */ + IotMqttError_t result; /**< @brief Result of operation, e.g. succeeded or failed. */ + } operation; + + /* Valid for incoming PUBLISH messages. */ + struct + { + const char * pTopicFilter; /**< @brief Topic filter that matched the message. */ + uint16_t topicFilterLength; /**< @brief Length of `pTopicFilter`. */ + IotMqttPublishInfo_t info; /**< @brief PUBLISH message received from the server. */ + } message; + + /* Valid when a connection is disconnected. */ + IotMqttDisconnectReason_t disconnectReason; /**< @brief Why the MQTT connection was disconnected. */ + } u; /**< @brief Valid member depends on callback type. */ +} IotMqttCallbackParam_t; + +/** + * @ingroup mqtt_datatypes_paramstructs + * @brief Information on a user-provided MQTT callback function. + * + * @paramfor @ref mqtt_function_subscribe, @ref mqtt_function_unsubscribe, + * and @ref mqtt_function_publish. Cannot be used with #IOT_MQTT_FLAG_WAITABLE. + * + * Provides a function to be invoked when an operation completes or when a + * server-to-client PUBLISH is received. + * + * @initializer{IotMqttCallbackInfo_t,IOT_MQTT_CALLBACK_INFO_INITIALIZER} + * + * Below is an example for receiving an asynchronous notification on operation + * completion. See @ref mqtt_function_subscribe for an example of using this struct + * with for incoming PUBLISH messages. + * + * @code{c} + * // Operation completion callback. + * void operationComplete( void * pArgument, IotMqttCallbackParam_t * pOperation ); + * + * // Callback information. + * IotMqttCallbackInfo_t callbackInfo = IOT_MQTT_CALLBACK_INFO_INITIALIZER; + * callbackInfo.function = operationComplete; + * + * // Operation to wait for. + * IotMqttError_t result = IotMqtt_Publish( &mqttConnection, + * &publishInfo, + * 0, + * &callbackInfo, + * &reference ); + * + * // Publish should have returned IOT_MQTT_STATUS_PENDING. Once a response + * // is received, operationComplete is executed with the actual status passed + * // in pOperation. + * @endcode + */ +typedef struct IotMqttCallbackInfo +{ + void * pCallbackContext; /**< @brief The first parameter to pass to the callback function to provide context. */ + + /** + * @brief User-provided callback function signature. + * + * @param[in] void * #IotMqttCallbackInfo_t.pCallbackContext + * @param[in] IotMqttCallbackParam_t * Details on the outcome of the MQTT operation + * or an incoming MQTT PUBLISH. + * + * @see #IotMqttCallbackParam_t for more information on the second parameter. + */ + void ( * function )( void *, + IotMqttCallbackParam_t * ); +} IotMqttCallbackInfo_t; + +/** + * @ingroup mqtt_datatypes_paramstructs + * @brief Information on an MQTT subscription. + * + * @paramfor @ref mqtt_function_subscribe, @ref mqtt_function_unsubscribe + * + * An array of these is passed to @ref mqtt_function_subscribe and @ref + * mqtt_function_unsubscribe. However, #IotMqttSubscription_t.callback and + * and #IotMqttSubscription_t.qos are ignored by @ref mqtt_function_unsubscribe. + * + * @initializer{IotMqttSubscription_t,IOT_MQTT_SUBSCRIPTION_INITIALIZER} + * + * @note The lengths of the strings in this struct should not include the NULL + * terminator. Strings in this struct do not need to be NULL-terminated. + * @see #IotMqttCallbackInfo_t for details on setting a callback function. + */ +typedef struct IotMqttSubscription +{ + /** + * @brief QoS of messages delivered on subscription. + * + * Must be `0` or `1`. Ignored by @ref mqtt_function_unsubscribe. + */ + IotMqttQos_t qos; + + const char * pTopicFilter; /**< @brief Topic filter of subscription. */ + uint16_t topicFilterLength; /**< @brief Length of #IotMqttSubscription_t.pTopicFilter. */ + + /** + * @brief Callback to invoke when a message is received. + * + * See #IotMqttCallbackInfo_t. Ignored by @ref mqtt_function_unsubscribe. + */ + IotMqttCallbackInfo_t callback; +} IotMqttSubscription_t; + +/** + * @ingroup mqtt_datatypes_paramstructs + * @brief Information on a new MQTT connection. + * + * @paramfor @ref mqtt_function_connect + * + * Passed as an argument to @ref mqtt_function_connect. Most members of this struct + * correspond to the content of an [MQTT CONNECT packet.] + * (http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/csprd02/mqtt-v3.1.1-csprd02.html#_Toc385349764) + * + * @initializer{IotMqttConnectInfo_t,IOT_MQTT_CONNECT_INFO_INITIALIZER} + * + * @note The lengths of the strings in this struct should not include the NULL + * terminator. Strings in this struct do not need to be NULL-terminated. + */ +typedef struct IotMqttConnectInfo +{ + /** + * @brief Specifies if this MQTT connection is to an AWS IoT MQTT server. + * + * The AWS IoT MQTT broker [differs somewhat from the MQTT specification.] + * (https://docs.aws.amazon.com/iot/latest/developerguide/mqtt.html) + * When this member is `true`, the MQTT library will accommodate these + * differences. This setting should be `false` when communicating with a + * fully-compliant MQTT broker. + * + * @attention This setting MUST be `true` when using the AWS IoT MQTT + * server; it MUST be `false` otherwise. + * @note Currently, @ref IOT_MQTT_CONNECT_INFO_INITIALIZER sets this + * this member to `true`. + */ + bool awsIotMqttMode; + + /** + * @brief Whether this connection is a clean session. + * + * MQTT servers can maintain and topic filter subscriptions and unacknowledged + * PUBLISH messages. These form part of an MQTT session, which is identified by + * the [client identifier](@ref IotMqttConnectInfo_t.pClientIdentifier). + * + * Setting this value to `true` establishes a clean session, which causes + * the MQTT server to discard any previous session data for a client identifier. + * When the client disconnects, the server discards all session data. If this + * value is `true`, #IotMqttConnectInfo_t.pPreviousSubscriptions and + * #IotMqttConnectInfo_t.previousSubscriptionCount are ignored. + * + * Setting this value to `false` does one of the following: + * - If no previous session exists, the MQTT server will create a new + * persistent session. The server may maintain subscriptions and + * unacknowledged PUBLISH messages after a client disconnects, to be restored + * once the same client identifier reconnects. + * - If a previous session exists, the MQTT server restores all of the session's + * subscriptions for the client identifier and may immediately transmit any + * unacknowledged PUBLISH packets to the client. + * + * When a client with a persistent session disconnects, the MQTT server + * continues to maintain all subscriptions and unacknowledged PUBLISH messages. + * The client must also remember the session subscriptions to restore them + * upon reconnecting. #IotMqttConnectInfo_t.pPreviousSubscriptions and + * #IotMqttConnectInfo_t.previousSubscriptionCount are used to restore a + * previous session's subscriptions client-side. + */ + bool cleanSession; + + /** + * @brief An array of MQTT subscriptions present in a previous session, if any. + * + * Pointer to the start of an array of subscriptions present a previous session, + * if any. These subscriptions will be immediately restored upon reconnecting. + * + * This member is ignored if it is `NULL` or #IotMqttConnectInfo_t.cleanSession + * is `true`. If this member is not `NULL`, #IotMqttConnectInfo_t.previousSubscriptionCount + * must be nonzero. + */ + const IotMqttSubscription_t * pPreviousSubscriptions; + + /** + * @brief The number of MQTT subscriptions present in a previous session, if any. + * + * Number of subscriptions contained in the array + * #IotMqttConnectInfo_t.pPreviousSubscriptions. + * + * This value is ignored if #IotMqttConnectInfo_t.pPreviousSubscriptions + * is `NULL` or #IotMqttConnectInfo_t.cleanSession is `true`. If + * #IotMqttConnectInfo_t.pPreviousSubscriptions is not `NULL`, this value + * must be nonzero. + */ + size_t previousSubscriptionCount; + + /** + * @brief A message to publish if the new MQTT connection is unexpectedly closed. + * + * A Last Will and Testament (LWT) message may be published if this connection is + * closed without sending an MQTT DISCONNECT packet. This pointer should be set to + * an #IotMqttPublishInfo_t representing any LWT message to publish. If an LWT + * is not needed, this member must be set to `NULL`. + * + * Unlike other PUBLISH messages, an LWT message is limited to 65535 bytes in + * length. Additionally, [pWillInfo->retryMs](@ref IotMqttPublishInfo_t.retryMs) + * and [pWillInfo->retryLimit](@ref IotMqttPublishInfo_t.retryLimit) will + * be ignored. + */ + const IotMqttPublishInfo_t * pWillInfo; + + uint16_t keepAliveSeconds; /**< @brief Period of keep-alive messages. Set to 0 to disable keep-alive. */ + + const char * pClientIdentifier; /**< @brief MQTT client identifier. */ + uint16_t clientIdentifierLength; /**< @brief Length of #IotMqttConnectInfo_t.pClientIdentifier. */ + + /* These credentials are not used by AWS IoT and may be ignored if + * awsIotMqttMode is true. */ + const char * pUserName; /**< @brief Username for MQTT connection. */ + uint16_t userNameLength; /**< @brief Length of #IotMqttConnectInfo_t.pUserName. */ + const char * pPassword; /**< @brief Password for MQTT connection. */ + uint16_t passwordLength; /**< @brief Length of #IotMqttConnectInfo_t.pPassword. */ +} IotMqttConnectInfo_t; + +#if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Forward declaration of the internal MQTT packet structure. + */ + struct _mqttPacket; +/** @endcond */ + +/** + * @ingroup mqtt_datatypes_paramstructs + * @brief Function pointers for MQTT packet serializer overrides. + * + * These function pointers allow the MQTT serialization and deserialization functions + * to be overridden for an MQTT connection. The compile-time setting + * @ref IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES must be `1` to enable this functionality. + * See the #IotMqttSerializer_t::serialize and #IotMqttSerializer_t::deserialize + * members for a list of functions that can be overridden. In addition, the functions + * for freeing packets and determining the packet type can also be overridden. If + * @ref IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES is `1`, the serializer initialization and + * cleanup functions may be extended. See documentation of + * @ref IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES for more information. + * + * If any function pointers that are `NULL`, then the default implementation of that + * function will be used. + */ + typedef struct IotMqttSerializer + { + /** + * @brief Get the MQTT packet type from a stream of bytes off the network. + * + * @param[in] pNetworkConnection Reference to the network connection. + * @param[in] pNetworkInterface Function pointers used to interact with the + * network. + * + * Default implementation: #_IotMqtt_GetPacketType + */ + uint8_t ( * getPacketType )( void * /* pNetworkConnection */, + const IotNetworkInterface_t * /* pNetworkInterface */ ); + + /** + * @brief Get the remaining length from a stream of bytes off the network. + * + * @param[in] pNetworkConnection Reference to the network connection. + * @param[in] pNetworkInterface Function pointers used to interact with the + * network. + * + * Default implementation: #_IotMqtt_GetRemainingLength + */ + size_t ( * getRemainingLength )( void * pNetworkConnection, + const IotNetworkInterface_t * pNetworkInterface ); + + /** + * @brief Free a packet generated by the serializer. + * + * This function pointer must be set if any other serializer override is set. + * @param[in] uint8_t* The packet to free. + * + * Default implementation: #_IotMqtt_FreePacket + */ + void ( * freePacket )( uint8_t * /* pPacket */ ); + + struct + { + /** + * @brief CONNECT packet serializer function. + * @param[in] IotMqttConnectInfo_t* User-provided CONNECT information. + * @param[out] uint8_t** Where the CONNECT packet is written. + * @param[out] size_t* Size of the CONNECT packet. + * + * Default implementation: #_IotMqtt_SerializeConnect + */ + IotMqttError_t ( * connect )( const IotMqttConnectInfo_t * /* pConnectInfo */, + uint8_t ** /* pConnectPacket */, + size_t * /* pPacketSize */ ); + + /** + * @brief PUBLISH packet serializer function. + * @param[in] IotMqttPublishInfo_t* User-provided PUBLISH information. + * @param[out] uint8_t** Where the PUBLISH packet is written. + * @param[out] size_t* Size of the PUBLISH packet. + * @param[out] uint16_t* The packet identifier generated for this PUBLISH. + * @param[out] uint8_t** Where the high byte of the packet identifier + * is written. + * + * Default implementation: #_IotMqtt_SerializePublish + */ + IotMqttError_t ( * publish )( const IotMqttPublishInfo_t * /* pPublishInfo */, + uint8_t ** /* pPublishPacket */, + size_t * /* pPacketSize */, + uint16_t * /* pPacketIdentifier */, + uint8_t ** /* pPacketIdentifierHigh */ ); + + /** + * @brief Set the `DUP` bit in a QoS `1` PUBLISH packet. + * @param[in] uint8_t* Pointer to the PUBLISH packet to modify. + * @param[in] uint8_t* The high byte of any packet identifier to modify. + * @param[out] uint16_t* New packet identifier (AWS IoT MQTT mode only). + * + * Default implementation: #_IotMqtt_PublishSetDup + */ + void ( *publishSetDup )( uint8_t * /* pPublishPacket */, + uint8_t * /* pPacketIdentifierHigh */, + uint16_t * /* pNewPacketIdentifier */ ); + + /** + * @brief PUBACK packet serializer function. + * @param[in] uint16_t The packet identifier to place in PUBACK. + * @param[out] uint8_t** Where the PUBACK packet is written. + * @param[out] size_t* Size of the PUBACK packet. + * + * Default implementation: #_IotMqtt_SerializePuback + */ + IotMqttError_t ( * puback )( uint16_t /* packetIdentifier */, + uint8_t ** /* pPubackPacket */, + size_t * /* pPacketSize */ ); + + /** + * @brief SUBSCRIBE packet serializer function. + * @param[in] IotMqttSubscription_t* User-provided array of subscriptions. + * @param[in] size_t Number of elements in the subscription array. + * @param[out] uint8_t** Where the SUBSCRIBE packet is written. + * @param[out] size_t* Size of the SUBSCRIBE packet. + * @param[out] uint16_t* The packet identifier generated for this SUBSCRIBE. + * + * Default implementation: #_IotMqtt_SerializeSubscribe + */ + IotMqttError_t ( * subscribe )( const IotMqttSubscription_t * /* pSubscriptionList */, + size_t /* subscriptionCount */, + uint8_t ** /* pSubscribePacket */, + size_t * /* pPacketSize */, + uint16_t * /* pPacketIdentifier */ ); + + /** + * @brief UNSUBSCRIBE packet serializer function. + * @param[in] IotMqttSubscription_t* User-provided array of subscriptions to remove. + * @param[in] size_t Number of elements in the subscription array. + * @param[out] uint8_t** Where the UNSUBSCRIBE packet is written. + * @param[out] size_t* Size of the UNSUBSCRIBE packet. + * @param[out] uint16_t* The packet identifier generated for this UNSUBSCRIBE. + * + * Default implementation: #_IotMqtt_SerializeUnsubscribe + */ + IotMqttError_t ( * unsubscribe )( const IotMqttSubscription_t * /* pSubscriptionList */, + size_t /* subscriptionCount */, + uint8_t ** /* pUnsubscribePacket */, + size_t * /* pPacketSize */, + uint16_t * /* pPacketIdentifier */ ); + + /** + * @brief PINGREQ packet serializer function. + * @param[out] uint8_t** Where the PINGREQ packet is written. + * @param[out] size_t* Size of the PINGREQ packet. + * + * Default implementation: #_IotMqtt_SerializePingreq + */ + IotMqttError_t ( * pingreq )( uint8_t ** /* pPingreqPacket */, + size_t * /* pPacketSize */ ); + + /** + * @brief DISCONNECT packet serializer function. + * @param[out] uint8_t** Where the DISCONNECT packet is written. + * @param[out] size_t* Size of the DISCONNECT packet. + * + * Default implementation: #_IotMqtt_SerializeDisconnect + */ + IotMqttError_t ( * disconnect )( uint8_t ** /* pDisconnectPacket */, + size_t * /* pPacketSize */ ); + } serialize; /**< @brief Overrides the packet serialization functions for a single connection. */ + + struct + { + /** + * @brief CONNACK packet deserializer function. + * @param[in,out] _mqttPacket* Pointer to an MQTT packet struct representing a CONNACK. + * + * Default implementation: #_IotMqtt_DeserializeConnack + */ + IotMqttError_t ( * connack )( struct _mqttPacket * /* pConnack */ ); + + /** + * @brief PUBLISH packet deserializer function. + * @param[in,out] _mqttPacket* Pointer to an MQTT packet struct representing a PUBLISH. + * + * Default implementation: #_IotMqtt_DeserializePublish + */ + IotMqttError_t ( * publish )( struct _mqttPacket * /* pPublish */ ); + + /** + * @brief PUBACK packet deserializer function. + * @param[in,out] _mqttPacket* Pointer to an MQTT packet struct representing a PUBACK. + * + * Default implementation: #_IotMqtt_DeserializePuback + */ + IotMqttError_t ( * puback )( struct _mqttPacket * pPuback ); + + /** + * @brief SUBACK packet deserializer function. + * @param[in,out] _mqttPacket* Pointer to an MQTT packet struct representing a SUBACK. + * + * Default implementation: #_IotMqtt_DeserializeSuback + */ + IotMqttError_t ( * suback )( struct _mqttPacket * /* pSuback */ ); + + /** + * @brief UNSUBACK packet deserializer function. + * @param[in,out] _mqttPacket* Pointer to an MQTT packet struct representing an UNSUBACK. + * + * Default implementation: #_IotMqtt_DeserializeUnsuback + */ + IotMqttError_t ( * unsuback )( struct _mqttPacket * /* pUnsuback */ ); + + /** + * @brief PINGRESP packet deserializer function. + * @param[in,out] _mqttPacket* Pointer to an MQTT packet struct representing a PINGRESP. + * + * Default implementation: #_IotMqtt_DeserializePingresp + */ + IotMqttError_t ( * pingresp )( struct _mqttPacket * /* pPingresp */ ); + } deserialize; /**< @brief Overrides the packet deserialization functions for a single connection. */ + } IotMqttSerializer_t; +#else /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + +/* When MQTT packet serializer overrides are disabled, this struct is an + * incomplete type. */ + typedef struct IotMqttSerializer IotMqttSerializer_t; + +#endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + +/** + * @ingroup mqtt_datatypes_paramstructs + * @brief Infomation on the transport-layer network connection for the new MQTT + * connection. + * + * @paramfor @ref mqtt_function_connect + * + * The MQTT library needs to be able to send and receive data over a network. + * This struct provides an interface for interacting with the network. + * + * @initializer{IotMqttNetworkInfo_t,IOT_MQTT_NETWORK_INFO_INITIALIZER} + */ +typedef struct IotMqttNetworkInfo +{ + /** + * @brief Whether a new network connection should be created. + * + * When this value is `true`, a new transport-layer network connection will + * be created along with the MQTT connection. #IotMqttNetworkInfo_t::pNetworkServerInfo + * and #IotMqttNetworkInfo_t::pNetworkCredentialInfo are valid when this value + * is `true`. + * + * When this value is `false`, the MQTT connection will use a transport-layer + * network connection that has already been established. The MQTT library will + * still set the appropriate receive callback even if the network connection + * has been established. + * #IotMqttNetworkInfo_t::pNetworkConnection, which represents an established + * network connection, is valid when this value is `false`. + */ + bool createNetworkConnection; + + union + { + struct + { + /** + * @brief Information on the MQTT server, passed as `pConnectionInfo` to + * #IotNetworkInterface_t::create. + * + * This member is opaque to the MQTT library. It is passed to the network + * interface when creating a new network connection. It is only valid when + * #IotMqttNetworkInfo_t::createNetworkConnection is `true`. + */ + void * pNetworkServerInfo; + + /** + * @brief Credentials for the MQTT server, passed as `pCredentialInfo` to + * #IotNetworkInterface_t::create. + * + * This member is opaque to the MQTT library. It is passed to the network + * interface when creating a new network connection. It is only valid when + * #IotMqttNetworkInfo_t::createNetworkConnection is `true`. + */ + void * pNetworkCredentialInfo; + } setup; + + /** + * @brief An established transport-layer network connection. + * + * This member is opaque to the MQTT library. It is passed to the network + * interface to reference an established network connection. It is only + * valid when #IotMqttNetworkInfo_t::createNetworkConnection is `false`. + */ + void * pNetworkConnection; + } u /**< @brief Valid member depends of IotMqttNetworkInfo_t.createNetworkConnection. */; + + /** + * @brief The network functions used by the new MQTT connection. + * + * @attention The function pointers of the network interface must remain valid + * for the lifetime of the MQTT connection. + */ + const IotNetworkInterface_t * pNetworkInterface; + + /** + * @brief A callback function to invoke when this MQTT connection is disconnected. + */ + IotMqttCallbackInfo_t disconnectCallback; + + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + + /** + * @brief MQTT packet serializer overrides used by the new MQTT connection. + * + * @attention The function pointers of the MQTT serializer overrides must + * remain valid for the lifetime of the MQTT connection. + */ + const IotMqttSerializer_t * pMqttSerializer; + #endif +} IotMqttNetworkInfo_t; + +/*------------------------- MQTT defined constants --------------------------*/ + +/** + * @constantspage{mqtt,MQTT library} + * + * @section mqtt_constants_initializers MQTT Initializers + * @brief Provides default values for the data types of the MQTT library. + * + * @snippet this define_mqtt_initializers + * + * All user-facing data types of the MQTT library should be initialized using + * one of the following. + * + * @warning Failing to initialize an MQTT data type with the appropriate initializer + * may result in undefined behavior! + * @note The initializers may change at any time in future versions, but their + * names will remain the same. + * + * Example + * @code{c} + * IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; + * IotMqttSerializer_t serializer = IOT_MQTT_SERIALIZER_INITIALIZER; + * IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; + * IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; + * IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; + * IotMqttCallbackInfo_t callbackInfo = IOT_MQTT_CALLBACK_INFO_INITIALIZER; + * IotMqttConnection_t connection = IOT_MQTT_CONNECTION_INITIALIZER; + * IotMqttOperation_t operation = IOT_MQTT_OPERATION_INITIALIZER; + * @endcode + * + * @section mqtt_constants_flags MQTT Function Flags + * @brief Flags that modify the behavior of MQTT library functions. + * - #IOT_MQTT_FLAG_WAITABLE
+ * @copybrief IOT_MQTT_FLAG_WAITABLE + * - #IOT_MQTT_FLAG_CLEANUP_ONLY
+ * @copybrief IOT_MQTT_FLAG_CLEANUP_ONLY + * + * Flags should be bitwise-ORed with each other to change the behavior of + * @ref mqtt_function_subscribe, @ref mqtt_function_unsubscribe, + * @ref mqtt_function_publish, or @ref mqtt_function_disconnect. + * + * @note The values of the flags may change at any time in future versions, but + * their names will remain the same. Additionally, flags that may be used together + * will be bitwise-exclusive of each other. + */ + +/* @[define_mqtt_initializers] */ +/** @brief Initializer for #IotMqttNetworkInfo_t. */ +#define IOT_MQTT_NETWORK_INFO_INITIALIZER { .createNetworkConnection = true } +/** @brief Initializer for #IotMqttSerializer_t. */ +#define IOT_MQTT_SERIALIZER_INITIALIZER { 0 } +/** @brief Initializer for #IotMqttConnectInfo_t. */ +#define IOT_MQTT_CONNECT_INFO_INITIALIZER { .cleanSession = true } +/** @brief Initializer for #IotMqttPublishInfo_t. */ +#define IOT_MQTT_PUBLISH_INFO_INITIALIZER { .qos = IOT_MQTT_QOS_0 } +/** @brief Initializer for #IotMqttSubscription_t. */ +#define IOT_MQTT_SUBSCRIPTION_INITIALIZER { .qos = IOT_MQTT_QOS_0 } +/** @brief Initializer for #IotMqttCallbackInfo_t. */ +#define IOT_MQTT_CALLBACK_INFO_INITIALIZER { 0 } +/** @brief Initializer for #IotMqttConnection_t. */ +#define IOT_MQTT_CONNECTION_INITIALIZER NULL +/** @brief Initializer for #IotMqttOperation_t. */ +#define IOT_MQTT_OPERATION_INITIALIZER NULL +/* @[define_mqtt_initializers] */ + +/** + * @brief Allows the use of @ref mqtt_function_wait for blocking until completion. + * + * This flag is always valid for @ref mqtt_function_subscribe and + * @ref mqtt_function_unsubscribe. If passed to @ref mqtt_function_publish, + * the parameter [pPublishInfo->qos](@ref IotMqttPublishInfo_t.qos) must not be `0`. + * + * An #IotMqttOperation_t MUST be provided if this flag is set. Additionally, an + * #IotMqttCallbackInfo_t MUST NOT be provided. + * + * @note If this flag is set, @ref mqtt_function_wait MUST be called to clean up + * resources. + */ +#define IOT_MQTT_FLAG_WAITABLE ( 0x00000001 ) + +/** + * @brief Causes @ref mqtt_function_disconnect to only free memory and not send + * an MQTT DISCONNECT packet. + * + * This flag is only valid for @ref mqtt_function_disconnect. It should be passed + * to @ref mqtt_function_disconnect if the network goes offline or is otherwise + * unusable. + */ +#define IOT_MQTT_FLAG_CLEANUP_ONLY ( 0x00000001 ) + +#endif /* ifndef IOT_MQTT_TYPES_H_ */ diff --git a/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/src/iot_ble_mqtt_serialize.c b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/src/iot_ble_mqtt_serialize.c new file mode 100644 index 000000000..143b44297 --- /dev/null +++ b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/src/iot_ble_mqtt_serialize.c @@ -0,0 +1,1528 @@ +/* + * Amazon FreeRTOS MQTT V2.0.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +/** + * @file aws_mqtt_lib_ble.c + * @brief MQTT library for BLE. + */ +/* The config header is always included first. */ +#include "iot_config.h" + +/* Standard includes. */ +#include +#include + +/* FreeRTOS includes. */ +#include "FreeRTOS.h" + +#include "iot_ble_config.h" + + +/* MQTT internal includes. */ +#include "platform/iot_threads.h" +#include "iot_serializer.h" +#include "platform/iot_network_ble.h" +#include "iot_ble_data_transfer.h" +#include "iot_ble_mqtt_serialize.h" +#include "private/iot_mqtt_internal.h" + +#define _INVALID_MQTT_PACKET_TYPE ( 0xF0 ) + + +#define _IS_VALID_SERIALIZER_RET( ret, pSerializerBuf ) \ + ( ( ret == IOT_SERIALIZER_SUCCESS ) || \ + ( ( !pSerializerBuf ) && ( ret == IOT_SERIALIZER_BUFFER_TOO_SMALL ) ) ) + +#define _NUM_CONNECT_PARMAS ( 4 ) +#define _NUM_DEFAULT_PUBLISH_PARMAS ( 4 ) +#define _NUM_PUBACK_PARMAS ( 2 ) +#define _NUM_SUBACK_PARAMS ( 4 ) +#define _NUM_UNSUBACK_PARAMS ( 3 ) +#define _NUM_DISCONNECT_PARAMS ( 1 ) +#define _NUM_PINGREQUEST_PARAMS ( 1 ) + +const IotMqttSerializer_t IotBleMqttSerializer = { + .serialize.connect = IotBleMqtt_SerializeConnect, + .serialize.publish = IotBleMqtt_SerializePublish, + .serialize.publishSetDup = IotBleMqtt_PublishSetDup, + .serialize.puback = IotBleMqtt_SerializePuback, + .serialize.subscribe = IotBleMqtt_SerializeSubscribe, + .serialize.unsubscribe = IotBleMqtt_SerializeUnsubscribe, + .serialize.pingreq = IotBleMqtt_SerializePingreq, + .serialize.disconnect = IotBleMqtt_SerializeDisconnect, + .freePacket = IotBleMqtt_FreePacket, + .getPacketType = IotBleMqtt_GetPacketType, + .getRemainingLength = IotBleMqtt_GetRemainingLength, + .deserialize.connack = IotBleMqtt_DeserializeConnack, + .deserialize.publish = IotBleMqtt_DeserializePublish, + .deserialize.puback = IotBleMqtt_DeserializePuback, + .deserialize.suback = IotBleMqtt_DeserializeSuback, + .deserialize.unsuback = IotBleMqtt_DeserializeUnsuback, + .deserialize.pingresp = IotBleMqtt_DeserializePingresp +}; + +/** + * @brief Guards access to the packet identifier counter. + * + * Each packet should have a unique packet identifier. This mutex ensures that only + * one thread at a time may read the global packet identifer. + */ + + +/** + * @brief Generates a monotonically increasing identifier used in MQTT message + * + * @return Identifier for the MQTT message + */ +static uint16_t _nextPacketIdentifier( void ); + + +static inline uint16_t _getNumPublishParams( const IotMqttPublishInfo_t * const pPublish ) +{ + return ( pPublish->qos > 0 ) ? ( _NUM_DEFAULT_PUBLISH_PARMAS + 1 ) : _NUM_DEFAULT_PUBLISH_PARMAS; +} + +static IotSerializerError_t _serializeConnect( const IotMqttConnectInfo_t * const pConnectInfo, + uint8_t* const pBuffer, + size_t* const pSize ); +static IotSerializerError_t _serializePublish( const IotMqttPublishInfo_t * const pPublishInfo, + uint8_t * pBuffer, + size_t * pSize, + uint16_t packetIdentifier ); +static IotSerializerError_t _serializePubAck( uint16_t packetIdentifier, + uint8_t * pBuffer, + size_t * pSize ); + + + +static IotSerializerError_t _serializeSubscribe( const IotMqttSubscription_t * const pSubscriptionList, + size_t subscriptionCount, + uint8_t * const pBuffer, + size_t * const pSize, + uint16_t packetIdentifier ); + +static IotSerializerError_t _serializeUnSubscribe( const IotMqttSubscription_t * const pSubscriptionList, + size_t subscriptionCount, + uint8_t * const pBuffer, + size_t * const pSize, + uint16_t packetIdentifier ); + +static IotSerializerError_t _serializeDisconnect( uint8_t * const pBuffer, + size_t * const pSize ); + + +static IotSerializerError_t _serializePingRequest( uint8_t * const pBuffer, + size_t * const pSize ); + +#if LIBRARY_LOG_LEVEL > AWS_IOT_LOG_NONE + +/** + * @brief If logging is enabled, define a log configuration that only prints the log + * string. This is used when printing out details of deserialized MQTT packets. + */ +static const IotLogConfig_t _logHideAll = +{ + .hideLibraryName = true, + .hideLogLevel = true, + .hideTimestring = true +}; +#endif + + +static IotMutex_t _packetIdentifierMutex; + + +/* Declaration of snprintf. The header stdio.h is not included because it + * includes conflicting symbols on some platforms. */ +extern int snprintf( char * s, + size_t n, + const char * format, + ... ); + +/*-----------------------------------------------------------*/ + +static uint16_t _nextPacketIdentifier( void ) +{ + static uint16_t nextPacketIdentifier = 1; + uint16_t newPacketIdentifier = 0; + + /* Lock the packet identifier mutex so that only one thread may read and + * modify nextPacketIdentifier. */ + IotMutex_Lock( &_packetIdentifierMutex ); + + /* Read the next packet identifier. */ + newPacketIdentifier = nextPacketIdentifier; + + /* The next packet identifier will be greater by 2. This prevents packet + * identifiers from ever being 0, which is not allowed by MQTT 3.1.1. Packet + * identifiers will follow the sequence 1,3,5...65535,1,3,5... */ + nextPacketIdentifier = ( uint16_t ) ( nextPacketIdentifier + ( ( uint16_t ) 2 ) ); + + /* Unlock the packet identifier mutex. */ + IotMutex_Unlock( &_packetIdentifierMutex ); + + return newPacketIdentifier; +} + +static IotSerializerError_t _serializeConnect( const IotMqttConnectInfo_t * const pConnectInfo, + uint8_t* const pBuffer, + size_t* const pSize ) +{ + IotSerializerError_t error = IOT_SERIALIZER_SUCCESS; + IotSerializerEncoderObject_t encoderObj = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_STREAM ; + IotSerializerEncoderObject_t connectMap = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; + IotSerializerScalarData_t data = { 0 }; + + error = IOT_BLE_MESG_ENCODER.init( &encoderObj, pBuffer, *pSize ); + if( error == IOT_SERIALIZER_SUCCESS ) + { + + error = IOT_BLE_MESG_ENCODER.openContainer( + &encoderObj, + &connectMap, + _NUM_CONNECT_PARMAS ); + } + + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + data.type = IOT_SERIALIZER_SCALAR_SIGNED_INT; + data.value.u.signedInt = IOT_BLE_MQTT_MSG_TYPE_CONNECT; + error = IOT_BLE_MESG_ENCODER.appendKeyValue( &connectMap, IOT_BLE_MQTT_MSG_TYPE, data ); + } + + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + data.type = IOT_SERIALIZER_SCALAR_TEXT_STRING; + data.value.u.string.pString = ( uint8_t * ) pConnectInfo->pClientIdentifier; + data.value.u.string.length = pConnectInfo->clientIdentifierLength; + error = IOT_BLE_MESG_ENCODER.appendKeyValue( &connectMap, IOT_BLE_MQTT_CLIENT_ID, data ); + } + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + data.type = IOT_SERIALIZER_SCALAR_TEXT_STRING; + data.value.u.string.pString = ( uint8_t * ) clientcredentialMQTT_BROKER_ENDPOINT; + data.value.u.string.length = strlen( clientcredentialMQTT_BROKER_ENDPOINT ); + error = IOT_BLE_MESG_ENCODER.appendKeyValue( &connectMap, IOT_BLE_MQTT_BROKER_EP, data ); + } + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + data.type = IOT_SERIALIZER_SCALAR_BOOL; + data.value.u.booleanValue = pConnectInfo->cleanSession; + error = IOT_BLE_MESG_ENCODER.appendKeyValue( &connectMap, IOT_BLE_MQTT_CLEAN_SESSION, data ); + } + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + error = IOT_BLE_MESG_ENCODER.closeContainer( &encoderObj, &connectMap ); + } + + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + if( pBuffer == NULL ) + { + *pSize = IOT_BLE_MESG_ENCODER.getExtraBufferSizeNeeded( &encoderObj ); + } + else + { + *pSize = IOT_BLE_MESG_ENCODER.getEncodedSize( &encoderObj, pBuffer ); + } + + IOT_BLE_MESG_ENCODER.destroy( &encoderObj ); + error = IOT_SERIALIZER_SUCCESS; + + } + + return error; +} + +static IotSerializerError_t _serializePublish( const IotMqttPublishInfo_t * const pPublishInfo, + uint8_t * pBuffer, + size_t * pSize, + uint16_t packetIdentifier ) +{ + IotSerializerError_t error = IOT_SERIALIZER_SUCCESS; + IotSerializerEncoderObject_t encoderObj = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_STREAM ; + IotSerializerEncoderObject_t publishMap = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; + IotSerializerScalarData_t data = { 0 }; + uint16_t numPublishParams = _getNumPublishParams( pPublishInfo ); + + error = IOT_BLE_MESG_ENCODER.init( &encoderObj, pBuffer, *pSize ); + + if( error == IOT_SERIALIZER_SUCCESS ) + { + + + error = IOT_BLE_MESG_ENCODER.openContainer( + &encoderObj, + &publishMap, + numPublishParams ); + } + + + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + data.type = IOT_SERIALIZER_SCALAR_SIGNED_INT; + data.value.u.signedInt = IOT_BLE_MQTT_MSG_TYPE_PUBLISH; + error = IOT_BLE_MESG_ENCODER.appendKeyValue( &publishMap, IOT_BLE_MQTT_MSG_TYPE, data ); + } + + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + data.type = IOT_SERIALIZER_SCALAR_TEXT_STRING; + data.value.u.string.pString = ( uint8_t * ) pPublishInfo->pTopicName; + data.value.u.string.length = pPublishInfo->topicNameLength; + error = IOT_BLE_MESG_ENCODER.appendKeyValue( &publishMap, IOT_BLE_MQTT_TOPIC, data ); + } + + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + + data.type = IOT_SERIALIZER_SCALAR_SIGNED_INT; + data.value.u.signedInt = pPublishInfo->qos; + error = IOT_BLE_MESG_ENCODER.appendKeyValue( &publishMap, IOT_BLE_MQTT_QOS, data ); + } + + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + data.type = IOT_SERIALIZER_SCALAR_BYTE_STRING; + data.value.u.string.pString = ( uint8_t * ) pPublishInfo->pPayload; + data.value.u.string.length = pPublishInfo->payloadLength; + error = IOT_BLE_MESG_ENCODER.appendKeyValue( &publishMap, IOT_BLE_MQTT_PAYLOAD, data ); + } + + + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + if( pPublishInfo->qos != 0 ) + { + data.type = IOT_SERIALIZER_SCALAR_SIGNED_INT; + data.value.u.signedInt = packetIdentifier; + error = IOT_BLE_MESG_ENCODER.appendKeyValue( &publishMap, IOT_BLE_MQTT_MESSAGE_ID, data ); + + } + } + + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + + error = IOT_BLE_MESG_ENCODER.closeContainer( &encoderObj, &publishMap ); + } + + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + if( pBuffer == NULL ) + { + *pSize = IOT_BLE_MESG_ENCODER.getExtraBufferSizeNeeded( &encoderObj ); + + } + else + { + *pSize = IOT_BLE_MESG_ENCODER.getEncodedSize( &encoderObj, pBuffer ); + } + IOT_BLE_MESG_ENCODER.destroy( &encoderObj ); + error = IOT_SERIALIZER_SUCCESS; + } + + + return error; +} + +static IotSerializerError_t _serializePubAck( uint16_t packetIdentifier, + uint8_t * pBuffer, + size_t * pSize ) + +{ + IotSerializerError_t error = IOT_SERIALIZER_SUCCESS; + IotSerializerEncoderObject_t encoderObj = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_STREAM ; + IotSerializerEncoderObject_t pubAckMap = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; + IotSerializerScalarData_t data = { 0 }; + + error = IOT_BLE_MESG_ENCODER.init( &encoderObj, pBuffer, *pSize ); + + if( error == IOT_SERIALIZER_SUCCESS ) + { + + error = IOT_BLE_MESG_ENCODER.openContainer( + &encoderObj, + &pubAckMap, + _NUM_PUBACK_PARMAS ); + } + + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + data.type = IOT_SERIALIZER_SCALAR_SIGNED_INT; + data.value.u.signedInt = IOT_BLE_MQTT_MSG_TYPE_PUBACK; + error = IOT_BLE_MESG_ENCODER.appendKeyValue( &pubAckMap, IOT_BLE_MQTT_MSG_TYPE, data ); + } + + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + data.type = IOT_SERIALIZER_SCALAR_SIGNED_INT; + data.value.u.signedInt = packetIdentifier; + error = IOT_BLE_MESG_ENCODER.appendKeyValue( &pubAckMap, IOT_BLE_MQTT_MESSAGE_ID, data ); + } + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + error = IOT_BLE_MESG_ENCODER.closeContainer( &encoderObj, &pubAckMap ); + } + + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + if( pBuffer == NULL ) + { + *pSize = IOT_BLE_MESG_ENCODER.getExtraBufferSizeNeeded( &encoderObj ); + } + else + { + *pSize = IOT_BLE_MESG_ENCODER.getEncodedSize( &encoderObj, pBuffer ); + } + IOT_BLE_MESG_ENCODER.destroy( &encoderObj ); + error = IOT_SERIALIZER_SUCCESS; + } + + return error; +} + + +static IotSerializerError_t _serializeSubscribe( const IotMqttSubscription_t * const pSubscriptionList, + size_t subscriptionCount, + uint8_t * const pBuffer, + size_t * const pSize, + uint16_t packetIdentifier ) +{ + IotSerializerError_t error = IOT_SERIALIZER_SUCCESS; + IotSerializerEncoderObject_t encoderObj = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_STREAM ; + IotSerializerEncoderObject_t subscribeMap = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; + IotSerializerEncoderObject_t subscriptionArray = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_ARRAY; + IotSerializerScalarData_t data = { 0 }; + uint16_t idx; + + error = IOT_BLE_MESG_ENCODER.init( &encoderObj, pBuffer, *pSize ); + + if( error == IOT_SERIALIZER_SUCCESS ) + { + error = IOT_BLE_MESG_ENCODER.openContainer( + &encoderObj, + &subscribeMap, + _NUM_SUBACK_PARAMS ); + } + + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + data.type = IOT_SERIALIZER_SCALAR_SIGNED_INT; + data.value.u.signedInt = IOT_BLE_MQTT_MSG_TYPE_SUBSCRIBE; + error = IOT_BLE_MESG_ENCODER.appendKeyValue( &subscribeMap, IOT_BLE_MQTT_MSG_TYPE, data ); + } + + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + + error = IOT_BLE_MESG_ENCODER.openContainerWithKey( + &subscribeMap, + IOT_BLE_MQTT_TOPIC_LIST, + &subscriptionArray, + subscriptionCount ); + } + + for( idx = 0; idx < subscriptionCount; idx++ ) + { + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + data.type = IOT_SERIALIZER_SCALAR_TEXT_STRING; + data.value.u.string.pString = ( uint8_t * ) pSubscriptionList[ idx ].pTopicFilter; + data.value.u.string.length = pSubscriptionList[ idx ].topicFilterLength; + error = IOT_BLE_MESG_ENCODER.append( &subscriptionArray, data ); + } + else + { + break; + } + } + + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + error = IOT_BLE_MESG_ENCODER.closeContainer( &subscribeMap, &subscriptionArray ); + } + + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + + error = IOT_BLE_MESG_ENCODER.openContainerWithKey( + &subscribeMap, + IOT_BLE_MQTT_QOS_LIST, + &subscriptionArray, + subscriptionCount ); + } + + + for( idx = 0; idx < subscriptionCount; idx++ ) + { + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + + data.type = IOT_SERIALIZER_SCALAR_SIGNED_INT; + data.value.u.signedInt = pSubscriptionList[ idx ].qos; + error = IOT_BLE_MESG_ENCODER.append( &subscriptionArray, data ); + } + else + { + break; + } + } + + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + error = IOT_BLE_MESG_ENCODER.closeContainer( &subscribeMap, &subscriptionArray ); + } + + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + + data.type = IOT_SERIALIZER_SCALAR_SIGNED_INT; + data.value.u.signedInt = packetIdentifier; + error = IOT_BLE_MESG_ENCODER.appendKeyValue( &subscribeMap, IOT_BLE_MQTT_MESSAGE_ID, data ); + } + + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + error = IOT_BLE_MESG_ENCODER.closeContainer( &encoderObj, &subscribeMap ); + } + + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + if( pBuffer == NULL ) + { + *pSize = IOT_BLE_MESG_ENCODER.getExtraBufferSizeNeeded( &encoderObj ); + } + else + { + *pSize = IOT_BLE_MESG_ENCODER.getEncodedSize( &encoderObj, pBuffer ); + } + + IOT_BLE_MESG_ENCODER.destroy( &encoderObj ); + error = IOT_SERIALIZER_SUCCESS; + } + return error; +} + +static IotSerializerError_t _serializeUnSubscribe( const IotMqttSubscription_t * const pSubscriptionList, + size_t subscriptionCount, + uint8_t * const pBuffer, + size_t * const pSize, + uint16_t packetIdentifier ) +{ + IotSerializerError_t error = IOT_SERIALIZER_SUCCESS; + IotSerializerEncoderObject_t encoderObj = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_STREAM; + IotSerializerEncoderObject_t subscribeMap = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; + IotSerializerEncoderObject_t subscriptionArray = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_ARRAY; + IotSerializerScalarData_t data = { 0 }; + uint16_t idx; + + error = IOT_BLE_MESG_ENCODER.init( &encoderObj, pBuffer, *pSize ); + + if( error == IOT_SERIALIZER_SUCCESS ) + { + + error = IOT_BLE_MESG_ENCODER.openContainer( + &encoderObj, + &subscribeMap, + _NUM_UNSUBACK_PARAMS ); + } + + + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + data.type = IOT_SERIALIZER_SCALAR_SIGNED_INT; + data.value.u.signedInt = IOT_BLE_MQTT_MSG_TYPE_UNSUBSCRIBE; + error = IOT_BLE_MESG_ENCODER.appendKeyValue( &subscribeMap, IOT_BLE_MQTT_MSG_TYPE, data ); + } + + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + error = IOT_BLE_MESG_ENCODER.openContainerWithKey ( + &subscribeMap, + IOT_BLE_MQTT_TOPIC_LIST, + &subscriptionArray, + subscriptionCount ); + } + + for( idx = 0; idx < subscriptionCount; idx++ ) + { + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + data.type = IOT_SERIALIZER_SCALAR_TEXT_STRING; + data.value.u.string.pString = ( uint8_t * ) pSubscriptionList[ idx ].pTopicFilter; + data.value.u.string.length = pSubscriptionList[ idx ].topicFilterLength; + error = IOT_BLE_MESG_ENCODER.append( &subscriptionArray, data ); + } + else + { + break; + } + } + + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + error = IOT_BLE_MESG_ENCODER.closeContainer( &subscribeMap, &subscriptionArray ); + } + + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + data.type = IOT_SERIALIZER_SCALAR_SIGNED_INT; + data.value.u.signedInt = packetIdentifier; + error = IOT_BLE_MESG_ENCODER.appendKeyValue( &subscribeMap, IOT_BLE_MQTT_MESSAGE_ID, data ); + } + + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + error = IOT_BLE_MESG_ENCODER.closeContainer( &encoderObj, &subscribeMap ); + } + + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + if( pBuffer == NULL ) + { + *pSize = IOT_BLE_MESG_ENCODER.getExtraBufferSizeNeeded( &encoderObj ); + + } + else + { + *pSize = IOT_BLE_MESG_ENCODER.getEncodedSize( &encoderObj, pBuffer ); + } + IOT_BLE_MESG_ENCODER.destroy( &encoderObj ); + error = IOT_SERIALIZER_SUCCESS; + } + + return error; +} + +static IotSerializerError_t _serializeDisconnect( uint8_t * const pBuffer, + size_t * const pSize ) +{ + IotSerializerError_t error = IOT_SERIALIZER_SUCCESS; + IotSerializerEncoderObject_t encoderObj = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_STREAM; + IotSerializerEncoderObject_t disconnectMap = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; + IotSerializerScalarData_t data = { 0 }; + + error = IOT_BLE_MESG_ENCODER.init( &encoderObj, pBuffer, *pSize ); + + if( error == IOT_SERIALIZER_SUCCESS ) + { + error = IOT_BLE_MESG_ENCODER.openContainer( + &encoderObj, + &disconnectMap, + _NUM_DISCONNECT_PARAMS ); + } + + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + data.type = IOT_SERIALIZER_SCALAR_SIGNED_INT; + data.value.u.signedInt = IOT_BLE_MQTT_MSG_TYPE_DISCONNECT; + error = IOT_BLE_MESG_ENCODER.appendKeyValue( &disconnectMap, IOT_BLE_MQTT_MSG_TYPE, data ); + } + + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + error = IOT_BLE_MESG_ENCODER.closeContainer( &encoderObj, &disconnectMap ); + } + + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + if( pBuffer == NULL ) + { + *pSize = IOT_BLE_MESG_ENCODER.getExtraBufferSizeNeeded( &encoderObj ); + } + else + { + *pSize = IOT_BLE_MESG_ENCODER.getEncodedSize( &encoderObj, pBuffer ); + } + IOT_BLE_MESG_ENCODER.destroy( &encoderObj ); + error = IOT_SERIALIZER_SUCCESS; + } + + return error; +} + +static IotSerializerError_t _serializePingRequest( uint8_t * const pBuffer, + size_t * const pSize ) +{ + IotSerializerError_t error = IOT_SERIALIZER_SUCCESS; + IotSerializerEncoderObject_t encoderObj = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_STREAM; + IotSerializerEncoderObject_t pingRequest = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; + IotSerializerScalarData_t data = { 0 }; + + error = IOT_BLE_MESG_ENCODER.init( &encoderObj, pBuffer, *pSize ); + + if( error == IOT_SERIALIZER_SUCCESS ) + { + error = IOT_BLE_MESG_ENCODER.openContainer( + &encoderObj, + &pingRequest, + _NUM_PINGREQUEST_PARAMS ); + } + + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + data.type = IOT_SERIALIZER_SCALAR_SIGNED_INT; + data.value.u.signedInt = IOT_BLE_MQTT_MSG_TYPE_PINGREQ; + error = IOT_BLE_MESG_ENCODER.appendKeyValue( &pingRequest, IOT_BLE_MQTT_MSG_TYPE, data ); + } + + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + error = IOT_BLE_MESG_ENCODER.closeContainer( &encoderObj, &pingRequest ); + } + + if( _IS_VALID_SERIALIZER_RET( error, pBuffer ) ) + { + if( pBuffer == NULL ) + { + *pSize = IOT_BLE_MESG_ENCODER.getExtraBufferSizeNeeded( &encoderObj ); + } + else + { + *pSize = IOT_BLE_MESG_ENCODER.getEncodedSize( &encoderObj, pBuffer ); + } + IOT_BLE_MESG_ENCODER.destroy( &encoderObj ); + error = IOT_SERIALIZER_SUCCESS; + } + + return error; +} + + +bool IotBleMqtt_InitSerialize( void ) +{ + /* Create the packet identifier mutex. */ + return IotMutex_Create( &_packetIdentifierMutex, false ); +} + +void IotBleMqtt_CleanupSerialize( void ) +{ + /* Destroy the packet identifier mutex */ + IotMutex_Destroy( &_packetIdentifierMutex ); +} + + +IotMqttError_t IotBleMqtt_SerializeConnect( const IotMqttConnectInfo_t * const pConnectInfo, + uint8_t ** const pConnectPacket, + size_t * const pPacketSize ) +{ + uint8_t * pBuffer = NULL; + size_t bufLen = 0; + IotSerializerError_t error; + IotMqttError_t ret = IOT_MQTT_SUCCESS; + + + error = _serializeConnect( pConnectInfo, NULL, &bufLen ); + if( error != IOT_SERIALIZER_SUCCESS ) + { + IotLogError( "Failed to find length of serialized CONNECT message, error = %d", error ); + ret = IOT_MQTT_BAD_PARAMETER; + } + + if( ret == IOT_MQTT_SUCCESS ) + { + + pBuffer = IotMqtt_MallocMessage( bufLen ); + + /* If Memory cannot be allocated log an error and return */ + if( pBuffer == NULL ) + { + IotLogError( "Failed to allocate memory for CONNECT packet." ); + ret = IOT_MQTT_NO_MEMORY; + } + } + + if( ret == IOT_MQTT_SUCCESS ) + { + error = _serializeConnect( pConnectInfo, pBuffer, &bufLen ); + if( error != IOT_SERIALIZER_SUCCESS ) + { + IotLogError( "Failed to serialize CONNECT message, error = %d", error ); + ret = IOT_MQTT_BAD_PARAMETER; + } + } + + if( ret == IOT_MQTT_SUCCESS ) + { + *pConnectPacket = pBuffer; + *pPacketSize = bufLen; + } + else + { + *pConnectPacket = NULL; + *pPacketSize = 0; + if( pBuffer != NULL ) + { + IotMqtt_FreeMessage( pBuffer ); + } + } + + return ret; +} + +IotMqttError_t IotBleMqtt_DeserializeConnack( _mqttPacket_t * pConnack ) +{ + + IotSerializerDecoderObject_t decoderObj = { 0 }, decoderValue = { 0 }; + IotSerializerError_t error; + IotMqttError_t ret = IOT_MQTT_SUCCESS; + int64_t respCode = 0L; + + error = IOT_BLE_MESG_DECODER.init( &decoderObj, ( uint8_t * ) pConnack->pRemainingData, pConnack->remainingLength ); + if( ( error != IOT_SERIALIZER_SUCCESS ) + || ( decoderObj.type != IOT_SERIALIZER_CONTAINER_MAP ) ) + { + IotLogError( "Malformed CONNACK, decoding the packet failed, decoder error = %d, type: %d", error, decoderObj.type ); + ret = IOT_MQTT_BAD_RESPONSE; + } + + + if( ret == IOT_MQTT_SUCCESS ) + { + + error = IOT_BLE_MESG_DECODER.find( &decoderObj, IOT_BLE_MQTT_STATUS, &decoderValue ); + if ( ( error != IOT_SERIALIZER_SUCCESS ) || + ( decoderValue.type != IOT_SERIALIZER_SCALAR_SIGNED_INT ) ) + { + IotLogError( "Invalid CONNACK, response code decode failed, error = %d, decoded value type = %d", error, decoderValue.type ); + ret = IOT_MQTT_BAD_RESPONSE; + } + else + { + + respCode = decoderValue.u.value.u.signedInt; + if( ( respCode != IOT_BLE_MQTT_STATUS_CONNECTING ) + && ( respCode != IOT_BLE_MQTT_STATUS_CONNECTED ) ) + { + ret = IOT_MQTT_SERVER_REFUSED; + } + } + } + + IOT_BLE_MESG_DECODER.destroy( &decoderObj ); + + return ret; +} + +IotMqttError_t IotBleMqtt_SerializePublish( const IotMqttPublishInfo_t * const pPublishInfo, + uint8_t ** const pPublishPacket, + size_t * const pPacketSize, + uint16_t * const pPacketIdentifier, + uint8_t ** pPacketIdentifierHigh ) +{ + + uint8_t * pBuffer = NULL; + size_t bufLen = 0; + uint16_t usPacketIdentifier = 0; + IotSerializerError_t error; + IotMqttError_t ret = IOT_MQTT_SUCCESS; + + (void)pPacketIdentifierHigh; + + if( pPublishInfo->qos != 0 ) + { + usPacketIdentifier = _nextPacketIdentifier(); + } + + error = _serializePublish( pPublishInfo, NULL, &bufLen, usPacketIdentifier ); + if( error != IOT_SERIALIZER_SUCCESS ) + { + IotLogError( "Failed to find size of serialized PUBLISH message, error = %d", error ); + ret = IOT_MQTT_BAD_PARAMETER; + } + + if( ret == IOT_MQTT_SUCCESS ) + { + + pBuffer = IotMqtt_MallocMessage( bufLen ); + /* If Memory cannot be allocated log an error and return */ + if( pBuffer == NULL ) + { + IotLogError( "Failed to allocate memory for PUBLISH packet." ); + ret = IOT_MQTT_NO_MEMORY; + } + } + + if( ret == IOT_MQTT_SUCCESS ) + { + + error = _serializePublish( pPublishInfo, pBuffer, &bufLen, usPacketIdentifier ); + if( error != IOT_SERIALIZER_SUCCESS ) + { + IotLogError( "Failed to serialize PUBLISH message, error = %d", error ); + ret = IOT_MQTT_BAD_PARAMETER; + } + } + + if( ret == IOT_MQTT_SUCCESS ) + { + *pPublishPacket = pBuffer; + *pPacketSize = bufLen; + *pPacketIdentifier = usPacketIdentifier; + } + else + { + if( pBuffer != NULL ) + { + IotMqtt_FreeMessage( pBuffer ); + } + *pPublishPacket = NULL; + *pPacketSize = 0; + } + + return ret; +} + +void IotBleMqtt_PublishSetDup( uint8_t * const pPublishPacket, uint8_t * pPacketIdentifierHigh, uint16_t * const pNewPacketIdentifier ) +{ + /** TODO: Currently DUP flag is not supported by BLE SDKs **/ +} + +IotMqttError_t IotBleMqtt_DeserializePublish( _mqttPacket_t * pPublish ) +{ + + IotSerializerDecoderObject_t decoderObj = { 0 }, decoderValue = { 0 }; + IotSerializerError_t xSerializerRet; + IotMqttError_t ret = IOT_MQTT_SUCCESS; + + xSerializerRet = IOT_BLE_MESG_DECODER.init( &decoderObj, ( uint8_t * ) pPublish->pRemainingData, pPublish->remainingLength ); + if ( (xSerializerRet != IOT_SERIALIZER_SUCCESS ) || + ( decoderObj.type != IOT_SERIALIZER_CONTAINER_MAP ) ) + { + + IotLogError( "Decoding PUBLISH packet failed, decoder error = %d, object type = %d", xSerializerRet, decoderObj.type ); + ret = IOT_MQTT_BAD_RESPONSE; + } + + if( ret == IOT_MQTT_SUCCESS ) + { + xSerializerRet = IOT_BLE_MESG_DECODER.find( &decoderObj, IOT_BLE_MQTT_QOS, &decoderValue ); + if ( ( xSerializerRet != IOT_SERIALIZER_SUCCESS ) || + ( decoderValue.type != IOT_SERIALIZER_SCALAR_SIGNED_INT ) ) + { + IotLogError( "QOS Value decode failed, error = %d, decoded value type = %d", xSerializerRet, decoderValue.type ); + ret = IOT_MQTT_BAD_RESPONSE; + } + else + { + pPublish->u.pIncomingPublish->u.publish.publishInfo.qos = decoderValue.u.value.u.signedInt; + } + } + + if( ret == IOT_MQTT_SUCCESS ) + { + decoderValue.u.value.u.string.pString = NULL; + decoderValue.u.value.u.string.length = 0; + xSerializerRet = IOT_BLE_MESG_DECODER.find( &decoderObj, IOT_BLE_MQTT_TOPIC, &decoderValue ); + + if( ( xSerializerRet != IOT_SERIALIZER_SUCCESS ) || + ( decoderValue.type != IOT_SERIALIZER_SCALAR_TEXT_STRING ) ) + { + IotLogError( "Topic value decode failed, error = %d", xSerializerRet ); + ret = IOT_MQTT_BAD_RESPONSE; + } + else + { + pPublish->u.pIncomingPublish->u.publish.publishInfo.pTopicName = ( const char* ) decoderValue.u.value.u.string.pString; + pPublish->u.pIncomingPublish->u.publish.publishInfo.topicNameLength = decoderValue.u.value.u.string.length; + } + } + + if( ret == IOT_MQTT_SUCCESS ) + { + decoderValue.u.value.u.string.pString = NULL; + decoderValue.u.value.u.string.length = 0; + xSerializerRet = IOT_BLE_MESG_DECODER.find( &decoderObj, IOT_BLE_MQTT_PAYLOAD, &decoderValue ); + + if( ( xSerializerRet != IOT_SERIALIZER_SUCCESS ) || + ( decoderValue.type != IOT_SERIALIZER_SCALAR_BYTE_STRING ) ) + { + IotLogError( "Payload value decode failed, error = %d", xSerializerRet ); + ret = IOT_MQTT_BAD_RESPONSE; + } + else + { + pPublish->u.pIncomingPublish->u.publish.publishInfo.pPayload = ( const char* ) decoderValue.u.value.u.string.pString; + pPublish->u.pIncomingPublish->u.publish.publishInfo.payloadLength = decoderValue.u.value.u.string.length; + } + } + + if( ret == IOT_MQTT_SUCCESS ) + { + if( pPublish->u.pIncomingPublish->u.publish.publishInfo.qos != 0 ) + { + xSerializerRet = IOT_BLE_MESG_DECODER.find( &decoderObj, IOT_BLE_MQTT_MESSAGE_ID, &decoderValue ); + if ( ( xSerializerRet != IOT_SERIALIZER_SUCCESS ) || + ( decoderValue.type != IOT_SERIALIZER_SCALAR_SIGNED_INT ) ) + { + IotLogError( "Message identifier decode failed, error = %d, decoded value type = %d", xSerializerRet, decoderValue.type ); + ret = IOT_MQTT_BAD_RESPONSE; + } + else + { + pPublish->packetIdentifier = ( uint16_t ) decoderValue.u.value.u.signedInt; + } + } + } + + if( ret == IOT_MQTT_SUCCESS ) + { + pPublish->u.pIncomingPublish->u.publish.publishInfo.retain = false; + } + + IOT_BLE_MESG_DECODER.destroy( &decoderObj ); + + return ret; +} + +IotMqttError_t IotBleMqtt_SerializePuback( uint16_t packetIdentifier, + uint8_t ** const pPubackPacket, + size_t * const pPacketSize ) +{ + uint8_t * pBuffer = NULL; + size_t bufLen = 0; + IotSerializerError_t error; + IotMqttError_t ret = IOT_MQTT_SUCCESS; + + error = _serializePubAck( packetIdentifier, NULL, &bufLen ); + + if( error != IOT_SERIALIZER_SUCCESS ) + { + IotLogError( "Failed to find size of serialized PUBACK message, error = %d", error ); + ret = IOT_MQTT_BAD_PARAMETER; + } + + + if( ret == IOT_MQTT_SUCCESS ) + { + pBuffer = IotMqtt_MallocMessage( bufLen ); + + /* If Memory cannot be allocated log an error and return */ + if( pBuffer == NULL ) + { + IotLogError( "Failed to allocate memory for PUBACK packet, packet identifier = %d", packetIdentifier ); + ret = IOT_MQTT_NO_MEMORY; + } + } + + + if( ret == IOT_MQTT_SUCCESS ) + { + error = _serializePubAck( packetIdentifier, pBuffer, &bufLen ); + + if( error != IOT_SERIALIZER_SUCCESS ) + { + IotLogError( "Failed to find size of serialized PUBACK message, error = %d", error ); + ret = IOT_MQTT_BAD_PARAMETER; + } + } + + + if( ret == IOT_MQTT_SUCCESS ) + { + *pPubackPacket = pBuffer; + *pPacketSize = bufLen; + } + else + { + if( pBuffer != NULL ) + { + IotMqtt_FreeMessage( pBuffer ); + } + + *pPubackPacket = NULL; + *pPacketSize = 0; + } + + return ret; + +} + +IotMqttError_t IotBleMqtt_DeserializePuback( _mqttPacket_t * pPuback ) +{ + + IotSerializerDecoderObject_t decoderObj = { 0 }, decoderValue = { 0 }; + IotSerializerError_t error; + IotMqttError_t ret = IOT_MQTT_SUCCESS; + + error = IOT_BLE_MESG_DECODER.init( &decoderObj, ( uint8_t * ) pPuback->pRemainingData, pPuback->remainingLength ); + + if ( ( error != IOT_SERIALIZER_SUCCESS ) + || ( decoderObj.type != IOT_SERIALIZER_CONTAINER_MAP ) ) + { + IotLogError( "Malformed PUBACK, decoding the packet failed, decoder error = %d, object type: %d", error, decoderObj.type ); + ret = IOT_MQTT_BAD_RESPONSE; + } + + + if( ret == IOT_MQTT_SUCCESS ) + { + + error = IOT_BLE_MESG_DECODER.find( &decoderObj, IOT_BLE_MQTT_MESSAGE_ID, &decoderValue ); + + if ( ( error != IOT_SERIALIZER_SUCCESS ) || + ( decoderValue.type != IOT_SERIALIZER_SCALAR_SIGNED_INT ) ) + { + IotLogError( "Message ID decode failed, error = %d, decoded value type = %d", error, decoderValue.type ); + ret = IOT_MQTT_BAD_RESPONSE; + } + else + { + pPuback->packetIdentifier = ( uint16_t ) decoderValue.u.value.u.signedInt; + } + } + + IOT_BLE_MESG_DECODER.destroy( &decoderObj ); + + return ret; + +} + +IotMqttError_t IotBleMqtt_SerializeSubscribe( const IotMqttSubscription_t * const pSubscriptionList, + size_t subscriptionCount, + uint8_t ** const pSubscribePacket, + size_t * const pPacketSize, + uint16_t * const pPacketIdentifier ) +{ + uint8_t * pBuffer = NULL; + size_t bufLen = 0; + uint16_t usPacketIdentifier = 0; + IotSerializerError_t error; + IotMqttError_t ret = IOT_MQTT_SUCCESS; + + usPacketIdentifier = _nextPacketIdentifier(); + + error = _serializeSubscribe( pSubscriptionList, subscriptionCount, NULL, &bufLen, usPacketIdentifier ); + if( error != IOT_SERIALIZER_SUCCESS ) + { + IotLogError( "Failed to find serialized length of SUBSCRIBE message, error = %d", error ); + ret = IOT_MQTT_BAD_PARAMETER; + } + + if( ret == IOT_MQTT_SUCCESS ) + { + pBuffer = IotMqtt_MallocMessage( bufLen ); + + /* If Memory cannot be allocated log an error and return */ + if( pBuffer == NULL ) + { + IotLogError( "Failed to allocate memory for SUBSCRIBE message." ); + ret = IOT_MQTT_NO_MEMORY; + } + } + + if( ret == IOT_MQTT_SUCCESS ) + { + error = _serializeSubscribe( pSubscriptionList, subscriptionCount, pBuffer, &bufLen, usPacketIdentifier ); + if( error != IOT_SERIALIZER_SUCCESS ) + { + IotLogError( "Failed to serialize SUBSCRIBE message, error = %d", error ); + ret = IOT_MQTT_BAD_PARAMETER; + } + } + + if( ret == IOT_MQTT_SUCCESS ) + { + *pSubscribePacket = pBuffer; + *pPacketSize = bufLen; + *pPacketIdentifier = usPacketIdentifier; + } + else + { + if( pBuffer != NULL ) + { + IotMqtt_FreeMessage( pBuffer ); + } + + *pSubscribePacket = NULL; + *pPacketSize = 0; + } + + return ret; +} + +IotMqttError_t IotBleMqtt_DeserializeSuback( _mqttPacket_t * pSuback ) +{ + + IotSerializerDecoderObject_t decoderObj = { 0 }, decoderValue = { 0 }; + IotSerializerError_t error; + int64_t subscriptionStatus; + IotMqttError_t ret = IOT_MQTT_SUCCESS; + + error = IOT_BLE_MESG_DECODER.init( &decoderObj, ( uint8_t * ) pSuback->pRemainingData, pSuback->remainingLength ); + + if ( ( error != IOT_SERIALIZER_SUCCESS ) + || ( decoderObj.type != IOT_SERIALIZER_CONTAINER_MAP ) ) + { + IotLogError( "Malformed SUBACK, decoding the packet failed, decoder error = %d, type: %d", error, decoderObj.type ); + ret = IOT_MQTT_BAD_RESPONSE; + } + + if( ret == IOT_MQTT_SUCCESS ) + { + + error = IOT_BLE_MESG_DECODER.find( &decoderObj, IOT_BLE_MQTT_MESSAGE_ID, &decoderValue ); + if ( ( error != IOT_SERIALIZER_SUCCESS ) || + ( decoderValue.type != IOT_SERIALIZER_SCALAR_SIGNED_INT ) ) + { + IotLogError( "Message ID decode failed, error = %d, decoded value type = %d", error, decoderValue.type ); + ret = IOT_MQTT_BAD_RESPONSE; + } + else + { + pSuback->packetIdentifier = ( uint16_t ) decoderValue.u.value.u.signedInt; + } + } + + if( ret == IOT_MQTT_SUCCESS ) + { + error = IOT_BLE_MESG_DECODER.find( &decoderObj, IOT_BLE_MQTT_STATUS, &decoderValue ); + if ( ( error != IOT_SERIALIZER_SUCCESS ) || + ( decoderValue.type != IOT_SERIALIZER_SCALAR_SIGNED_INT ) ) + { + IotLogError( "Status code decode failed, error = %d, decoded value type = %d", error, decoderValue.type ); + ret = IOT_MQTT_BAD_RESPONSE; + } + else + { + subscriptionStatus = ( uint16_t ) decoderValue.u.value.u.signedInt; + switch( subscriptionStatus ) + { + case 0x00: + case 0x01: + case 0x02: + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Topic accepted, max QoS %hhu.", subscriptionStatus ); + ret = IOT_MQTT_SUCCESS; + break; + case 0x80: + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Topic refused." ); + + /* Remove a rejected subscription from the subscription manager. */ + _IotMqtt_RemoveSubscriptionByPacket( + pSuback->u.pMqttConnection, + pSuback->packetIdentifier, + 0 ); + ret = IOT_MQTT_SERVER_REFUSED; + break; + default: + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Bad SUBSCRIBE status %hhu.", subscriptionStatus ); + + ret = IOT_MQTT_BAD_RESPONSE; + break; + } + } + + } + + IOT_BLE_MESG_DECODER.destroy( &decoderObj ); + + return ret; +} + +IotMqttError_t IotBleMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * const pSubscriptionList, + size_t subscriptionCount, + uint8_t ** const pUnsubscribePacket, + size_t * const pPacketSize, + uint16_t * const pPacketIdentifier ) +{ + + uint8_t * pBuffer = NULL; + size_t bufLen = 0; + uint16_t usPacketIdentifier = 0; + IotSerializerError_t error; + IotMqttError_t ret = IOT_MQTT_SUCCESS; + + usPacketIdentifier = _nextPacketIdentifier(); + + error = _serializeUnSubscribe( pSubscriptionList, subscriptionCount, NULL, &bufLen, usPacketIdentifier ); + if( error != IOT_SERIALIZER_SUCCESS ) + { + IotLogError( "Failed to find serialized length of UNSUBSCRIBE message, error = %d", error ); + ret = IOT_MQTT_BAD_PARAMETER; + } + + if( ret == IOT_MQTT_SUCCESS ) + { + pBuffer = IotMqtt_MallocMessage( bufLen ); + + /* If Memory cannot be allocated log an error and return */ + if( pBuffer == NULL ) + { + IotLogError( "Failed to allocate memory for UNSUBSCRIBE message." ); + ret = IOT_MQTT_NO_MEMORY; + } + } + + if( ret == IOT_MQTT_SUCCESS ) + { + error = _serializeUnSubscribe( pSubscriptionList, subscriptionCount, pBuffer, &bufLen, usPacketIdentifier ); + if( error != IOT_SERIALIZER_SUCCESS ) + { + IotLogError( "Failed to serialize UNSUBSCRIBE message, error = %d", error ); + ret = IOT_MQTT_BAD_PARAMETER; + } + } + + if( ret == IOT_MQTT_SUCCESS ) + { + *pUnsubscribePacket = pBuffer; + *pPacketSize = bufLen; + *pPacketIdentifier = usPacketIdentifier; + } + else + { + if( pBuffer != NULL ) + { + IotMqtt_FreeMessage( pBuffer ); + } + + *pUnsubscribePacket = NULL; + *pPacketSize = 0; + } + + return ret; +} + +IotMqttError_t IotBleMqtt_DeserializeUnsuback( _mqttPacket_t * pUnsuback ) +{ + IotSerializerDecoderObject_t decoderObj = { 0 }, decoderValue = { 0 }; + IotSerializerError_t error; + IotMqttError_t ret = IOT_MQTT_SUCCESS; + + error = IOT_BLE_MESG_DECODER.init( &decoderObj, ( uint8_t * ) pUnsuback->pRemainingData, pUnsuback->remainingLength ); + if( ( error != IOT_SERIALIZER_SUCCESS ) + || ( decoderObj.type != IOT_SERIALIZER_CONTAINER_MAP ) ) + { + IotLogError( "Malformed UNSUBACK, decoding the packet failed, decoder error = %d, type:%d ", error, decoderObj.type ); + ret = IOT_MQTT_BAD_RESPONSE; + } + + if( ret == IOT_MQTT_SUCCESS ) + { + error = IOT_BLE_MESG_DECODER.find( &decoderObj, IOT_BLE_MQTT_MESSAGE_ID, &decoderValue ); + if ( ( error != IOT_SERIALIZER_SUCCESS ) || + ( decoderValue.type != IOT_SERIALIZER_SCALAR_SIGNED_INT ) ) + { + IotLogError( "UNSUBACK Message identifier decode failed, error = %d, decoded value type = %d", error, decoderValue.type ); + ret = IOT_MQTT_BAD_RESPONSE; + + } + else + { + pUnsuback->packetIdentifier = ( uint16_t ) decoderValue.u.value.u.signedInt; + } + } + + IOT_BLE_MESG_DECODER.destroy( &decoderObj ); + + return IOT_MQTT_SUCCESS; +} + +IotMqttError_t IotBleMqtt_SerializeDisconnect( uint8_t ** const pDisconnectPacket, + size_t * const pPacketSize ) +{ + uint8_t *pBuffer = NULL; + size_t bufLen = 0; + IotSerializerError_t error; + IotMqttError_t ret = IOT_MQTT_SUCCESS; + + error = _serializeDisconnect( NULL, &bufLen); + if( error != IOT_SERIALIZER_SUCCESS ) + { + IotLogError( "Failed to find serialized length of DISCONNECT message, error = %d", error ); + ret = IOT_MQTT_BAD_PARAMETER; + } + + if( ret == IOT_MQTT_SUCCESS ) + { + pBuffer = IotMqtt_MallocMessage( bufLen ); + + /* If Memory cannot be allocated log an error and return */ + if( pBuffer == NULL ) + { + IotLogError( "Failed to allocate memory for DISCONNECT message." ); + ret = IOT_MQTT_NO_MEMORY; + } + } + + + if( ret == IOT_MQTT_SUCCESS ) + { + error = _serializeDisconnect( pBuffer, &bufLen ); + if( error != IOT_SERIALIZER_SUCCESS ) + { + IotLogError( "Failed to serialize DISCONNECT message, error = %d", error ); + ret = IOT_MQTT_BAD_PARAMETER; + } + } + + if( ret == IOT_MQTT_SUCCESS ) + { + *pDisconnectPacket = pBuffer; + *pPacketSize = bufLen; + } + else + { + if( pBuffer != NULL ) + { + IotMqtt_FreeMessage( pBuffer ); + } + + *pDisconnectPacket = NULL; + *pPacketSize = 0; + } + + return ret; +} + +size_t IotBleMqtt_GetRemainingLength ( void * pNetworkConnection, + const IotNetworkInterface_t * pNetworkInterface ) +{ + const uint8_t *pBuffer; + size_t length; + + IotBleDataTransfer_PeekReceiveBuffer( *( IotBleDataTransferChannel_t ** ) ( pNetworkConnection ), &pBuffer, &length ); + + return length; +} + + +uint8_t IotBleMqtt_GetPacketType( void * pNetworkConnection, const IotNetworkInterface_t * pNetworkInterface ) +{ + + IotSerializerDecoderObject_t decoderObj = { 0 }, decoderValue = { 0 }; + IotSerializerError_t error; + uint8_t value = 0xFF, packetType = _INVALID_MQTT_PACKET_TYPE; + const uint8_t *pBuffer; + size_t length; + + IotBleDataTransfer_PeekReceiveBuffer( *( IotBleDataTransferChannel_t ** ) ( pNetworkConnection ), &pBuffer, &length ); + + error = IOT_BLE_MESG_DECODER.init( &decoderObj, pBuffer, length ); + + if( ( error == IOT_SERIALIZER_SUCCESS ) + && ( decoderObj.type == IOT_SERIALIZER_CONTAINER_MAP ) ) + { + + error = IOT_BLE_MESG_DECODER.find( &decoderObj, IOT_BLE_MQTT_MSG_TYPE, &decoderValue ); + + if ( ( error == IOT_SERIALIZER_SUCCESS ) && + ( decoderValue.type == IOT_SERIALIZER_SCALAR_SIGNED_INT ) ) + { + value = ( uint16_t ) decoderValue.u.value.u.signedInt; + + /** Left shift by 4 bits as MQTT library expects packet type to be upper 4 bits **/ + packetType = value << 4; + } + else + { + IotLogError( "Packet type decode failed, error = %d, decoded value type = %d", error, decoderValue.type ); + } + } + else + { + IotLogError( "Decoding the packet failed, decoder error = %d, type = %d", error, decoderObj.type ); + } + + IOT_BLE_MESG_DECODER.destroy( &decoderObj ); + + return packetType; +} + +IotMqttError_t IotBleMqtt_SerializePingreq( uint8_t ** const pPingreqPacket, + size_t * const pPacketSize ) +{ + uint8_t *pBuffer = NULL; + size_t bufLen = 0; + IotSerializerError_t error; + IotMqttError_t ret = IOT_MQTT_SUCCESS; + + error = _serializePingRequest( NULL, &bufLen); + if( error != IOT_SERIALIZER_SUCCESS ) + { + IotLogError( "Failed to find serialized length of DISCONNECT message, error = %d", error ); + ret = IOT_MQTT_BAD_PARAMETER; + } + + if( ret == IOT_MQTT_SUCCESS ) + { + pBuffer = IotMqtt_MallocMessage( bufLen ); + + /* If Memory cannot be allocated log an error and return */ + if( pBuffer == NULL ) + { + IotLogError( "Failed to allocate memory for DISCONNECT message." ); + ret = IOT_MQTT_NO_MEMORY; + } + } + + if( ret == IOT_MQTT_SUCCESS ) + { + error = _serializePingRequest( pBuffer, &bufLen ); + if( error != IOT_SERIALIZER_SUCCESS ) + { + IotLogError( "Failed to serialize DISCONNECT message, error = %d", error ); + ret = IOT_MQTT_BAD_PARAMETER; + } + } + + if( ret == IOT_MQTT_SUCCESS ) + { + *pPingreqPacket = pBuffer; + *pPacketSize = bufLen; + } + else + { + if( pBuffer != NULL ) + { + IotMqtt_FreeMessage( pBuffer ); + } + + *pPingreqPacket = NULL; + *pPacketSize = 0; + } + + return ret; + +} + +IotMqttError_t IotBleMqtt_DeserializePingresp( _mqttPacket_t * pPingresp ) +{ + /* Ping Response for BLE contains only packet type field in CBOR, which is already decoded + in IotBleMqtt_GetPacketType() function. Returning IOT_MQTT_SUCCESS. */ + return IOT_MQTT_SUCCESS; +} + +void IotBleMqtt_FreePacket( uint8_t * pPacket ) +{ + IotMqtt_FreeMessage( pPacket ); +} diff --git a/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/src/iot_mqtt_agent.c b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/src/iot_mqtt_agent.c new file mode 100644 index 000000000..9a73b2587 --- /dev/null +++ b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/src/iot_mqtt_agent.c @@ -0,0 +1,794 @@ +/* + * Amazon FreeRTOS MQTT V2.0.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +/** + * @file iot_mqtt_agent.c + * @brief MQTT Agent implementation. Provides backwards compatibility between + * MQTT v2 and MQTT v1. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Standard includes. */ +#include + +/* FreeRTOS includes. */ +#include "FreeRTOS.h" +#include "semphr.h" + +/* MQTT v1 includes. */ +#include "iot_mqtt_agent.h" +#include "iot_mqtt_agent_config.h" +#include "iot_mqtt_agent_config_defaults.h" + +/* MQTT v2 include. */ +#include "iot_mqtt.h" + +/* Platform network include. */ +#include "platform/iot_network_freertos.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief Converts FreeRTOS ticks to milliseconds. + */ +#define mqttTICKS_TO_MS( xTicks ) ( xTicks * 1000 / configTICK_RATE_HZ ) + +/*-----------------------------------------------------------*/ + +/** + * @brief Stores data to convert between the MQTT v1 subscription callback + * and the MQTT v2 subscription callback. + */ +#if ( mqttconfigENABLE_SUBSCRIPTION_MANAGEMENT == 1 ) + typedef struct MQTTCallback + { + BaseType_t xInUse; /**< Whether this instance is in-use. */ + MQTTPublishCallback_t xFunction; /**< MQTT v1 callback function. */ + void * pvParameter; /**< Parameter to xFunction. */ + + uint16_t usTopicFilterLength; /**< Length of pcTopicFilter. */ + char pcTopicFilter[ mqttconfigSUBSCRIPTION_MANAGER_MAX_TOPIC_LENGTH ]; /**< Topic filter. */ + } MQTTCallback_t; +#endif + +/** + * @brief Stores data on an active MQTT connection. + */ +typedef struct MQTTConnection +{ + IotMqttConnection_t xMQTTConnection; /**< MQTT v2 connection handle. */ + MQTTAgentCallback_t pxCallback; /**< MQTT v1 global callback. */ + void * pvUserData; /**< Parameter to pxCallback. */ + StaticSemaphore_t xConnectionMutex; /**< Protects from concurrent accesses. */ + #if ( mqttconfigENABLE_SUBSCRIPTION_MANAGEMENT == 1 ) + MQTTCallback_t xCallbacks /**< Conversion table of MQTT v1 to MQTT v2 subscription callbacks. */ + [ mqttconfigSUBSCRIPTION_MANAGER_MAX_SUBSCRIPTIONS ]; + #endif +} MQTTConnection_t; + +/*-----------------------------------------------------------*/ + +/** + * @brief Convert an MQTT v2 return code to an MQTT v1 return code. + * + * @param[in] xMqttStatus The MQTT v2 return code. + * + * @return An equivalent MQTT v1 return code. + */ +static inline MQTTAgentReturnCode_t prvConvertReturnCode( IotMqttError_t xMqttStatus ); + +/** + * @brief Wraps an MQTT v1 publish callback. + * + * @param[in] pvParameter The MQTT connection. + * @param[in] pxPublish Information about the incoming publish. + */ +static void prvPublishCallbackWrapper( void * pvParameter, + IotMqttCallbackParam_t * const pxPublish ); + +/** + * @brief Wraps an MQTT v1 disconnect callback. + * + * @param[in] pvCallbackContext The MQTT connection. + * @param[in] pxDisconnect Information about the disconnect. + */ +static void prvDisconnectCallbackWrapper( void * pvParameter, + IotMqttCallbackParam_t * pxDisconnect ); + +#if ( mqttconfigENABLE_SUBSCRIPTION_MANAGEMENT == 1 ) + +/** + * @brief Store an MQTT v1 callback in the conversion table. + * + * @param[in] pxConnection Where to store the callback. + * @param[in] pcTopicFilter Topic filter to store. + * @param[in] usTopicFilterLength Length of pcTopicFilter. + * @param[in] xCallback MQTT v1 callback to store. + * @param[in] pvParameter Parameter to xCallback. + * + * @return pdPASS if the callback was successfully stored; pdFAIL otherwise. + */ + static BaseType_t prvStoreCallback( MQTTConnection_t * const pxConnection, + const char * const pcTopicFilter, + uint16_t usTopicFilterLength, + MQTTPublishCallback_t xCallback, + void * pvParameter ); + +/** + * @brief Search the callback conversion table for the given topic filter. + * + * @param[in] pxConnection The connection containing the conversion table. + * @param[in] pcTopicFilter The topic filter to search for. + * @param[in] usTopicFilterLength The length of pcTopicFilter. + * + * @return A pointer to the callback entry if found; NULL otherwise. + * @note This function should be called with pxConnection->xConnectionMutex + * locked. + */ + static MQTTCallback_t * prvFindCallback( MQTTConnection_t * const pxConnection, + const char * const pcTopicFilter, + uint16_t usTopicFilterLength ); + +/** + * @brief Remove a topic filter from the callback conversion table. + * + * @param[in] pxConnection The connection containing the conversion table. + * @param[in] pcTopicFilter The topic filter to remove. + * @param[in] usTopicFilterLength The length of pcTopic. + */ + static void prvRemoveCallback( MQTTConnection_t * const pxConnection, + const char * const pcTopicFilter, + uint16_t usTopicFilterLength ); +#endif /* if ( mqttconfigENABLE_SUBSCRIPTION_MANAGEMENT == 1 ) */ + +/*-----------------------------------------------------------*/ + +/** + * @brief The number of available MQTT brokers, controlled by the constant + * mqttconfigMAX_BROKERS; + */ +static UBaseType_t uxAvailableBrokers = mqttconfigMAX_BROKERS; + +/*-----------------------------------------------------------*/ + +static inline MQTTAgentReturnCode_t prvConvertReturnCode( IotMqttError_t xMqttStatus ) +{ + MQTTAgentReturnCode_t xStatus = eMQTTAgentSuccess; + + switch( xMqttStatus ) + { + case IOT_MQTT_SUCCESS: + case IOT_MQTT_STATUS_PENDING: + xStatus = eMQTTAgentSuccess; + break; + + case IOT_MQTT_TIMEOUT: + xStatus = eMQTTAgentTimeout; + break; + + default: + xStatus = eMQTTAgentFailure; + break; + } + + return xStatus; +} + +/*-----------------------------------------------------------*/ + +static void prvPublishCallbackWrapper( void * pvParameter, + IotMqttCallbackParam_t * const pxPublish ) +{ + BaseType_t xStatus = pdPASS; + size_t xBufferSize = 0; + uint8_t * pucMqttBuffer = NULL; + MQTTBool_t xCallbackReturn = eMQTTFalse; + MQTTConnection_t * pxConnection = ( MQTTConnection_t * ) pvParameter; + MQTTAgentCallbackParams_t xPublishData = { .xMQTTEvent = eMQTTAgentPublish }; + + /* Calculate the size of the MQTT buffer that must be allocated. */ + if( xStatus == pdPASS ) + { + xBufferSize = pxPublish->u.message.info.topicNameLength + + pxPublish->u.message.info.payloadLength; + + /* Check for overflow. */ + if( ( xBufferSize < pxPublish->u.message.info.topicNameLength ) || + ( xBufferSize < pxPublish->u.message.info.payloadLength ) ) + { + mqttconfigDEBUG_LOG( ( "Incoming PUBLISH message and topic name length too large.\r\n" ) ); + xStatus = pdFAIL; + } + } + + /* Allocate an MQTT buffer for the callback. */ + if( xStatus == pdPASS ) + { + pucMqttBuffer = pvPortMalloc( xBufferSize ); + + if( pucMqttBuffer == NULL ) + { + mqttconfigDEBUG_LOG( ( "Failed to allocate memory for MQTT buffer.\r\n" ) ); + xStatus = pdFAIL; + } + else + { + /* Copy the topic name and payload. The topic name and payload must be + * copied in case the user decides to take ownership of the MQTT buffer. + * The original buffer containing the MQTT topic name and payload may + * contain further unprocessed packets and must remain property of the + * MQTT library. Therefore, the topic name and payload are copied into + * another buffer for the user. */ + ( void ) memcpy( pucMqttBuffer, + pxPublish->u.message.info.pTopicName, + pxPublish->u.message.info.topicNameLength ); + ( void ) memcpy( pucMqttBuffer + pxPublish->u.message.info.topicNameLength, + pxPublish->u.message.info.pPayload, + pxPublish->u.message.info.payloadLength ); + + /* Set the members of the callback parameter. */ + xPublishData.xMQTTEvent = eMQTTAgentPublish; + xPublishData.u.xPublishData.pucTopic = pucMqttBuffer; + xPublishData.u.xPublishData.usTopicLength = pxPublish->u.message.info.topicNameLength; + xPublishData.u.xPublishData.pvData = pucMqttBuffer + pxPublish->u.message.info.topicNameLength; + xPublishData.u.xPublishData.ulDataLength = ( uint32_t ) pxPublish->u.message.info.payloadLength; + xPublishData.u.xPublishData.xQos = ( MQTTQoS_t ) pxPublish->u.message.info.qos; + xPublishData.u.xPublishData.xBuffer = pucMqttBuffer; + } + } + + if( xStatus == pdPASS ) + { + #if ( mqttconfigENABLE_SUBSCRIPTION_MANAGEMENT == 1 ) + /* When subscription management is enabled, search for a matching subscription. */ + MQTTCallback_t * pxCallbackEntry = prvFindCallback( pxConnection, + pxPublish->u.message.pTopicFilter, + pxPublish->u.message.topicFilterLength ); + + /* Check if a matching MQTT v1 subscription was found. */ + if( pxCallbackEntry != NULL ) + { + /* Invoke the topic-specific callback if it exists. */ + if( pxCallbackEntry->xFunction != NULL ) + { + xCallbackReturn = pxCallbackEntry->xFunction( pxCallbackEntry->pvParameter, + &( xPublishData.u.xPublishData ) ); + } + else + { + /* Otherwise, invoke the global callback. */ + if( pxConnection->pxCallback != NULL ) + { + xCallbackReturn = ( MQTTBool_t ) pxConnection->pxCallback( pxConnection->pvUserData, + &xPublishData ); + } + } + } + #else /* if ( mqttconfigENABLE_SUBSCRIPTION_MANAGEMENT == 1 ) */ + + /* When subscription management is disabled, invoke the global callback + * if one exists. */ + + /* When subscription management is disabled, the topic filter must be "#". */ + mqttconfigASSERT( *( xPublish.message.pTopicFilter ) == '#' ); + mqttconfigASSERT( xPublish.message.topicFilterLength == 1 ); + + if( pxConnection->pxCallback != NULL ) + { + xCallbackReturn = ( MQTTBool_t ) pxConnection->pxCallback( pxConnection->pvUserData, + &xPublishData ); + } + #endif /* if ( mqttconfigENABLE_SUBSCRIPTION_MANAGEMENT == 1 ) */ + } + + /* Free the MQTT buffer if the user did not take ownership of it. */ + if( ( xCallbackReturn == eMQTTFalse ) && ( pucMqttBuffer != NULL ) ) + { + vPortFree( pucMqttBuffer ); + } +} + +/*-----------------------------------------------------------*/ + +static void prvDisconnectCallbackWrapper( void * pvParameter, + IotMqttCallbackParam_t * pxDisconnect ) +{ + MQTTConnection_t * pxConnection = ( MQTTConnection_t * ) pvParameter; + MQTTAgentCallbackParams_t xCallbackParams = { .xMQTTEvent = eMQTTAgentDisconnect }; + + ( void ) pxDisconnect; + + /* This function should only be called if a callback was set. */ + mqttconfigASSERT( pxConnection->pxCallback != NULL ); + + /* Invoke the MQTT v1 callback. Ignore the return value. */ + pxConnection->pxCallback( pxConnection->pvUserData, + &xCallbackParams ); +} + +/*-----------------------------------------------------------*/ + +#if ( mqttconfigENABLE_SUBSCRIPTION_MANAGEMENT == 1 ) + static BaseType_t prvStoreCallback( MQTTConnection_t * const pxConnection, + const char * const pcTopicFilter, + uint16_t usTopicFilterLength, + MQTTPublishCallback_t xCallback, + void * pvParameter ) + { + MQTTCallback_t * pxCallback = NULL; + BaseType_t xStatus = pdFAIL, i = 0; + + /* Prevent other tasks from modifying stored callbacks while this function + * runs. */ + if( xSemaphoreTake( ( QueueHandle_t ) &( pxConnection->xConnectionMutex ), + portMAX_DELAY ) == pdTRUE ) + { + /* Check if the topic filter already has an entry. */ + pxCallback = prvFindCallback( pxConnection, pcTopicFilter, usTopicFilterLength ); + + if( pxCallback == NULL ) + { + /* If no entry was found, find a free entry. */ + for( i = 0; i < mqttconfigSUBSCRIPTION_MANAGER_MAX_SUBSCRIPTIONS; i++ ) + { + if( pxConnection->xCallbacks[ i ].xInUse == pdFALSE ) + { + pxConnection->xCallbacks[ i ].xInUse = pdTRUE; + pxCallback = &( pxConnection->xCallbacks[ i ] ); + break; + } + } + } + + /* Set the members of the callback entry. */ + if( i < mqttconfigSUBSCRIPTION_MANAGER_MAX_SUBSCRIPTIONS ) + { + pxCallback->pvParameter = pvParameter; + pxCallback->usTopicFilterLength = usTopicFilterLength; + pxCallback->xFunction = xCallback; + ( void ) strncpy( pxCallback->pcTopicFilter, pcTopicFilter, usTopicFilterLength ); + xStatus = pdPASS; + } + + ( void ) xSemaphoreGive( ( QueueHandle_t ) &( pxConnection->xConnectionMutex ) ); + } + + return xStatus; + } + +/*-----------------------------------------------------------*/ + + static MQTTCallback_t * prvFindCallback( MQTTConnection_t * const pxConnection, + const char * const pcTopicFilter, + uint16_t usTopicFilterLength ) + { + BaseType_t i = 0; + MQTTCallback_t * pxResult = NULL; + + /* Search the callback conversion table for the topic filter. */ + for( i = 0; i < mqttconfigSUBSCRIPTION_MANAGER_MAX_SUBSCRIPTIONS; i++ ) + { + if( ( pxConnection->xCallbacks[ i ].usTopicFilterLength == usTopicFilterLength ) && + ( strncmp( pxConnection->xCallbacks[ i ].pcTopicFilter, + pcTopicFilter, + usTopicFilterLength ) == 0 ) ) + { + pxResult = &( pxConnection->xCallbacks[ i ] ); + break; + } + } + + return pxResult; + } + +/*-----------------------------------------------------------*/ + + static void prvRemoveCallback( MQTTConnection_t * const pxConnection, + const char * const pcTopicFilter, + uint16_t usTopicFilterLength ) + { + MQTTCallback_t * pxCallback = NULL; + + /* Prevent other tasks from modifying stored callbacks while this function + * runs. */ + if( xSemaphoreTake( ( QueueHandle_t ) &( pxConnection->xConnectionMutex ), + portMAX_DELAY ) == pdTRUE ) + { + /* Find the given topic filter. */ + pxCallback = prvFindCallback( pxConnection, pcTopicFilter, usTopicFilterLength ); + + if( pxCallback != NULL ) + { + /* Clear the callback entry. */ + mqttconfigASSERT( pxCallback->xInUse == pdTRUE ); + ( void ) memset( pxCallback, 0x00, sizeof( MQTTCallback_t ) ); + } + + ( void ) xSemaphoreGive( ( QueueHandle_t ) &( pxConnection->xConnectionMutex ) ); + } + } +#endif /* if ( mqttconfigENABLE_SUBSCRIPTION_MANAGEMENT == 1 ) */ + +/*-----------------------------------------------------------*/ + +IotMqttConnection_t MQTT_AGENT_Getv2Connection( MQTTAgentHandle_t xMQTTHandle ) +{ + MQTTConnection_t * pxConnection = ( MQTTConnection_t * ) xMQTTHandle; + + return pxConnection->xMQTTConnection; +} + +/*-----------------------------------------------------------*/ + +BaseType_t MQTT_AGENT_Init( void ) +{ + BaseType_t xStatus = pdFALSE; + + /* Call the initialization function of MQTT v2. */ + if( IotMqtt_Init() == IOT_MQTT_SUCCESS ) + { + xStatus = pdTRUE; + } + + return xStatus; +} + +/*-----------------------------------------------------------*/ + +MQTTAgentReturnCode_t MQTT_AGENT_Create( MQTTAgentHandle_t * const pxMQTTHandle ) +{ + MQTTConnection_t * pxNewConnection = NULL; + MQTTAgentReturnCode_t xStatus = eMQTTAgentSuccess; + + /* Check how many brokers are available; fail if all brokers are in use. */ + taskENTER_CRITICAL(); + { + if( uxAvailableBrokers == 0 ) + { + xStatus = eMQTTAgentFailure; + } + else + { + uxAvailableBrokers--; + mqttconfigASSERT( uxAvailableBrokers <= mqttconfigMAX_BROKERS ); + } + } + taskEXIT_CRITICAL(); + + /* Allocate memory for an MQTT connection. */ + if( xStatus == eMQTTAgentSuccess ) + { + pxNewConnection = pvPortMalloc( sizeof( MQTTConnection_t ) ); + + if( pxNewConnection == NULL ) + { + xStatus = eMQTTAgentFailure; + + taskENTER_CRITICAL(); + { + uxAvailableBrokers++; + mqttconfigASSERT( uxAvailableBrokers <= mqttconfigMAX_BROKERS ); + } + taskEXIT_CRITICAL(); + } + else + { + ( void ) memset( pxNewConnection, 0x00, sizeof( MQTTConnection_t ) ); + pxNewConnection->xMQTTConnection = IOT_MQTT_CONNECTION_INITIALIZER; + } + } + + /* Create the connection mutex and set the output parameter. */ + if( xStatus == eMQTTAgentSuccess ) + { + ( void ) xSemaphoreCreateMutexStatic( &( pxNewConnection->xConnectionMutex ) ); + *pxMQTTHandle = ( MQTTAgentHandle_t ) pxNewConnection; + } + + return xStatus; +} + +/*-----------------------------------------------------------*/ + +MQTTAgentReturnCode_t MQTT_AGENT_Delete( MQTTAgentHandle_t xMQTTHandle ) +{ + MQTTConnection_t * pxConnection = ( MQTTConnection_t * ) xMQTTHandle; + + /* Clean up any allocated MQTT or network resources. */ + if( pxConnection->xMQTTConnection != IOT_MQTT_CONNECTION_INITIALIZER ) + { + IotMqtt_Disconnect( pxConnection->xMQTTConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); + pxConnection->xMQTTConnection = IOT_MQTT_CONNECTION_INITIALIZER; + } + + /* Free memory used by the MQTT connection. */ + vPortFree( pxConnection ); + + /* Increment the number of available brokers. */ + taskENTER_CRITICAL(); + { + uxAvailableBrokers++; + mqttconfigASSERT( uxAvailableBrokers <= mqttconfigMAX_BROKERS ); + } + taskEXIT_CRITICAL(); + + return eMQTTAgentSuccess; +} + +/*-----------------------------------------------------------*/ + +MQTTAgentReturnCode_t MQTT_AGENT_Connect( MQTTAgentHandle_t xMQTTHandle, + const MQTTAgentConnectParams_t * const pxConnectParams, + TickType_t xTimeoutTicks ) +{ + MQTTAgentReturnCode_t xStatus = eMQTTAgentSuccess; + IotMqttError_t xMqttStatus = IOT_MQTT_STATUS_PENDING; + MQTTConnection_t * pxConnection = ( MQTTConnection_t * ) xMQTTHandle; + IotNetworkServerInfo_t xServerInfo = { 0 }; + IotNetworkCredentials_t xCredentials = AWS_IOT_NETWORK_CREDENTIALS_AFR_INITIALIZER, * pxCredentials = NULL; + IotMqttNetworkInfo_t xNetworkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; + IotMqttConnectInfo_t xMqttConnectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; + + /* Copy the global callback and parameter. */ + pxConnection->pxCallback = pxConnectParams->pxCallback; + pxConnection->pvUserData = pxConnectParams->pvUserData; + + /* Set the TLS info for a secured connection. */ + if( ( pxConnectParams->xSecuredConnection == pdTRUE ) || + ( ( pxConnectParams->xFlags & mqttagentREQUIRE_TLS ) == mqttagentREQUIRE_TLS ) ) + { + pxCredentials = &xCredentials; + + /* Set the server certificate. Other credentials are set by the initializer. */ + xCredentials.pRootCa = pxConnectParams->pcCertificate; + xCredentials.rootCaSize = ( size_t ) pxConnectParams->ulCertificateSize; + + /* Disable ALPN if requested. */ + if( ( pxConnectParams->xFlags & mqttagentUSE_AWS_IOT_ALPN_443 ) == 0 ) + { + xCredentials.pAlpnProtos = NULL; + } + + /* Disable SNI if requested. */ + if( ( pxConnectParams->xURLIsIPAddress == pdTRUE ) || + ( ( pxConnectParams->xFlags & mqttagentURL_IS_IP_ADDRESS ) == mqttagentURL_IS_IP_ADDRESS ) ) + { + xCredentials.disableSni = true; + } + } + + /* Set the server info. */ + xServerInfo.pHostName = pxConnectParams->pcURL; + xServerInfo.port = pxConnectParams->usPort; + + /* Set the members of the network info. */ + xNetworkInfo.createNetworkConnection = true; + xNetworkInfo.u.setup.pNetworkServerInfo = &xServerInfo; + xNetworkInfo.u.setup.pNetworkCredentialInfo = pxCredentials; + xNetworkInfo.pNetworkInterface = IOT_NETWORK_INTERFACE_AFR; + + if( pxConnectParams->pxCallback != NULL ) + { + xNetworkInfo.disconnectCallback.function = prvDisconnectCallbackWrapper; + xNetworkInfo.disconnectCallback.pCallbackContext = pxConnection; + } + + /* Set the members of the MQTT connect info. */ + xMqttConnectInfo.awsIotMqttMode = true; + xMqttConnectInfo.cleanSession = true; + xMqttConnectInfo.pClientIdentifier = ( const char * ) ( pxConnectParams->pucClientId ); + xMqttConnectInfo.clientIdentifierLength = pxConnectParams->usClientIdLength; + xMqttConnectInfo.keepAliveSeconds = mqttconfigKEEP_ALIVE_INTERVAL_SECONDS; + + /* Call MQTT v2's CONNECT function. */ + xMqttStatus = IotMqtt_Connect( &xNetworkInfo, + &xMqttConnectInfo, + mqttTICKS_TO_MS( xTimeoutTicks ), + &( pxConnection->xMQTTConnection ) ); + xStatus = prvConvertReturnCode( xMqttStatus ); + + /* Add a subscription to "#" to support the global callback when subscription + * manager is disabled. */ + #if ( mqttconfigENABLE_SUBSCRIPTION_MANAGEMENT == 0 ) + IotMqttSubscription_t xGlobalSubscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; + IotMqttReference_t xGlobalSubscriptionRef = IOT_MQTT_REFERENCE_INITIALIZER; + + if( xStatus == eMQTTAgentSuccess ) + { + xGlobalSubscription.pTopicFilter = "#"; + xGlobalSubscription.topicFilterLength = 1; + xGlobalSubscription.qos = 0; + xGlobalSubscription.callback.param1 = pxConnection; + xGlobalSubscription.callback.function = prvPublishCallbackWrapper; + + xMqttStatus = IotMqtt_Subscribe( pxConnection->xMQTTConnection, + &xGlobalSubscription, + 1, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &xGlobalSubscriptionRef ); + xStatus = prvConvertReturnCode( xMqttStatus ); + } + + /* Wait for the subscription to "#" to complete. */ + if( xStatus == eMQTTAgentSuccess ) + { + xMqttStatus = IotMqtt_Wait( xGlobalSubscriptionRef, + mqttTICKS_TO_MS( xTimeoutTicks ) ); + xStatus = prvConvertReturnCode( xMqttStatus ); + } + #endif /* if ( mqttconfigENABLE_SUBSCRIPTION_MANAGEMENT == 1 ) */ + + return xStatus; +} + +/*-----------------------------------------------------------*/ + +MQTTAgentReturnCode_t MQTT_AGENT_Disconnect( MQTTAgentHandle_t xMQTTHandle, + TickType_t xTimeoutTicks ) +{ + MQTTConnection_t * pxConnection = ( MQTTConnection_t * ) xMQTTHandle; + + /* MQTT v2's DISCONNECT function does not have a timeout argument. */ + ( void ) xTimeoutTicks; + + /* Check that the connection is established. */ + if( pxConnection->xMQTTConnection != IOT_MQTT_CONNECTION_INITIALIZER ) + { + /* Call MQTT v2's DISCONNECT function. */ + IotMqtt_Disconnect( pxConnection->xMQTTConnection, + 0 ); + pxConnection->xMQTTConnection = IOT_MQTT_CONNECTION_INITIALIZER; + } + + return eMQTTAgentSuccess; +} + +/*-----------------------------------------------------------*/ + +MQTTAgentReturnCode_t MQTT_AGENT_Subscribe( MQTTAgentHandle_t xMQTTHandle, + const MQTTAgentSubscribeParams_t * const pxSubscribeParams, + TickType_t xTimeoutTicks ) +{ + MQTTAgentReturnCode_t xStatus = eMQTTAgentSuccess; + IotMqttError_t xMqttStatus = IOT_MQTT_STATUS_PENDING; + MQTTConnection_t * pxConnection = ( MQTTConnection_t * ) xMQTTHandle; + IotMqttSubscription_t xSubscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; + + /* Store the topic filter if subscription management is enabled. */ + #if ( mqttconfigENABLE_SUBSCRIPTION_MANAGEMENT == 1 ) + /* Check topic filter length. */ + if( pxSubscribeParams->usTopicLength > mqttconfigSUBSCRIPTION_MANAGER_MAX_TOPIC_LENGTH ) + { + xStatus = eMQTTAgentFailure; + } + + /* Store the subscription. */ + if( prvStoreCallback( pxConnection, + ( const char * ) pxSubscribeParams->pucTopic, + pxSubscribeParams->usTopicLength, + pxSubscribeParams->pxPublishCallback, + pxSubscribeParams->pvPublishCallbackContext ) == pdFAIL ) + { + xStatus = eMQTTAgentFailure; + } + #endif /* if ( mqttconfigENABLE_SUBSCRIPTION_MANAGEMENT == 1 ) */ + + /* Call MQTT v2 blocking SUBSCRIBE function. */ + if( xStatus == eMQTTAgentSuccess ) + { + /* Set the members of the MQTT subscription. */ + xSubscription.pTopicFilter = ( const char * ) ( pxSubscribeParams->pucTopic ); + xSubscription.topicFilterLength = pxSubscribeParams->usTopicLength; + xSubscription.qos = ( IotMqttQos_t ) pxSubscribeParams->xQoS; + xSubscription.callback.pCallbackContext = pxConnection; + xSubscription.callback.function = prvPublishCallbackWrapper; + + xMqttStatus = IotMqtt_TimedSubscribe( pxConnection->xMQTTConnection, + &xSubscription, + 1, + 0, + mqttTICKS_TO_MS( xTimeoutTicks ) ); + xStatus = prvConvertReturnCode( xMqttStatus ); + } + + return xStatus; +} + +/*-----------------------------------------------------------*/ + +MQTTAgentReturnCode_t MQTT_AGENT_Unsubscribe( MQTTAgentHandle_t xMQTTHandle, + const MQTTAgentUnsubscribeParams_t * const pxUnsubscribeParams, + TickType_t xTimeoutTicks ) +{ + IotMqttError_t xMqttStatus = IOT_MQTT_STATUS_PENDING; + MQTTConnection_t * pxConnection = ( MQTTConnection_t * ) xMQTTHandle; + IotMqttSubscription_t xSubscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; + + /* Remove any subscription callback that may be registered. */ + #if ( mqttconfigENABLE_SUBSCRIPTION_MANAGEMENT == 1 ) + prvRemoveCallback( pxConnection, + ( const char * ) ( pxUnsubscribeParams->pucTopic ), + pxUnsubscribeParams->usTopicLength ); + #endif + + /* Set the members of the subscription to remove. */ + xSubscription.pTopicFilter = ( const char * ) ( pxUnsubscribeParams->pucTopic ); + xSubscription.topicFilterLength = pxUnsubscribeParams->usTopicLength; + xSubscription.callback.pCallbackContext = pxConnection; + xSubscription.callback.function = prvPublishCallbackWrapper; + + /* Call MQTT v2 blocking UNSUBSCRIBE function. */ + xMqttStatus = IotMqtt_TimedUnsubscribe( pxConnection->xMQTTConnection, + &xSubscription, + 1, + 0, + mqttTICKS_TO_MS( xTimeoutTicks ) ); + + return prvConvertReturnCode( xMqttStatus ); +} + +/*-----------------------------------------------------------*/ + +MQTTAgentReturnCode_t MQTT_AGENT_Publish( MQTTAgentHandle_t xMQTTHandle, + const MQTTAgentPublishParams_t * const pxPublishParams, + TickType_t xTimeoutTicks ) +{ + IotMqttError_t xMqttStatus = IOT_MQTT_STATUS_PENDING; + MQTTConnection_t * pxConnection = ( MQTTConnection_t * ) xMQTTHandle; + IotMqttPublishInfo_t xPublishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; + + /* Set the members of the publish info. */ + xPublishInfo.pTopicName = ( const char * ) pxPublishParams->pucTopic; + xPublishInfo.topicNameLength = pxPublishParams->usTopicLength; + xPublishInfo.qos = ( IotMqttQos_t ) pxPublishParams->xQoS; + xPublishInfo.pPayload = ( const void * ) pxPublishParams->pvData; + xPublishInfo.payloadLength = pxPublishParams->ulDataLength; + + /* Call the MQTT v2 blocking PUBLISH function. */ + xMqttStatus = IotMqtt_TimedPublish( pxConnection->xMQTTConnection, + &xPublishInfo, + 0, + mqttTICKS_TO_MS( xTimeoutTicks ) ); + + return prvConvertReturnCode( xMqttStatus ); +} + +/*-----------------------------------------------------------*/ + +MQTTAgentReturnCode_t MQTT_AGENT_ReturnBuffer( MQTTAgentHandle_t xMQTTHandle, + MQTTBufferHandle_t xBufferHandle ) +{ + ( void ) xMQTTHandle; + + /* Free the MQTT buffer. */ + vPortFree( xBufferHandle ); + + return eMQTTAgentSuccess; +} + +/*-----------------------------------------------------------*/ diff --git a/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/src/iot_mqtt_api.c b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/src/iot_mqtt_api.c new file mode 100644 index 000000000..7df5326dc --- /dev/null +++ b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/src/iot_mqtt_api.c @@ -0,0 +1,2018 @@ +/* + * Amazon FreeRTOS MQTT V2.0.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +/** + * @file iot_mqtt_api.c + * @brief Implements most user-facing functions of the MQTT library. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Standard includes. */ +#include + +/* Error handling include. */ +#include "private/iot_error.h" + +/* MQTT internal include. */ +#include "private/iot_mqtt_internal.h" + +/* Platform layer includes. */ +#include "platform/iot_clock.h" +#include "platform/iot_threads.h" + +/* Validate MQTT configuration settings. */ +#if IOT_MQTT_ENABLE_ASSERTS != 0 && IOT_MQTT_ENABLE_ASSERTS != 1 + #error "IOT_MQTT_ENABLE_ASSERTS must be 0 or 1." +#endif +#if IOT_MQTT_ENABLE_METRICS != 0 && IOT_MQTT_ENABLE_METRICS != 1 + #error "IOT_MQTT_ENABLE_METRICS must be 0 or 1." +#endif +#if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES != 0 && IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES != 1 + #error "IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES must be 0 or 1." +#endif +#if IOT_MQTT_RESPONSE_WAIT_MS <= 0 + #error "IOT_MQTT_RESPONSE_WAIT_MS cannot be 0 or negative." +#endif +#if IOT_MQTT_RETRY_MS_CEILING <= 0 + #error "IOT_MQTT_RETRY_MS_CEILING cannot be 0 or negative." +#endif + +/*-----------------------------------------------------------*/ + +/** + * @brief Set the unsubscribed flag of an MQTT subscription. + * + * @param[in] pSubscriptionLink Pointer to the link member of an #_mqttSubscription_t. + * @param[in] pMatch Not used. + * + * @return Always returns `true`. + */ +static bool _mqttSubscription_setUnsubscribe( const IotLink_t * pSubscriptionLink, + void * pMatch ); + +/** + * @brief Destroy an MQTT subscription if its reference count is 0. + * + * @param[in] pData The subscription to destroy. This parameter is of type + * `void*` for compatibility with [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ +static void _mqttSubscription_tryDestroy( void * pData ); + +/** + * @brief Decrement the reference count of an MQTT operation and attempt to + * destroy it. + * + * @param[in] pData The operation data to destroy. This parameter is of type + * `void*` for compatibility with [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ +static void _mqttOperation_tryDestroy( void * pData ); + +/** + * @brief Create a keep-alive job for an MQTT connection. + * + * @param[in] pNetworkInfo User-provided network information for the new + * connection. + * @param[in] keepAliveSeconds User-provided keep-alive interval. + * @param[out] pMqttConnection The MQTT connection associated with the keep-alive. + * + * @return `true` if the keep-alive job was successfully created; `false` otherwise. + */ +static bool _createKeepAliveJob( const IotMqttNetworkInfo_t * pNetworkInfo, + uint16_t keepAliveSeconds, + _mqttConnection_t * pMqttConnection ); + +/** + * @brief Creates a new MQTT connection and initializes its members. + * + * @param[in] awsIotMqttMode Specifies if this connection is to an AWS IoT MQTT server. + * @param[in] pNetworkInfo User-provided network information for the new + * connection. + * @param[in] keepAliveSeconds User-provided keep-alive interval for the new connection. + * + * @return Pointer to a newly-created MQTT connection; `NULL` on failure. + */ +static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, + const IotMqttNetworkInfo_t * pNetworkInfo, + uint16_t keepAliveSeconds ); + +/** + * @brief Destroys the members of an MQTT connection. + * + * @param[in] pMqttConnection Which connection to destroy. + */ +static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection ); + +/** + * @brief The common component of both @ref mqtt_function_subscribe and @ref + * mqtt_function_unsubscribe. + * + * See @ref mqtt_function_subscribe or @ref mqtt_function_unsubscribe for a + * description of the parameters and return values. + */ +static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, + IotMqttConnection_t mqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + const IotMqttCallbackInfo_t * pCallbackInfo, + IotMqttOperation_t * pOperationReference ); + +/*-----------------------------------------------------------*/ + +static bool _mqttSubscription_setUnsubscribe( const IotLink_t * pSubscriptionLink, + void * pMatch ) +{ + /* Because this function is called from a container function, the given link + * must never be NULL. */ + IotMqtt_Assert( pSubscriptionLink != NULL ); + + _mqttSubscription_t * pSubscription = IotLink_Container( _mqttSubscription_t, + pSubscriptionLink, + link ); + + /* Silence warnings about unused parameters. */ + ( void ) pMatch; + + /* Set the unsubscribed flag. */ + pSubscription->unsubscribed = true; + + return true; +} + +/*-----------------------------------------------------------*/ + +static void _mqttSubscription_tryDestroy( void * pData ) +{ + _mqttSubscription_t * pSubscription = ( _mqttSubscription_t * ) pData; + + /* Reference count must not be negative. */ + IotMqtt_Assert( pSubscription->references >= 0 ); + + /* Unsubscribed flag should be set. */ + IotMqtt_Assert( pSubscription->unsubscribed == true ); + + /* Free the subscription if it has no references. */ + if( pSubscription->references == 0 ) + { + IotMqtt_FreeSubscription( pSubscription ); + } + else + { + EMPTY_ELSE_MARKER; + } +} + +/*-----------------------------------------------------------*/ + +static void _mqttOperation_tryDestroy( void * pData ) +{ + _mqttOperation_t * pOperation = ( _mqttOperation_t * ) pData; + IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; + + /* Incoming PUBLISH operations may always be freed. */ + if( pOperation->incomingPublish == true ) + { + /* Cancel the incoming PUBLISH operation's job. */ + taskPoolStatus = IotTaskPool_TryCancel( IOT_SYSTEM_TASKPOOL, + pOperation->job, + NULL ); + + /* If the operation's job was not canceled, it must be already executing. + * Any other return value is invalid. */ + IotMqtt_Assert( ( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) || + ( taskPoolStatus == IOT_TASKPOOL_CANCEL_FAILED ) ); + + /* Check if the incoming PUBLISH job was canceled. */ + if( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) + { + /* Job was canceled. Process incoming PUBLISH now to clean up. */ + _IotMqtt_ProcessIncomingPublish( IOT_SYSTEM_TASKPOOL, + pOperation->job, + pOperation ); + } + else + { + /* The executing job will process the PUBLISH, so nothing is done here. */ + EMPTY_ELSE_MARKER; + } + } + else + { + /* Decrement reference count and destroy operation if possible. */ + if( _IotMqtt_DecrementOperationReferences( pOperation, true ) == true ) + { + _IotMqtt_DestroyOperation( pOperation ); + } + else + { + EMPTY_ELSE_MARKER; + } + } +} + +/*-----------------------------------------------------------*/ + +static bool _createKeepAliveJob( const IotMqttNetworkInfo_t * pNetworkInfo, + uint16_t keepAliveSeconds, + _mqttConnection_t * pMqttConnection ) +{ + bool status = true; + IotMqttError_t serializeStatus = IOT_MQTT_SUCCESS; + IotTaskPoolError_t jobStatus = IOT_TASKPOOL_SUCCESS; + + /* Network information is not used when MQTT packet serializers are disabled. */ + ( void ) pNetworkInfo; + + /* Default PINGREQ serializer function. */ + IotMqttError_t ( * serializePingreq )( uint8_t **, + size_t * ) = _IotMqtt_SerializePingreq; + + /* Convert the keep-alive interval to milliseconds. */ + pMqttConnection->keepAliveMs = keepAliveSeconds * 1000; + pMqttConnection->nextKeepAliveMs = pMqttConnection->keepAliveMs; + + /* Choose a PINGREQ serializer function. */ + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pNetworkInfo->pMqttSerializer != NULL ) + { + if( pNetworkInfo->pMqttSerializer->serialize.pingreq != NULL ) + { + serializePingreq = pNetworkInfo->pMqttSerializer->serialize.pingreq; + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + + /* Generate a PINGREQ packet. */ + serializeStatus = serializePingreq( &( pMqttConnection->pPingreqPacket ), + &( pMqttConnection->pingreqPacketSize ) ); + + if( serializeStatus != IOT_MQTT_SUCCESS ) + { + IotLogError( "Failed to allocate PINGREQ packet for new connection." ); + + status = false; + } + else + { + /* Create the task pool job that processes keep-alive. */ + jobStatus = IotTaskPool_CreateJob( _IotMqtt_ProcessKeepAlive, + pMqttConnection, + &( pMqttConnection->keepAliveJobStorage ), + &( pMqttConnection->keepAliveJob ) ); + + /* Task pool job creation for a pre-allocated job should never fail. + * Abort the program if it does. */ + if( jobStatus != IOT_TASKPOOL_SUCCESS ) + { + IotLogError( "Failed to create keep-alive job for new connection." ); + + IotMqtt_Assert( false ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Keep-alive references its MQTT connection, so increment reference. */ + ( pMqttConnection->references )++; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, + const IotMqttNetworkInfo_t * pNetworkInfo, + uint16_t keepAliveSeconds ) +{ + IOT_FUNCTION_ENTRY( bool, true ); + _mqttConnection_t * pMqttConnection = NULL; + bool referencesMutexCreated = false, subscriptionMutexCreated = false; + + /* Allocate memory for the new MQTT connection. */ + pMqttConnection = IotMqtt_MallocConnection( sizeof( _mqttConnection_t ) ); + + if( pMqttConnection == NULL ) + { + IotLogError( "Failed to allocate memory for new connection." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + /* Clear the MQTT connection, then copy the MQTT server mode, network + * interface, and disconnect callback. */ + ( void ) memset( pMqttConnection, 0x00, sizeof( _mqttConnection_t ) ); + pMqttConnection->awsIotMqttMode = awsIotMqttMode; + pMqttConnection->pNetworkInterface = pNetworkInfo->pNetworkInterface; + pMqttConnection->disconnectCallback = pNetworkInfo->disconnectCallback; + + /* Start a new MQTT connection with a reference count of 1. */ + pMqttConnection->references = 1; + } + + /* Create the references mutex for a new connection. It is a recursive mutex. */ + referencesMutexCreated = IotMutex_Create( &( pMqttConnection->referencesMutex ), true ); + + if( referencesMutexCreated == false ) + { + IotLogError( "Failed to create references mutex for new connection." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Create the subscription mutex for a new connection. */ + subscriptionMutexCreated = IotMutex_Create( &( pMqttConnection->subscriptionMutex ), false ); + + if( subscriptionMutexCreated == false ) + { + IotLogError( "Failed to create subscription mutex for new connection." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Create the new connection's subscription and operation lists. */ + IotListDouble_Create( &( pMqttConnection->subscriptionList ) ); + IotListDouble_Create( &( pMqttConnection->pendingProcessing ) ); + IotListDouble_Create( &( pMqttConnection->pendingResponse ) ); + + /* AWS IoT service limits set minimum and maximum values for keep-alive interval. + * Adjust the user-provided keep-alive interval based on these requirements. */ + if( awsIotMqttMode == true ) + { + if( keepAliveSeconds < AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE ) + { + keepAliveSeconds = AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE; + } + else if( keepAliveSeconds > AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ) + { + keepAliveSeconds = AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE; + } + else if( keepAliveSeconds == 0 ) + { + keepAliveSeconds = AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE; + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Check if keep-alive is active for this connection. */ + if( keepAliveSeconds != 0 ) + { + if( _createKeepAliveJob( pNetworkInfo, + keepAliveSeconds, + pMqttConnection ) == false ) + { + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Clean up mutexes and connection if this function failed. */ + IOT_FUNCTION_CLEANUP_BEGIN(); + + if( status == false ) + { + if( subscriptionMutexCreated == true ) + { + IotMutex_Destroy( &( pMqttConnection->subscriptionMutex ) ); + } + else + { + EMPTY_ELSE_MARKER; + } + + if( referencesMutexCreated == true ) + { + IotMutex_Destroy( &( pMqttConnection->referencesMutex ) ); + } + else + { + EMPTY_ELSE_MARKER; + } + + if( pMqttConnection != NULL ) + { + IotMqtt_FreeConnection( pMqttConnection ); + pMqttConnection = NULL; + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + return pMqttConnection; +} + +/*-----------------------------------------------------------*/ + +static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection ) +{ + IotNetworkError_t networkStatus = IOT_NETWORK_SUCCESS; + + /* Clean up keep-alive if still allocated. */ + if( pMqttConnection->keepAliveMs != 0 ) + { + IotLogDebug( "(MQTT connection %p) Cleaning up keep-alive.", pMqttConnection ); + + _IotMqtt_FreePacket( pMqttConnection->pPingreqPacket ); + + /* Clear data about the keep-alive. */ + pMqttConnection->keepAliveMs = 0; + pMqttConnection->pPingreqPacket = NULL; + pMqttConnection->pingreqPacketSize = 0; + + /* Decrement reference count. */ + pMqttConnection->references--; + } + else + { + EMPTY_ELSE_MARKER; + } + + /* A connection to be destroyed should have no keep-alive and at most 1 + * reference. */ + IotMqtt_Assert( pMqttConnection->references <= 1 ); + IotMqtt_Assert( pMqttConnection->keepAliveMs == 0 ); + IotMqtt_Assert( pMqttConnection->pPingreqPacket == NULL ); + IotMqtt_Assert( pMqttConnection->pingreqPacketSize == 0 ); + + /* Remove all subscriptions. */ + IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); + IotListDouble_RemoveAllMatches( &( pMqttConnection->subscriptionList ), + _mqttSubscription_setUnsubscribe, + NULL, + _mqttSubscription_tryDestroy, + offsetof( _mqttSubscription_t, link ) ); + IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); + + /* Destroy an owned network connection. */ + if( pMqttConnection->ownNetworkConnection == true ) + { + networkStatus = pMqttConnection->pNetworkInterface->destroy( pMqttConnection->pNetworkConnection ); + + if( networkStatus != IOT_NETWORK_SUCCESS ) + { + IotLogWarn( "(MQTT connection %p) Failed to destroy network connection.", + pMqttConnection ); + } + else + { + IotLogInfo( "(MQTT connection %p) Network connection destroyed.", + pMqttConnection ); + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Destroy mutexes. */ + IotMutex_Destroy( &( pMqttConnection->referencesMutex ) ); + IotMutex_Destroy( &( pMqttConnection->subscriptionMutex ) ); + + IotLogDebug( "(MQTT connection %p) Connection destroyed.", pMqttConnection ); + + /* Free connection. */ + IotMqtt_FreeConnection( pMqttConnection ); +} + +/*-----------------------------------------------------------*/ + +static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, + IotMqttConnection_t mqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + const IotMqttCallbackInfo_t * pCallbackInfo, + IotMqttOperation_t * pOperationReference ) +{ + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + _mqttOperation_t * pSubscriptionOperation = NULL; + + /* Subscription serializer function. */ + IotMqttError_t ( * serializeSubscription )( const IotMqttSubscription_t *, + size_t, + uint8_t **, + size_t *, + uint16_t * ) = NULL; + + /* This function should only be called for subscribe or unsubscribe. */ + IotMqtt_Assert( ( operation == IOT_MQTT_SUBSCRIBE ) || + ( operation == IOT_MQTT_UNSUBSCRIBE ) ); + + /* Check that all elements in the subscription list are valid. */ + if( _IotMqtt_ValidateSubscriptionList( operation, + mqttConnection->awsIotMqttMode, + pSubscriptionList, + subscriptionCount ) == false ) + { + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Check that a reference pointer is provided for a waitable operation. */ + if( ( flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE ) + { + if( pOperationReference == NULL ) + { + IotLogError( "Reference must be provided for a waitable %s.", + IotMqtt_OperationType( operation ) ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Choose a subscription serialize function. */ + if( operation == IOT_MQTT_SUBSCRIBE ) + { + serializeSubscription = _IotMqtt_SerializeSubscribe; + + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( mqttConnection->pSerializer != NULL ) + { + if( mqttConnection->pSerializer->serialize.subscribe != NULL ) + { + serializeSubscription = mqttConnection->pSerializer->serialize.subscribe; + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + } + else + { + serializeSubscription = _IotMqtt_SerializeUnsubscribe; + + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( mqttConnection->pSerializer != NULL ) + { + if( mqttConnection->pSerializer->serialize.unsubscribe != NULL ) + { + serializeSubscription = mqttConnection->pSerializer->serialize.unsubscribe; + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + } + + /* Remove the MQTT subscription list for an UNSUBSCRIBE. */ + if( operation == IOT_MQTT_UNSUBSCRIBE ) + { + _IotMqtt_RemoveSubscriptionByTopicFilter( mqttConnection, + pSubscriptionList, + subscriptionCount ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Create a subscription operation. */ + status = _IotMqtt_CreateOperation( mqttConnection, + flags, + pCallbackInfo, + &pSubscriptionOperation ); + + if( status != IOT_MQTT_SUCCESS ) + { + IOT_GOTO_CLEANUP(); + } + + /* Check the subscription operation data and set the operation type. */ + IotMqtt_Assert( pSubscriptionOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); + IotMqtt_Assert( pSubscriptionOperation->u.operation.retry.limit == 0 ); + pSubscriptionOperation->u.operation.type = operation; + + /* Generate a subscription packet from the subscription list. */ + status = serializeSubscription( pSubscriptionList, + subscriptionCount, + &( pSubscriptionOperation->u.operation.pMqttPacket ), + &( pSubscriptionOperation->u.operation.packetSize ), + &( pSubscriptionOperation->u.operation.packetIdentifier ) ); + + if( status != IOT_MQTT_SUCCESS ) + { + IOT_GOTO_CLEANUP(); + } + + /* Check the serialized MQTT packet. */ + IotMqtt_Assert( pSubscriptionOperation->u.operation.pMqttPacket != NULL ); + IotMqtt_Assert( pSubscriptionOperation->u.operation.packetSize > 0 ); + + /* Add the subscription list for a SUBSCRIBE. */ + if( operation == IOT_MQTT_SUBSCRIBE ) + { + status = _IotMqtt_AddSubscriptions( mqttConnection, + pSubscriptionOperation->u.operation.packetIdentifier, + pSubscriptionList, + subscriptionCount ); + + if( status != IOT_MQTT_SUCCESS ) + { + IOT_GOTO_CLEANUP(); + } + } + + /* Set the reference, if provided. */ + if( pOperationReference != NULL ) + { + *pOperationReference = pSubscriptionOperation; + } + + /* Schedule the subscription operation for network transmission. */ + status = _IotMqtt_ScheduleOperation( pSubscriptionOperation, + _IotMqtt_ProcessSend, + 0 ); + + if( status != IOT_MQTT_SUCCESS ) + { + IotLogError( "(MQTT connection %p) Failed to schedule %s for sending.", + mqttConnection, + IotMqtt_OperationType( operation ) ); + + if( operation == IOT_MQTT_SUBSCRIBE ) + { + _IotMqtt_RemoveSubscriptionByPacket( mqttConnection, + pSubscriptionOperation->u.operation.packetIdentifier, + -1 ); + } + + /* Clear the previously set (and now invalid) reference. */ + if( pOperationReference != NULL ) + { + *pOperationReference = IOT_MQTT_OPERATION_INITIALIZER; + } + + IOT_GOTO_CLEANUP(); + } + + /* Clean up if this function failed. */ + IOT_FUNCTION_CLEANUP_BEGIN(); + + if( status != IOT_MQTT_SUCCESS ) + { + if( pSubscriptionOperation != NULL ) + { + _IotMqtt_DestroyOperation( pSubscriptionOperation ); + } + } + else + { + status = IOT_MQTT_STATUS_PENDING; + + IotLogInfo( "(MQTT connection %p) %s operation scheduled.", + mqttConnection, + IotMqtt_OperationType( operation ) ); + } + + IOT_FUNCTION_CLEANUP_END(); +} + +/*-----------------------------------------------------------*/ + +bool _IotMqtt_IncrementConnectionReferences( _mqttConnection_t * pMqttConnection ) +{ + bool disconnected = false; + + /* Lock the mutex protecting the reference count. */ + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + + /* Reference count must not be negative. */ + IotMqtt_Assert( pMqttConnection->references >= 0 ); + + /* Read connection status. */ + disconnected = pMqttConnection->disconnected; + + /* Increment the connection's reference count if it is not disconnected. */ + if( disconnected == false ) + { + ( pMqttConnection->references )++; + IotLogDebug( "(MQTT connection %p) Reference count changed from %ld to %ld.", + pMqttConnection, + ( long int ) pMqttConnection->references - 1, + ( long int ) pMqttConnection->references ); + } + else + { + IotLogWarn( "(MQTT connection %p) Attempt to use closed connection.", pMqttConnection ); + } + + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + + return( disconnected == false ); +} + +/*-----------------------------------------------------------*/ + +void _IotMqtt_DecrementConnectionReferences( _mqttConnection_t * pMqttConnection ) +{ + bool destroyConnection = false; + + /* Lock the mutex protecting the reference count. */ + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + + /* Decrement reference count. It must not be negative. */ + ( pMqttConnection->references )--; + IotMqtt_Assert( pMqttConnection->references >= 0 ); + + IotLogDebug( "(MQTT connection %p) Reference count changed from %ld to %ld.", + pMqttConnection, + ( long int ) pMqttConnection->references + 1, + ( long int ) pMqttConnection->references ); + + /* Check if this connection may be destroyed. */ + if( pMqttConnection->references == 0 ) + { + destroyConnection = true; + } + else + { + EMPTY_ELSE_MARKER; + } + + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + + /* Destroy an unreferenced MQTT connection. */ + if( destroyConnection == true ) + { + IotLogDebug( "(MQTT connection %p) Connection will be destroyed now.", + pMqttConnection ); + _destroyMqttConnection( pMqttConnection ); + } + else + { + EMPTY_ELSE_MARKER; + } +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t IotMqtt_Init( void ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + + /* Call any additional serializer initialization function if serializer + * overrides are enabled. */ + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + #ifdef _IotMqtt_InitSerializeAdditional + if( _IotMqtt_InitSerializeAdditional() == false ) + { + status = IOT_MQTT_INIT_FAILED; + } + else + { + EMPTY_ELSE_MARKER; + } + #endif + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + + /* Log initialization status. */ + if( status != IOT_MQTT_SUCCESS ) + { + IotLogError( "Failed to initialize MQTT library serializer. " ); + } + else + { + IotLogInfo( "MQTT library successfully initialized." ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +void IotMqtt_Cleanup( void ) +{ + /* Call any additional serializer cleanup initialization function if serializer + * overrides are enabled. */ + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + #ifdef _IotMqtt_CleanupSerializeAdditional + _IotMqtt_CleanupSerializeAdditional(); + #endif + #endif + + IotLogInfo( "MQTT library cleanup done." ); +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, + const IotMqttConnectInfo_t * pConnectInfo, + uint32_t timeoutMs, + IotMqttConnection_t * const pMqttConnection ) +{ + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + bool networkCreated = false, ownNetworkConnection = false; + IotNetworkError_t networkStatus = IOT_NETWORK_SUCCESS; + IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; + void * pNetworkConnection = NULL; + _mqttOperation_t * pOperation = NULL; + _mqttConnection_t * pNewMqttConnection = NULL; + + /* Default CONNECT serializer function. */ + IotMqttError_t ( * serializeConnect )( const IotMqttConnectInfo_t *, + uint8_t **, + size_t * ) = _IotMqtt_SerializeConnect; + + /* Network info must not be NULL. */ + if( pNetworkInfo == NULL ) + { + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Validate network interface and connect info. */ + if( _IotMqtt_ValidateConnect( pConnectInfo ) == false ) + { + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* If will info is provided, check that it is valid. */ + if( pConnectInfo->pWillInfo != NULL ) + { + if( _IotMqtt_ValidatePublish( pConnectInfo->awsIotMqttMode, + pConnectInfo->pWillInfo ) == false ) + { + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + else if( pConnectInfo->pWillInfo->payloadLength > UINT16_MAX ) + { + /* Will message payloads cannot be larger than 65535. This restriction + * applies only to will messages, and not normal PUBLISH messages. */ + IotLogError( "Will payload cannot be larger than 65535." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* If previous subscriptions are provided, check that they are valid. */ + if( pConnectInfo->cleanSession == false ) + { + if( pConnectInfo->pPreviousSubscriptions != NULL ) + { + if( _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, + pConnectInfo->awsIotMqttMode, + pConnectInfo->pPreviousSubscriptions, + pConnectInfo->previousSubscriptionCount ) == false ) + { + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Create a new MQTT connection if requested. Otherwise, copy the existing + * network connection. */ + if( pNetworkInfo->createNetworkConnection == true ) + { + networkStatus = pNetworkInfo->pNetworkInterface->create( pNetworkInfo->u.setup.pNetworkServerInfo, + pNetworkInfo->u.setup.pNetworkCredentialInfo, + &pNetworkConnection ); + + if( networkStatus == IOT_NETWORK_SUCCESS ) + { + networkCreated = true; + + /* This MQTT connection owns the network connection it created and + * should destroy it on cleanup. */ + ownNetworkConnection = true; + } + else + { + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NETWORK_ERROR ); + } + } + else + { + pNetworkConnection = pNetworkInfo->u.pNetworkConnection; + networkCreated = true; + } + + IotLogInfo( "Establishing new MQTT connection." ); + + /* Initialize a new MQTT connection object. */ + pNewMqttConnection = _createMqttConnection( pConnectInfo->awsIotMqttMode, + pNetworkInfo, + pConnectInfo->keepAliveSeconds ); + + if( pNewMqttConnection == NULL ) + { + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + } + else + { + /* Set the network connection associated with the MQTT connection. */ + pNewMqttConnection->pNetworkConnection = pNetworkConnection; + pNewMqttConnection->ownNetworkConnection = ownNetworkConnection; + + /* Set the MQTT packet serializer overrides. */ + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + pNewMqttConnection->pSerializer = pNetworkInfo->pMqttSerializer; + #endif + } + + /* Set the MQTT receive callback. */ + networkStatus = pNewMqttConnection->pNetworkInterface->setReceiveCallback( pNetworkConnection, + IotMqtt_ReceiveCallback, + pNewMqttConnection ); + + if( networkStatus != IOT_NETWORK_SUCCESS ) + { + IotLogError( "Failed to set MQTT network receive callback." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NETWORK_ERROR ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Create a CONNECT operation. */ + status = _IotMqtt_CreateOperation( pNewMqttConnection, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &pOperation ); + + if( status != IOT_MQTT_SUCCESS ) + { + IOT_GOTO_CLEANUP(); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Ensure the members set by operation creation and serialization + * are appropriate for a blocking CONNECT. */ + IotMqtt_Assert( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); + IotMqtt_Assert( ( pOperation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) + == IOT_MQTT_FLAG_WAITABLE ); + IotMqtt_Assert( pOperation->u.operation.retry.limit == 0 ); + + /* Set the operation type. */ + pOperation->u.operation.type = IOT_MQTT_CONNECT; + + /* Add previous session subscriptions. */ + if( pConnectInfo->pPreviousSubscriptions != NULL ) + { + /* Previous subscription count should have been validated as nonzero. */ + IotMqtt_Assert( pConnectInfo->previousSubscriptionCount > 0 ); + + status = _IotMqtt_AddSubscriptions( pNewMqttConnection, + 2, + pConnectInfo->pPreviousSubscriptions, + pConnectInfo->previousSubscriptionCount ); + + if( status != IOT_MQTT_SUCCESS ) + { + IOT_GOTO_CLEANUP(); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Choose a CONNECT serializer function. */ + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pNewMqttConnection->pSerializer != NULL ) + { + if( pNewMqttConnection->pSerializer->serialize.connect != NULL ) + { + serializeConnect = pNewMqttConnection->pSerializer->serialize.connect; + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + + /* Convert the connect info and will info objects to an MQTT CONNECT packet. */ + status = serializeConnect( pConnectInfo, + &( pOperation->u.operation.pMqttPacket ), + &( pOperation->u.operation.packetSize ) ); + + if( status != IOT_MQTT_SUCCESS ) + { + IOT_GOTO_CLEANUP(); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Check the serialized MQTT packet. */ + IotMqtt_Assert( pOperation->u.operation.pMqttPacket != NULL ); + IotMqtt_Assert( pOperation->u.operation.packetSize > 0 ); + + /* Add the CONNECT operation to the send queue for network transmission. */ + status = _IotMqtt_ScheduleOperation( pOperation, + _IotMqtt_ProcessSend, + 0 ); + + if( status != IOT_MQTT_SUCCESS ) + { + IotLogError( "Failed to enqueue CONNECT for sending." ); + } + else + { + /* Wait for the CONNECT operation to complete, i.e. wait for CONNACK. */ + status = IotMqtt_Wait( pOperation, + timeoutMs ); + + /* The call to wait cleans up the CONNECT operation, so set the pointer + * to NULL. */ + pOperation = NULL; + } + + /* When a connection is successfully established, schedule keep-alive job. */ + if( status == IOT_MQTT_SUCCESS ) + { + /* Check if a keep-alive job should be scheduled. */ + if( pNewMqttConnection->keepAliveMs != 0 ) + { + IotLogDebug( "Scheduling first MQTT keep-alive job." ); + + taskPoolStatus = IotTaskPool_ScheduleDeferred( IOT_SYSTEM_TASKPOOL, + pNewMqttConnection->keepAliveJob, + pNewMqttConnection->nextKeepAliveMs ); + + if( taskPoolStatus != IOT_TASKPOOL_SUCCESS ) + { + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_SCHEDULING_ERROR ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + IOT_FUNCTION_CLEANUP_BEGIN(); + + if( status != IOT_MQTT_SUCCESS ) + { + IotLogError( "Failed to establish new MQTT connection, error %s.", + IotMqtt_strerror( status ) ); + + /* The network connection must be closed if it was created. */ + if( networkCreated == true ) + { + networkStatus = pNetworkInfo->pNetworkInterface->close( pNetworkConnection ); + + if( networkStatus != IOT_NETWORK_SUCCESS ) + { + IotLogWarn( "Failed to close network connection." ); + } + else + { + IotLogInfo( "Network connection closed on error." ); + } + } + else + { + EMPTY_ELSE_MARKER; + } + + if( pOperation != NULL ) + { + _IotMqtt_DestroyOperation( pOperation ); + } + else + { + EMPTY_ELSE_MARKER; + } + + if( pNewMqttConnection != NULL ) + { + _destroyMqttConnection( pNewMqttConnection ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + IotLogInfo( "New MQTT connection %p established.", pMqttConnection ); + + /* Set the output parameter. */ + *pMqttConnection = pNewMqttConnection; + } + + IOT_FUNCTION_CLEANUP_END(); +} + +/*-----------------------------------------------------------*/ + +void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, + uint32_t flags ) +{ + bool disconnected = false; + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + _mqttOperation_t * pOperation = NULL; + + IotLogInfo( "(MQTT connection %p) Disconnecting connection.", mqttConnection ); + + /* Read the connection status. */ + IotMutex_Lock( &( mqttConnection->referencesMutex ) ); + disconnected = mqttConnection->disconnected; + IotMutex_Unlock( &( mqttConnection->referencesMutex ) ); + + /* Only send a DISCONNECT packet if the connection is active and the "cleanup only" + * flag is not set. */ + if( disconnected == false ) + { + if( ( flags & IOT_MQTT_FLAG_CLEANUP_ONLY ) == 0 ) + { + /* Create a DISCONNECT operation. This function blocks until the DISCONNECT + * packet is sent, so it sets IOT_MQTT_FLAG_WAITABLE. */ + status = _IotMqtt_CreateOperation( mqttConnection, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &pOperation ); + + if( status == IOT_MQTT_SUCCESS ) + { + /* Ensure that the members set by operation creation and serialization + * are appropriate for a blocking DISCONNECT. */ + IotMqtt_Assert( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); + IotMqtt_Assert( ( pOperation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) + == IOT_MQTT_FLAG_WAITABLE ); + IotMqtt_Assert( pOperation->u.operation.retry.limit == 0 ); + + /* Set the operation type. */ + pOperation->u.operation.type = IOT_MQTT_DISCONNECT; + + /* Choose a disconnect serializer. */ + IotMqttError_t ( * serializeDisconnect )( uint8_t **, + size_t * ) = _IotMqtt_SerializeDisconnect; + + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( mqttConnection->pSerializer != NULL ) + { + if( mqttConnection->pSerializer->serialize.disconnect != NULL ) + { + serializeDisconnect = mqttConnection->pSerializer->serialize.disconnect; + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + + /* Generate a DISCONNECT packet. */ + status = serializeDisconnect( &( pOperation->u.operation.pMqttPacket ), + &( pOperation->u.operation.packetSize ) ); + } + else + { + EMPTY_ELSE_MARKER; + } + + if( status == IOT_MQTT_SUCCESS ) + { + /* Check the serialized MQTT packet. */ + IotMqtt_Assert( pOperation->u.operation.pMqttPacket != NULL ); + IotMqtt_Assert( pOperation->u.operation.packetSize > 0 ); + + /* Schedule the DISCONNECT operation for network transmission. */ + if( _IotMqtt_ScheduleOperation( pOperation, + _IotMqtt_ProcessSend, + 0 ) != IOT_MQTT_SUCCESS ) + { + IotLogWarn( "(MQTT connection %p) Failed to schedule DISCONNECT for sending.", + mqttConnection ); + _IotMqtt_DestroyOperation( pOperation ); + } + else + { + /* Wait a short time for the DISCONNECT packet to be transmitted. */ + status = IotMqtt_Wait( pOperation, + IOT_MQTT_RESPONSE_WAIT_MS ); + + /* A wait on DISCONNECT should only ever return SUCCESS, TIMEOUT, + * or NETWORK ERROR. */ + if( status == IOT_MQTT_SUCCESS ) + { + IotLogInfo( "(MQTT connection %p) Connection disconnected.", mqttConnection ); + } + else + { + IotMqtt_Assert( ( status == IOT_MQTT_TIMEOUT ) || + ( status == IOT_MQTT_NETWORK_ERROR ) ); + + IotLogWarn( "(MQTT connection %p) DISCONNECT not sent, error %s.", + mqttConnection, + IotMqtt_strerror( status ) ); + } + } + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Close the underlying network connection. This also cleans up keep-alive. */ + _IotMqtt_CloseNetworkConnection( IOT_MQTT_DISCONNECT_CALLED, + mqttConnection ); + + /* Check if the connection may be destroyed. */ + IotMutex_Lock( &( mqttConnection->referencesMutex ) ); + + /* At this point, the connection should be marked disconnected. */ + IotMqtt_Assert( mqttConnection->disconnected == true ); + + /* Attempt cancel and destroy each operation in the connection's lists. */ + IotListDouble_RemoveAll( &( mqttConnection->pendingProcessing ), + _mqttOperation_tryDestroy, + offsetof( _mqttOperation_t, link ) ); + + IotListDouble_RemoveAll( &( mqttConnection->pendingResponse ), + _mqttOperation_tryDestroy, + offsetof( _mqttOperation_t, link ) ); + + IotMutex_Unlock( &( mqttConnection->referencesMutex ) ); + + /* Decrement the connection reference count and destroy it if possible. */ + _IotMqtt_DecrementConnectionReferences( mqttConnection ); +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t IotMqtt_Subscribe( IotMqttConnection_t mqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + const IotMqttCallbackInfo_t * pCallbackInfo, + IotMqttOperation_t * pSubscribeOperation ) +{ + return _subscriptionCommon( IOT_MQTT_SUBSCRIBE, + mqttConnection, + pSubscriptionList, + subscriptionCount, + flags, + pCallbackInfo, + pSubscribeOperation ); +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t IotMqtt_TimedSubscribe( IotMqttConnection_t mqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + uint32_t timeoutMs ) +{ + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + IotMqttOperation_t subscribeOperation = IOT_MQTT_OPERATION_INITIALIZER; + + /* Flags are not used, but the parameter is present for future compatibility. */ + ( void ) flags; + + /* Call the asynchronous SUBSCRIBE function. */ + status = IotMqtt_Subscribe( mqttConnection, + pSubscriptionList, + subscriptionCount, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &subscribeOperation ); + + /* Wait for the SUBSCRIBE operation to complete. */ + if( status == IOT_MQTT_STATUS_PENDING ) + { + status = IotMqtt_Wait( subscribeOperation, timeoutMs ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Ensure that a status was set. */ + IotMqtt_Assert( status != IOT_MQTT_STATUS_PENDING ); + + return status; +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t IotMqtt_Unsubscribe( IotMqttConnection_t mqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + const IotMqttCallbackInfo_t * pCallbackInfo, + IotMqttOperation_t * pUnsubscribeOperation ) +{ + return _subscriptionCommon( IOT_MQTT_UNSUBSCRIBE, + mqttConnection, + pSubscriptionList, + subscriptionCount, + flags, + pCallbackInfo, + pUnsubscribeOperation ); +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t IotMqtt_TimedUnsubscribe( IotMqttConnection_t mqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + uint32_t timeoutMs ) +{ + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + IotMqttOperation_t unsubscribeOperation = IOT_MQTT_OPERATION_INITIALIZER; + + /* Flags are not used, but the parameter is present for future compatibility. */ + ( void ) flags; + + /* Call the asynchronous UNSUBSCRIBE function. */ + status = IotMqtt_Unsubscribe( mqttConnection, + pSubscriptionList, + subscriptionCount, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &unsubscribeOperation ); + + /* Wait for the UNSUBSCRIBE operation to complete. */ + if( status == IOT_MQTT_STATUS_PENDING ) + { + status = IotMqtt_Wait( unsubscribeOperation, timeoutMs ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Ensure that a status was set. */ + IotMqtt_Assert( status != IOT_MQTT_STATUS_PENDING ); + + return status; +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, + const IotMqttPublishInfo_t * pPublishInfo, + uint32_t flags, + const IotMqttCallbackInfo_t * pCallbackInfo, + IotMqttOperation_t * pPublishOperation ) +{ + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + _mqttOperation_t * pOperation = NULL; + uint8_t ** pPacketIdentifierHigh = NULL; + + /* Default PUBLISH serializer function. */ + IotMqttError_t ( * serializePublish )( const IotMqttPublishInfo_t *, + uint8_t **, + size_t *, + uint16_t *, + uint8_t ** ) = _IotMqtt_SerializePublish; + + /* Check that the PUBLISH information is valid. */ + if( _IotMqtt_ValidatePublish( mqttConnection->awsIotMqttMode, + pPublishInfo ) == false ) + { + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Check that no notification is requested for a QoS 0 publish. */ + if( pPublishInfo->qos == IOT_MQTT_QOS_0 ) + { + if( pCallbackInfo != NULL ) + { + IotLogError( "QoS 0 PUBLISH should not have notification parameters set." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + else if( ( flags & IOT_MQTT_FLAG_WAITABLE ) != 0 ) + { + IotLogError( "QoS 0 PUBLISH should not have notification parameters set." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + else + { + EMPTY_ELSE_MARKER; + } + + if( pPublishOperation != NULL ) + { + IotLogWarn( "Ignoring reference parameter for QoS 0 publish." ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Check that a reference pointer is provided for a waitable operation. */ + if( ( flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE ) + { + if( pPublishOperation == NULL ) + { + IotLogError( "Reference must be provided for a waitable PUBLISH." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Create a PUBLISH operation. */ + status = _IotMqtt_CreateOperation( mqttConnection, + flags, + pCallbackInfo, + &pOperation ); + + if( status != IOT_MQTT_SUCCESS ) + { + IOT_GOTO_CLEANUP(); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Check the PUBLISH operation data and set the operation type. */ + IotMqtt_Assert( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); + pOperation->u.operation.type = IOT_MQTT_PUBLISH_TO_SERVER; + + /* Choose a PUBLISH serializer function. */ + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( mqttConnection->pSerializer != NULL ) + { + if( mqttConnection->pSerializer->serialize.publish != NULL ) + { + serializePublish = mqttConnection->pSerializer->serialize.publish; + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + + /* In AWS IoT MQTT mode, a pointer to the packet identifier must be saved. */ + if( mqttConnection->awsIotMqttMode == true ) + { + pPacketIdentifierHigh = &( pOperation->u.operation.pPacketIdentifierHigh ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Generate a PUBLISH packet from pPublishInfo. */ + status = serializePublish( pPublishInfo, + &( pOperation->u.operation.pMqttPacket ), + &( pOperation->u.operation.packetSize ), + &( pOperation->u.operation.packetIdentifier ), + pPacketIdentifierHigh ); + + if( status != IOT_MQTT_SUCCESS ) + { + IOT_GOTO_CLEANUP(); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Check the serialized MQTT packet. */ + IotMqtt_Assert( pOperation->u.operation.pMqttPacket != NULL ); + IotMqtt_Assert( pOperation->u.operation.packetSize > 0 ); + + /* Initialize PUBLISH retry if retryLimit is set. */ + if( pPublishInfo->retryLimit > 0 ) + { + /* A QoS 0 PUBLISH may not be retried. */ + if( pPublishInfo->qos != IOT_MQTT_QOS_0 ) + { + pOperation->u.operation.retry.limit = pPublishInfo->retryLimit; + pOperation->u.operation.retry.nextPeriod = pPublishInfo->retryMs; + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Set the reference, if provided. */ + if( pPublishInfo->qos != IOT_MQTT_QOS_0 ) + { + if( pPublishOperation != NULL ) + { + *pPublishOperation = pOperation; + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Add the PUBLISH operation to the send queue for network transmission. */ + status = _IotMqtt_ScheduleOperation( pOperation, + _IotMqtt_ProcessSend, + 0 ); + + if( status != IOT_MQTT_SUCCESS ) + { + IotLogError( "(MQTT connection %p) Failed to enqueue PUBLISH for sending.", + mqttConnection ); + + /* Clear the previously set (and now invalid) reference. */ + if( pPublishInfo->qos != IOT_MQTT_QOS_0 ) + { + if( pPublishOperation != NULL ) + { + *pPublishOperation = IOT_MQTT_OPERATION_INITIALIZER; + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + IOT_GOTO_CLEANUP(); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Clean up the PUBLISH operation if this function fails. Otherwise, set the + * appropriate return code based on QoS. */ + IOT_FUNCTION_CLEANUP_BEGIN(); + + if( status != IOT_MQTT_SUCCESS ) + { + if( pOperation != NULL ) + { + _IotMqtt_DestroyOperation( pOperation ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + if( pPublishInfo->qos > IOT_MQTT_QOS_0 ) + { + status = IOT_MQTT_STATUS_PENDING; + } + else + { + EMPTY_ELSE_MARKER; + } + + IotLogInfo( "(MQTT connection %p) MQTT PUBLISH operation queued.", + mqttConnection ); + } + + IOT_FUNCTION_CLEANUP_END(); +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t IotMqtt_TimedPublish( IotMqttConnection_t mqttConnection, + const IotMqttPublishInfo_t * pPublishInfo, + uint32_t flags, + uint32_t timeoutMs ) +{ + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + IotMqttOperation_t publishOperation = IOT_MQTT_OPERATION_INITIALIZER, + * pPublishOperation = NULL; + + /* Clear the flags. */ + flags = 0; + + /* Set the waitable flag and reference for QoS 1 PUBLISH. */ + if( pPublishInfo->qos == IOT_MQTT_QOS_1 ) + { + flags = IOT_MQTT_FLAG_WAITABLE; + pPublishOperation = &publishOperation; + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Call the asynchronous PUBLISH function. */ + status = IotMqtt_Publish( mqttConnection, + pPublishInfo, + flags, + NULL, + pPublishOperation ); + + /* Wait for a queued QoS 1 PUBLISH to complete. */ + if( pPublishInfo->qos == IOT_MQTT_QOS_1 ) + { + if( status == IOT_MQTT_STATUS_PENDING ) + { + status = IotMqtt_Wait( publishOperation, timeoutMs ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, + uint32_t timeoutMs ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + _mqttConnection_t * pMqttConnection = operation->pMqttConnection; + + /* Validate the given operation reference. */ + if( _IotMqtt_ValidateOperation( operation ) == false ) + { + status = IOT_MQTT_BAD_PARAMETER; + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Check the MQTT connection status. */ + if( status == IOT_MQTT_SUCCESS ) + { + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + + if( pMqttConnection->disconnected == true ) + { + IotLogError( "(MQTT connection %p, %s operation %p) MQTT connection is closed. " + "Operation cannot be waited on.", + pMqttConnection, + IotMqtt_OperationType( operation->u.operation.type ), + operation ); + + status = IOT_MQTT_NETWORK_ERROR; + } + else + { + IotLogInfo( "(MQTT connection %p, %s operation %p) Waiting for operation completion.", + pMqttConnection, + IotMqtt_OperationType( operation->u.operation.type ), + operation ); + } + + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + + /* Only wait on an operation if the MQTT connection is active. */ + if( status == IOT_MQTT_SUCCESS ) + { + if( IotSemaphore_TimedWait( &( operation->u.operation.notify.waitSemaphore ), + timeoutMs ) == false ) + { + status = IOT_MQTT_TIMEOUT; + + /* Attempt to cancel the job of the timed out operation. */ + ( void ) _IotMqtt_DecrementOperationReferences( operation, true ); + + /* Clean up lingering subscriptions from a timed-out SUBSCRIBE. */ + if( operation->u.operation.type == IOT_MQTT_SUBSCRIBE ) + { + IotLogDebug( "(MQTT connection %p, SUBSCRIBE operation %p) Cleaning up" + " subscriptions of timed-out SUBSCRIBE.", + pMqttConnection, + operation ); + + _IotMqtt_RemoveSubscriptionByPacket( pMqttConnection, + operation->u.operation.packetIdentifier, + -1 ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + /* Retrieve the status of the completed operation. */ + status = operation->u.operation.status; + } + + IotLogInfo( "(MQTT connection %p, %s operation %p) Wait complete with result %s.", + pMqttConnection, + IotMqtt_OperationType( operation->u.operation.type ), + operation, + IotMqtt_strerror( status ) ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Wait is finished; decrement operation reference count. */ + if( _IotMqtt_DecrementOperationReferences( operation, false ) == true ) + { + _IotMqtt_DestroyOperation( operation ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +const char * IotMqtt_strerror( IotMqttError_t status ) +{ + const char * pMessage = NULL; + + switch( status ) + { + case IOT_MQTT_SUCCESS: + pMessage = "SUCCESS"; + break; + + case IOT_MQTT_STATUS_PENDING: + pMessage = "PENDING"; + break; + + case IOT_MQTT_INIT_FAILED: + pMessage = "INITIALIZATION FAILED"; + break; + + case IOT_MQTT_BAD_PARAMETER: + pMessage = "BAD PARAMETER"; + break; + + case IOT_MQTT_NO_MEMORY: + pMessage = "NO MEMORY"; + break; + + case IOT_MQTT_NETWORK_ERROR: + pMessage = "NETWORK ERROR"; + break; + + case IOT_MQTT_SCHEDULING_ERROR: + pMessage = "SCHEDULING ERROR"; + break; + + case IOT_MQTT_BAD_RESPONSE: + pMessage = "BAD RESPONSE RECEIVED"; + break; + + case IOT_MQTT_TIMEOUT: + pMessage = "TIMEOUT"; + break; + + case IOT_MQTT_SERVER_REFUSED: + pMessage = "SERVER REFUSED"; + break; + + case IOT_MQTT_RETRY_NO_RESPONSE: + pMessage = "NO RESPONSE"; + break; + + default: + pMessage = "INVALID STATUS"; + break; + } + + return pMessage; +} + +/*-----------------------------------------------------------*/ + +const char * IotMqtt_OperationType( IotMqttOperationType_t operation ) +{ + const char * pMessage = NULL; + + switch( operation ) + { + case IOT_MQTT_CONNECT: + pMessage = "CONNECT"; + break; + + case IOT_MQTT_PUBLISH_TO_SERVER: + pMessage = "PUBLISH"; + break; + + case IOT_MQTT_PUBACK: + pMessage = "PUBACK"; + break; + + case IOT_MQTT_SUBSCRIBE: + pMessage = "SUBSCRIBE"; + break; + + case IOT_MQTT_UNSUBSCRIBE: + pMessage = "UNSUBSCRIBE"; + break; + + case IOT_MQTT_PINGREQ: + pMessage = "PINGREQ"; + break; + + case IOT_MQTT_DISCONNECT: + pMessage = "DISCONNECT"; + break; + + default: + pMessage = "INVALID OPERATION"; + break; + } + + return pMessage; +} + +/*-----------------------------------------------------------*/ + +/* Provide access to internal functions and variables if testing. */ +#if IOT_BUILD_TESTS == 1 + #include "iot_test_access_mqtt_api.c" +#endif diff --git a/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/src/iot_mqtt_network.c b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/src/iot_mqtt_network.c new file mode 100644 index 000000000..169a292df --- /dev/null +++ b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/src/iot_mqtt_network.c @@ -0,0 +1,912 @@ +/* + * Amazon FreeRTOS MQTT V2.0.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +/** + * @file iot_mqtt_network.c + * @brief Implements functions involving transport layer connections. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Standard includes. */ +#include + +/* Error handling include. */ +#include "private/iot_error.h" + +/* MQTT internal include. */ +#include "private/iot_mqtt_internal.h" + +/* Platform layer includes. */ +#include "platform/iot_threads.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief Check if an incoming packet type is valid. + * + * @param[in] packetType The packet type to check. + * + * @return `true` if the packet type is valid; `false` otherwise. + */ +static bool _incomingPacketValid( uint8_t packetType ); + +/** + * @brief Get an incoming MQTT packet from the network. + * + * @param[in] pNetworkConnection Network connection to use for receive, which + * may be different from the network connection associated with the MQTT connection. + * @param[in] pMqttConnection The associated MQTT connection. + * @param[out] pIncomingPacket Output parameter for the incoming packet. + * + * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_NO_MEMORY or #IOT_MQTT_BAD_RESPONSE. + */ +static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, + const _mqttConnection_t * pMqttConnection, + _mqttPacket_t * pIncomingPacket ); + +/** + * @brief Deserialize a packet received from the network. + * + * @param[in] pMqttConnection The associated MQTT connection. + * @param[in] pIncomingPacket The packet received from the network. + * + * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_NO_MEMORY, #IOT_MQTT_NETWORK_ERROR, + * #IOT_MQTT_SCHEDULING_ERROR, #IOT_MQTT_BAD_RESPONSE, or #IOT_MQTT_SERVER_REFUSED. + */ +static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConnection, + _mqttPacket_t * pIncomingPacket ); + +/** + * @brief Send a PUBACK for a received QoS 1 PUBLISH packet. + * + * @param[in] pMqttConnection Which connection the PUBACK should be sent over. + * @param[in] packetIdentifier Which packet identifier to include in PUBACK. + */ +static void _sendPuback( _mqttConnection_t * pMqttConnection, + uint16_t packetIdentifier ); + +/*-----------------------------------------------------------*/ + +static bool _incomingPacketValid( uint8_t packetType ) +{ + bool status = true; + + /* Check packet type. Mask out lower bits to ignore flags. */ + switch( packetType & 0xf0 ) + { + /* Valid incoming packet types. */ + case MQTT_PACKET_TYPE_CONNACK: + case MQTT_PACKET_TYPE_PUBLISH: + case MQTT_PACKET_TYPE_PUBACK: + case MQTT_PACKET_TYPE_SUBACK: + case MQTT_PACKET_TYPE_UNSUBACK: + case MQTT_PACKET_TYPE_PINGRESP: + break; + + /* Any other packet type is invalid. */ + default: + status = false; + break; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, + const _mqttConnection_t * pMqttConnection, + _mqttPacket_t * pIncomingPacket ) +{ + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + size_t dataBytesRead = 0; + + /* Default functions for retrieving packet type and length. */ + uint8_t ( * getPacketType )( void *, + const IotNetworkInterface_t * ) = _IotMqtt_GetPacketType; + size_t ( * getRemainingLength )( void *, + const IotNetworkInterface_t * ) = _IotMqtt_GetRemainingLength; + + /* No buffer for remaining data should be allocated. */ + IotMqtt_Assert( pIncomingPacket->pRemainingData == NULL ); + IotMqtt_Assert( pIncomingPacket->remainingLength == 0 ); + + /* Choose packet type and length functions. */ + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->pSerializer != NULL ) + { + if( pMqttConnection->pSerializer->getPacketType != NULL ) + { + getPacketType = pMqttConnection->pSerializer->getPacketType; + } + else + { + EMPTY_ELSE_MARKER; + } + + if( pMqttConnection->pSerializer->getRemainingLength != NULL ) + { + getRemainingLength = pMqttConnection->pSerializer->getRemainingLength; + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + + /* Read the packet type, which is the first byte available. */ + pIncomingPacket->type = getPacketType( pNetworkConnection, + pMqttConnection->pNetworkInterface ); + + /* Check that the incoming packet type is valid. */ + if( _incomingPacketValid( pIncomingPacket->type ) == false ) + { + IotLogError( "(MQTT connection %p) Unknown packet type %02x received.", + pMqttConnection, + pIncomingPacket->type ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Read the remaining length. */ + pIncomingPacket->remainingLength = getRemainingLength( pNetworkConnection, + pMqttConnection->pNetworkInterface ); + + if( pIncomingPacket->remainingLength == MQTT_REMAINING_LENGTH_INVALID ) + { + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Allocate a buffer for the remaining data and read the data. */ + if( pIncomingPacket->remainingLength > 0 ) + { + pIncomingPacket->pRemainingData = IotMqtt_MallocMessage( pIncomingPacket->remainingLength ); + + if( pIncomingPacket->pRemainingData == NULL ) + { + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + } + else + { + EMPTY_ELSE_MARKER; + } + + dataBytesRead = pMqttConnection->pNetworkInterface->receive( pNetworkConnection, + pIncomingPacket->pRemainingData, + pIncomingPacket->remainingLength ); + + if( dataBytesRead != pIncomingPacket->remainingLength ) + { + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Clean up on error. */ + IOT_FUNCTION_CLEANUP_BEGIN(); + + if( status != IOT_MQTT_SUCCESS ) + { + if( pIncomingPacket->pRemainingData != NULL ) + { + IotMqtt_FreeMessage( pIncomingPacket->pRemainingData ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + IOT_FUNCTION_CLEANUP_END(); +} + +/*-----------------------------------------------------------*/ + +static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConnection, + _mqttPacket_t * pIncomingPacket ) +{ + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + _mqttOperation_t * pOperation = NULL; + + /* Deserializer function. */ + IotMqttError_t ( * deserialize )( _mqttPacket_t * ) = NULL; + + /* A buffer for remaining data must be allocated if remaining length is not 0. */ + IotMqtt_Assert( ( pIncomingPacket->remainingLength > 0 ) == + ( pIncomingPacket->pRemainingData != NULL ) ); + + /* Only valid packets should be given to this function. */ + IotMqtt_Assert( _incomingPacketValid( pIncomingPacket->type ) == true ); + + /* Mask out the low bits of packet type to ignore flags. */ + switch( ( pIncomingPacket->type & 0xf0 ) ) + { + case MQTT_PACKET_TYPE_CONNACK: + IotLogDebug( "(MQTT connection %p) CONNACK in data stream.", pMqttConnection ); + + /* Choose CONNACK deserializer. */ + deserialize = _IotMqtt_DeserializeConnack; + + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->pSerializer != NULL ) + { + if( pMqttConnection->pSerializer->deserialize.connack != NULL ) + { + deserialize = pMqttConnection->pSerializer->deserialize.connack; + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + + /* Deserialize CONNACK and notify of result. */ + status = deserialize( pIncomingPacket ); + pOperation = _IotMqtt_FindOperation( pMqttConnection, + IOT_MQTT_CONNECT, + NULL ); + + if( pOperation != NULL ) + { + pOperation->u.operation.status = status; + _IotMqtt_Notify( pOperation ); + } + else + { + EMPTY_ELSE_MARKER; + } + + break; + + case MQTT_PACKET_TYPE_PUBLISH: + IotLogDebug( "(MQTT connection %p) PUBLISH in data stream.", pMqttConnection ); + + /* Allocate memory to handle the incoming PUBLISH. */ + pOperation = IotMqtt_MallocOperation( sizeof( _mqttOperation_t ) ); + + if( pOperation == NULL ) + { + IotLogWarn( "Failed to allocate memory for incoming PUBLISH." ); + status = IOT_MQTT_NO_MEMORY; + + break; + } + else + { + /* Set the members of the incoming PUBLISH operation. */ + ( void ) memset( pOperation, 0x00, sizeof( _mqttOperation_t ) ); + pOperation->incomingPublish = true; + pOperation->pMqttConnection = pMqttConnection; + pIncomingPacket->u.pIncomingPublish = pOperation; + } + + /* Choose a PUBLISH deserializer. */ + deserialize = _IotMqtt_DeserializePublish; + + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->pSerializer != NULL ) + { + if( pMqttConnection->pSerializer->deserialize.publish != NULL ) + { + deserialize = pMqttConnection->pSerializer->deserialize.publish; + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + + /* Deserialize incoming PUBLISH. */ + status = deserialize( pIncomingPacket ); + + if( status == IOT_MQTT_SUCCESS ) + { + /* Send a PUBACK for QoS 1 PUBLISH. */ + if( pOperation->u.publish.publishInfo.qos == IOT_MQTT_QOS_1 ) + { + _sendPuback( pMqttConnection, pIncomingPacket->packetIdentifier ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Transfer ownership of the received MQTT packet to the PUBLISH operation. */ + pOperation->u.publish.pReceivedData = pIncomingPacket->pRemainingData; + pIncomingPacket->pRemainingData = NULL; + + /* Add the PUBLISH to the list of operations pending processing. */ + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + IotListDouble_InsertHead( &( pMqttConnection->pendingProcessing ), + &( pOperation->link ) ); + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + + /* Increment the MQTT connection reference count before scheduling an + * incoming PUBLISH. */ + if( _IotMqtt_IncrementConnectionReferences( pMqttConnection ) == true ) + { + /* Schedule PUBLISH for callback invocation. */ + status = _IotMqtt_ScheduleOperation( pOperation, _IotMqtt_ProcessIncomingPublish, 0 ); + } + else + { + status = IOT_MQTT_NETWORK_ERROR; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Free PUBLISH operation on error. */ + if( status != IOT_MQTT_SUCCESS ) + { + /* Check ownership of the received MQTT packet. */ + if( pOperation->u.publish.pReceivedData != NULL ) + { + /* Retrieve the pointer MQTT packet pointer so it may be freed later. */ + IotMqtt_Assert( pIncomingPacket->pRemainingData == NULL ); + pIncomingPacket->pRemainingData = ( uint8_t * ) pOperation->u.publish.pReceivedData; + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Remove operation from pending processing list. */ + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + + if( IotLink_IsLinked( &( pOperation->link ) ) == true ) + { + IotListDouble_Remove( &( pOperation->link ) ); + } + else + { + EMPTY_ELSE_MARKER; + } + + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + + IotMqtt_Assert( pOperation != NULL ); + IotMqtt_FreeOperation( pOperation ); + } + else + { + EMPTY_ELSE_MARKER; + } + + break; + + case MQTT_PACKET_TYPE_PUBACK: + IotLogDebug( "(MQTT connection %p) PUBACK in data stream.", pMqttConnection ); + + /* Choose PUBACK deserializer. */ + deserialize = _IotMqtt_DeserializePuback; + + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->pSerializer != NULL ) + { + if( pMqttConnection->pSerializer->deserialize.puback != NULL ) + { + deserialize = pMqttConnection->pSerializer->deserialize.puback; + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + + /* Deserialize PUBACK and notify of result. */ + status = deserialize( pIncomingPacket ); + pOperation = _IotMqtt_FindOperation( pMqttConnection, + IOT_MQTT_PUBLISH_TO_SERVER, + &( pIncomingPacket->packetIdentifier ) ); + + if( pOperation != NULL ) + { + pOperation->u.operation.status = status; + _IotMqtt_Notify( pOperation ); + } + else + { + EMPTY_ELSE_MARKER; + } + + break; + + case MQTT_PACKET_TYPE_SUBACK: + IotLogDebug( "(MQTT connection %p) SUBACK in data stream.", pMqttConnection ); + + /* Choose SUBACK deserializer. */ + deserialize = _IotMqtt_DeserializeSuback; + + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->pSerializer != NULL ) + { + if( pMqttConnection->pSerializer->deserialize.suback != NULL ) + { + deserialize = pMqttConnection->pSerializer->deserialize.suback; + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + + /* Deserialize SUBACK and notify of result. */ + pIncomingPacket->u.pMqttConnection = pMqttConnection; + status = deserialize( pIncomingPacket ); + pOperation = _IotMqtt_FindOperation( pMqttConnection, + IOT_MQTT_SUBSCRIBE, + &( pIncomingPacket->packetIdentifier ) ); + + if( pOperation != NULL ) + { + pOperation->u.operation.status = status; + _IotMqtt_Notify( pOperation ); + } + else + { + EMPTY_ELSE_MARKER; + } + + break; + + case MQTT_PACKET_TYPE_UNSUBACK: + IotLogDebug( "(MQTT connection %p) UNSUBACK in data stream.", pMqttConnection ); + + /* Choose UNSUBACK deserializer. */ + deserialize = _IotMqtt_DeserializeUnsuback; + + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->pSerializer != NULL ) + { + if( pMqttConnection->pSerializer->deserialize.unsuback != NULL ) + { + deserialize = pMqttConnection->pSerializer->deserialize.unsuback; + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + + /* Deserialize UNSUBACK and notify of result. */ + status = deserialize( pIncomingPacket ); + pOperation = _IotMqtt_FindOperation( pMqttConnection, + IOT_MQTT_UNSUBSCRIBE, + &( pIncomingPacket->packetIdentifier ) ); + + if( pOperation != NULL ) + { + pOperation->u.operation.status = status; + _IotMqtt_Notify( pOperation ); + } + else + { + EMPTY_ELSE_MARKER; + } + + break; + + default: + /* The only remaining valid type is PINGRESP. */ + IotMqtt_Assert( ( pIncomingPacket->type & 0xf0 ) == MQTT_PACKET_TYPE_PINGRESP ); + IotLogDebug( "(MQTT connection %p) PINGRESP in data stream.", pMqttConnection ); + + /* Choose PINGRESP deserializer. */ + deserialize = _IotMqtt_DeserializePingresp; + + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->pSerializer != NULL ) + { + if( pMqttConnection->pSerializer->deserialize.pingresp != NULL ) + { + deserialize = pMqttConnection->pSerializer->deserialize.pingresp; + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + + /* Deserialize PINGRESP. */ + status = deserialize( pIncomingPacket ); + + if( status == IOT_MQTT_SUCCESS ) + { + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + + if( pMqttConnection->keepAliveFailure == false ) + { + IotLogWarn( "(MQTT connection %p) Unexpected PINGRESP received.", + pMqttConnection ); + } + else + { + IotLogDebug( "(MQTT connection %p) PINGRESP successfully parsed.", + pMqttConnection ); + + pMqttConnection->keepAliveFailure = false; + } + + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + } + else + { + EMPTY_ELSE_MARKER; + } + + break; + } + + if( status != IOT_MQTT_SUCCESS ) + { + IotLogError( "(MQTT connection %p) Packet parser status %s.", + pMqttConnection, + IotMqtt_strerror( status ) ); + } + else + { + EMPTY_ELSE_MARKER; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static void _sendPuback( _mqttConnection_t * pMqttConnection, + uint16_t packetIdentifier ) +{ + IotMqttError_t serializeStatus = IOT_MQTT_SUCCESS; + uint8_t * pPuback = NULL; + size_t pubackSize = 0, bytesSent = 0; + + /* Default PUBACK serializer and free packet functions. */ + IotMqttError_t ( * serializePuback )( uint16_t, + uint8_t **, + size_t * ) = _IotMqtt_SerializePuback; + void ( * freePacket )( uint8_t * ) = _IotMqtt_FreePacket; + + IotLogDebug( "(MQTT connection %p) Sending PUBACK for received PUBLISH %hu.", + pMqttConnection, + packetIdentifier ); + + /* Choose PUBACK serializer and free packet functions. */ + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->pSerializer != NULL ) + { + if( pMqttConnection->pSerializer->serialize.puback != NULL ) + { + serializePuback = pMqttConnection->pSerializer->serialize.puback; + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->pSerializer != NULL ) + { + if( pMqttConnection->pSerializer->freePacket != NULL ) + { + freePacket = pMqttConnection->pSerializer->freePacket; + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + + /* Generate a PUBACK packet from the packet identifier. */ + serializeStatus = serializePuback( packetIdentifier, + &pPuback, + &pubackSize ); + + if( serializeStatus != IOT_MQTT_SUCCESS ) + { + IotLogWarn( "(MQTT connection %p) Failed to generate PUBACK packet for " + "received PUBLISH %hu.", + pMqttConnection, + packetIdentifier ); + } + else + { + bytesSent = pMqttConnection->pNetworkInterface->send( pMqttConnection->pNetworkConnection, + pPuback, + pubackSize ); + + if( bytesSent != pubackSize ) + { + IotLogWarn( "(MQTT connection %p) Failed to send PUBACK for received" + " PUBLISH %hu.", + pMqttConnection, + packetIdentifier ); + } + else + { + IotLogDebug( "(MQTT connection %p) PUBACK for received PUBLISH %hu sent.", + pMqttConnection, + packetIdentifier ); + } + + freePacket( pPuback ); + } +} + +/*-----------------------------------------------------------*/ + +bool _IotMqtt_GetNextByte( void * pNetworkConnection, + const IotNetworkInterface_t * pNetworkInterface, + uint8_t * pIncomingByte ) +{ + bool status = false; + uint8_t incomingByte = 0; + size_t bytesReceived = 0; + + /* Attempt to read 1 byte. */ + bytesReceived = pNetworkInterface->receive( pNetworkConnection, + &incomingByte, + 1 ); + + /* Set the output parameter and return success if 1 byte was read. */ + if( bytesReceived == 1 ) + { + *pIncomingByte = incomingByte; + status = true; + } + else + { + /* Network receive must return 0 on failure. */ + IotMqtt_Assert( bytesReceived == 0 ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason, + _mqttConnection_t * pMqttConnection ) +{ + IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; + IotNetworkError_t closeStatus = IOT_NETWORK_SUCCESS; + IotMqttCallbackParam_t callbackParam = { .u.message = { 0 } }; + + /* Mark the MQTT connection as disconnected and the keep-alive as failed. */ + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + pMqttConnection->disconnected = true; + pMqttConnection->keepAliveFailure = true; + + if( pMqttConnection->keepAliveMs != 0 ) + { + /* Keep-alive must have a PINGREQ allocated. */ + IotMqtt_Assert( pMqttConnection->pPingreqPacket != NULL ); + IotMqtt_Assert( pMqttConnection->pingreqPacketSize != 0 ); + + /* PINGREQ provides a reference to the connection, so reference count must + * be nonzero. */ + IotMqtt_Assert( pMqttConnection->references > 0 ); + + /* Attempt to cancel the keep-alive job. */ + taskPoolStatus = IotTaskPool_TryCancel( IOT_SYSTEM_TASKPOOL, + pMqttConnection->keepAliveJob, + NULL ); + + /* If the keep-alive job was not canceled, it must be already executing. + * Any other return value is invalid. */ + IotMqtt_Assert( ( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) || + ( taskPoolStatus == IOT_TASKPOOL_CANCEL_FAILED ) ); + + /* Clean up keep-alive if its job was successfully canceled. Otherwise, + * the executing keep-alive job will clean up itself. */ + if( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) + { + /* Clean up PINGREQ packet and job. */ + _IotMqtt_FreePacket( pMqttConnection->pPingreqPacket ); + + /* Clear data about the keep-alive. */ + pMqttConnection->keepAliveMs = 0; + pMqttConnection->pPingreqPacket = NULL; + pMqttConnection->pingreqPacketSize = 0; + + /* Keep-alive is cleaned up; decrement reference count. Since this + * function must be followed with a call to DISCONNECT, a check to + * destroy the connection is not done here. */ + pMqttConnection->references--; + + IotLogDebug( "(MQTT connection %p) Keep-alive job canceled and cleaned up.", + pMqttConnection ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + + /* Close the network connection. */ + if( pMqttConnection->pNetworkInterface->close != NULL ) + { + closeStatus = pMqttConnection->pNetworkInterface->close( pMqttConnection->pNetworkConnection ); + + if( closeStatus == IOT_NETWORK_SUCCESS ) + { + IotLogInfo( "(MQTT connection %p) Network connection closed.", pMqttConnection ); + } + else + { + IotLogWarn( "(MQTT connection %p) Failed to close network connection, error %d.", + pMqttConnection, + closeStatus ); + } + } + else + { + IotLogWarn( "(MQTT connection %p) No network close function was set. Network connection" + " not closed.", pMqttConnection ); + } + + /* Invoke the disconnect callback. */ + if( pMqttConnection->disconnectCallback.function != NULL ) + { + /* Set the members of the callback parameter. */ + callbackParam.mqttConnection = pMqttConnection; + callbackParam.u.disconnectReason = disconnectReason; + + pMqttConnection->disconnectCallback.function( pMqttConnection->disconnectCallback.pCallbackContext, + &callbackParam ); + } + else + { + EMPTY_ELSE_MARKER; + } +} + +/*-----------------------------------------------------------*/ + +void IotMqtt_ReceiveCallback( void * pNetworkConnection, + void * pReceiveContext ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + _mqttPacket_t incomingPacket = { .u.pMqttConnection = NULL }; + + /* Cast context to correct type. */ + _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) pReceiveContext; + + /* Read an MQTT packet from the network. */ + status = _getIncomingPacket( pNetworkConnection, + pMqttConnection, + &incomingPacket ); + + if( status == IOT_MQTT_SUCCESS ) + { + /* Deserialize the received packet. */ + status = _deserializeIncomingPacket( pMqttConnection, + &incomingPacket ); + + /* Free any buffers allocated for the MQTT packet. */ + if( incomingPacket.pRemainingData != NULL ) + { + IotMqtt_FreeMessage( incomingPacket.pRemainingData ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Close the network connection on a bad response. */ + if( status == IOT_MQTT_BAD_RESPONSE ) + { + IotLogError( "(MQTT connection %p) Error processing incoming data. Closing connection.", + pMqttConnection ); + + _IotMqtt_CloseNetworkConnection( IOT_MQTT_BAD_PACKET_RECEIVED, + pMqttConnection ); + } + else + { + EMPTY_ELSE_MARKER; + } +} + +/*-----------------------------------------------------------*/ diff --git a/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/src/iot_mqtt_operation.c b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/src/iot_mqtt_operation.c new file mode 100644 index 000000000..7923d62b3 --- /dev/null +++ b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/src/iot_mqtt_operation.c @@ -0,0 +1,1332 @@ +/* + * Amazon FreeRTOS MQTT V2.0.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +/** + * @file iot_mqtt_operation.c + * @brief Implements functions that process MQTT operations. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Standard includes. */ +#include + +/* Error handling include. */ +#include "private/iot_error.h" + +/* MQTT internal include. */ +#include "private/iot_mqtt_internal.h" + +/* Platform layer includes. */ +#include "platform/iot_clock.h" +#include "platform/iot_threads.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief First parameter to #_mqttOperation_match. + */ +typedef struct _operationMatchParam +{ + IotMqttOperationType_t type; /**< @brief The type of operation to look for. */ + const uint16_t * pPacketIdentifier; /**< @brief The packet identifier associated with the operation. + * Set to `NULL` to ignore packet identifier. */ +} _operationMatchParam_t; + +/*-----------------------------------------------------------*/ + +/** + * @brief Match an MQTT operation by type and packet identifier. + * + * @param[in] pOperationLink Pointer to the link member of an #_mqttOperation_t. + * @param[in] pMatch Pointer to an #_operationMatchParam_t. + * + * @return `true` if the operation matches the parameters in `pArgument`; `false` + * otherwise. + */ +static bool _mqttOperation_match( const IotLink_t * pOperationLink, + void * pMatch ); + +/** + * @brief Check if an operation with retry has exceeded its retry limit. + * + * If a PUBLISH operation is available for retry, this function also sets any + * necessary DUP flags. + * + * @param[in] pOperation The operation to check. + * + * @return `true` if the operation may be retried; `false` otherwise. + */ +static bool _checkRetryLimit( _mqttOperation_t * pOperation ); + +/** + * @brief Schedule the next send of an operation with retry. + * + * @param[in] pOperation The operation to schedule. + * + * @return `true` if the reschedule succeeded; `false` otherwise. + */ +static bool _scheduleNextRetry( _mqttOperation_t * pOperation ); + +/*-----------------------------------------------------------*/ + +static bool _mqttOperation_match( const IotLink_t * pOperationLink, + void * pMatch ) +{ + bool match = false; + + /* Because this function is called from a container function, the given link + * must never be NULL. */ + IotMqtt_Assert( pOperationLink != NULL ); + + _mqttOperation_t * pOperation = IotLink_Container( _mqttOperation_t, + pOperationLink, + link ); + _operationMatchParam_t * pParam = ( _operationMatchParam_t * ) pMatch; + + /* Check for matching operations. */ + if( pParam->type == pOperation->u.operation.type ) + { + /* Check for matching packet identifiers. */ + if( pParam->pPacketIdentifier == NULL ) + { + match = true; + } + else + { + match = ( *( pParam->pPacketIdentifier ) == pOperation->u.operation.packetIdentifier ); + } + } + else + { + EMPTY_ELSE_MARKER; + } + + return match; +} + +/*-----------------------------------------------------------*/ + +static bool _checkRetryLimit( _mqttOperation_t * pOperation ) +{ + _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; + bool status = true; + + /* Choose a set DUP function. */ + void ( * publishSetDup )( uint8_t *, + uint8_t *, + uint16_t * ) = _IotMqtt_PublishSetDup; + + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->pSerializer != NULL ) + { + if( pMqttConnection->pSerializer->serialize.publishSetDup != NULL ) + { + publishSetDup = pMqttConnection->pSerializer->serialize.publishSetDup; + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + + /* Only PUBLISH may be retried. */ + IotMqtt_Assert( pOperation->u.operation.type == IOT_MQTT_PUBLISH_TO_SERVER ); + + /* Check if the retry limit is exhausted. */ + if( pOperation->u.operation.retry.count > pOperation->u.operation.retry.limit ) + { + /* The retry count may be at most one more than the retry limit, which + * accounts for the final check for a PUBACK. */ + IotMqtt_Assert( pOperation->u.operation.retry.count == pOperation->u.operation.retry.limit + 1 ); + + IotLogDebug( "(MQTT connection %p, PUBLISH operation %p) No response received after %lu retries.", + pMqttConnection, + pOperation, + pOperation->u.operation.retry.limit ); + + status = false; + } + /* Check if this is the first retry. */ + else if( pOperation->u.operation.retry.count == 1 ) + { + /* Always set the DUP flag on the first retry. */ + publishSetDup( pOperation->u.operation.pMqttPacket, + pOperation->u.operation.pPacketIdentifierHigh, + &( pOperation->u.operation.packetIdentifier ) ); + } + else + { + /* In AWS IoT MQTT mode, the DUP flag (really a change to the packet + * identifier) must be reset on every retry. */ + if( pMqttConnection->awsIotMqttMode == true ) + { + publishSetDup( pOperation->u.operation.pMqttPacket, + pOperation->u.operation.pPacketIdentifierHigh, + &( pOperation->u.operation.packetIdentifier ) ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static bool _scheduleNextRetry( _mqttOperation_t * pOperation ) +{ + bool firstRetry = false; + uint32_t scheduleDelay = 0; + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; + + /* This function should never be called with retry count greater than + * retry limit. */ + IotMqtt_Assert( pOperation->u.operation.retry.count <= pOperation->u.operation.retry.limit ); + + /* Increment the retry count. */ + ( pOperation->u.operation.retry.count )++; + + /* Check for a response shortly for the final retry. Otherwise, calculate the + * next retry period. */ + if( pOperation->u.operation.retry.count > pOperation->u.operation.retry.limit ) + { + scheduleDelay = IOT_MQTT_RESPONSE_WAIT_MS; + + IotLogDebug( "(MQTT connection %p, PUBLISH operation %p) Final retry was sent. Will check " + "for response in %d ms.", + pMqttConnection, + pOperation, + IOT_MQTT_RESPONSE_WAIT_MS ); + } + else + { + scheduleDelay = pOperation->u.operation.retry.nextPeriod; + + /* Double the retry period, subject to a ceiling value. */ + pOperation->u.operation.retry.nextPeriod *= 2; + + if( pOperation->u.operation.retry.nextPeriod > IOT_MQTT_RETRY_MS_CEILING ) + { + pOperation->u.operation.retry.nextPeriod = IOT_MQTT_RETRY_MS_CEILING; + } + else + { + EMPTY_ELSE_MARKER; + } + + IotLogDebug( "(MQTT connection %p, PUBLISH operation %p) Scheduling retry %lu of %lu in %lu ms.", + pMqttConnection, + pOperation, + ( unsigned long ) pOperation->u.operation.retry.count, + ( unsigned long ) pOperation->u.operation.retry.limit, + ( unsigned long ) scheduleDelay ); + + /* Check if this is the first retry. */ + firstRetry = ( pOperation->u.operation.retry.count == 1 ); + + /* On the first retry, the PUBLISH will be moved from the pending processing + * list to the pending responses list. Lock the connection references mutex + * to manipulate the lists. */ + if( firstRetry == true ) + { + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + + /* Reschedule the PUBLISH for another send. */ + status = _IotMqtt_ScheduleOperation( pOperation, + _IotMqtt_ProcessSend, + scheduleDelay ); + + /* Check for successful reschedule. */ + if( status == IOT_MQTT_SUCCESS ) + { + /* Move a successfully rescheduled PUBLISH from the pending processing + * list to the pending responses list on the first retry. */ + if( firstRetry == true ) + { + /* Operation must be linked. */ + IotMqtt_Assert( IotLink_IsLinked( &( pOperation->link ) ) == true ); + + /* Transfer to pending response list. */ + IotListDouble_Remove( &( pOperation->link ) ); + IotListDouble_InsertHead( &( pMqttConnection->pendingResponse ), + &( pOperation->link ) ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* The references mutex only needs to be unlocked on the first retry, since + * only the first retry manipulates the connection lists. */ + if( firstRetry == true ) + { + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + } + else + { + EMPTY_ELSE_MARKER; + } + + return( status == IOT_MQTT_SUCCESS ); +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, + uint32_t flags, + const IotMqttCallbackInfo_t * pCallbackInfo, + _mqttOperation_t ** pNewOperation ) +{ + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + bool decrementOnError = false; + _mqttOperation_t * pOperation = NULL; + bool waitable = ( ( flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE ); + + /* If the waitable flag is set, make sure that there's no callback. */ + if( waitable == true ) + { + if( pCallbackInfo != NULL ) + { + IotLogError( "Callback should not be set for a waitable operation." ); + + return IOT_MQTT_BAD_PARAMETER; + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + IotLogDebug( "(MQTT connection %p) Creating new operation record.", + pMqttConnection ); + + /* Increment the reference count for the MQTT connection when creating a new + * operation. */ + if( _IotMqtt_IncrementConnectionReferences( pMqttConnection ) == false ) + { + IotLogError( "(MQTT connection %p) New operation record cannot be created" + " for a closed connection", + pMqttConnection ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NETWORK_ERROR ); + } + else + { + /* Reference count will need to be decremented on error. */ + decrementOnError = true; + } + + /* Allocate memory for a new operation. */ + pOperation = IotMqtt_MallocOperation( sizeof( _mqttOperation_t ) ); + + if( pOperation == NULL ) + { + IotLogError( "(MQTT connection %p) Failed to allocate memory for new " + "operation record.", + pMqttConnection ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + } + else + { + /* Clear the operation data. */ + ( void ) memset( pOperation, 0x00, sizeof( _mqttOperation_t ) ); + + /* Initialize some members of the new operation. */ + pOperation->pMqttConnection = pMqttConnection; + pOperation->u.operation.jobReference = 1; + pOperation->u.operation.flags = flags; + pOperation->u.operation.status = IOT_MQTT_STATUS_PENDING; + } + + /* Check if the waitable flag is set. If it is, create a semaphore to + * wait on. */ + if( waitable == true ) + { + /* Create a semaphore to wait on for a waitable operation. */ + if( IotSemaphore_Create( &( pOperation->u.operation.notify.waitSemaphore ), 0, 1 ) == false ) + { + IotLogError( "(MQTT connection %p) Failed to create semaphore for " + "waitable operation.", + pMqttConnection ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + } + else + { + /* A waitable operation is created with an additional reference for the + * Wait function. */ + ( pOperation->u.operation.jobReference )++; + } + } + else + { + /* If the waitable flag isn't set but a callback is, copy the callback + * information. */ + if( pCallbackInfo != NULL ) + { + pOperation->u.operation.notify.callback = *pCallbackInfo; + } + else + { + EMPTY_ELSE_MARKER; + } + } + + /* Add this operation to the MQTT connection's operation list. */ + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + IotListDouble_InsertHead( &( pMqttConnection->pendingProcessing ), + &( pOperation->link ) ); + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + + /* Set the output parameter. */ + *pNewOperation = pOperation; + + /* Clean up operation and decrement reference count if this function failed. */ + IOT_FUNCTION_CLEANUP_BEGIN(); + + if( status != IOT_MQTT_SUCCESS ) + { + if( decrementOnError == true ) + { + _IotMqtt_DecrementConnectionReferences( pMqttConnection ); + } + else + { + EMPTY_ELSE_MARKER; + } + + if( pOperation != NULL ) + { + IotMqtt_FreeOperation( pOperation ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + IOT_FUNCTION_CLEANUP_END(); +} + +/*-----------------------------------------------------------*/ + +bool _IotMqtt_DecrementOperationReferences( _mqttOperation_t * pOperation, + bool cancelJob ) +{ + bool destroyOperation = false; + IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; + _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; + + /* Attempt to cancel the operation's job. */ + if( cancelJob == true ) + { + taskPoolStatus = IotTaskPool_TryCancel( IOT_SYSTEM_TASKPOOL, + pOperation->job, + NULL ); + + /* If the operation's job was not canceled, it must be already executing. + * Any other return value is invalid. */ + IotMqtt_Assert( ( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) || + ( taskPoolStatus == IOT_TASKPOOL_CANCEL_FAILED ) ); + + if( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) + { + IotLogDebug( "(MQTT connection %p, %s operation %p) Job canceled.", + pMqttConnection, + IotMqtt_OperationType( pOperation->u.operation.type ), + pOperation ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Decrement job reference count. */ + if( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) + { + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + pOperation->u.operation.jobReference--; + + IotLogDebug( "(MQTT connection %p, %s operation %p) Job reference changed" + " from %ld to %ld.", + pMqttConnection, + IotMqtt_OperationType( pOperation->u.operation.type ), + pOperation, + pOperation->u.operation.jobReference + 1, + pOperation->u.operation.jobReference ); + + /* The job reference count must be 0 or 1 after the decrement. */ + IotMqtt_Assert( ( pOperation->u.operation.jobReference == 0 ) || + ( pOperation->u.operation.jobReference == 1 ) ); + + /* This operation may be destroyed if its reference count is 0. */ + if( pOperation->u.operation.jobReference == 0 ) + { + destroyOperation = true; + } + else + { + EMPTY_ELSE_MARKER; + } + + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + } + else + { + EMPTY_ELSE_MARKER; + } + + return destroyOperation; +} + +/*-----------------------------------------------------------*/ + +void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation ) +{ + _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; + + /* Default free packet function. */ + void ( * freePacket )( uint8_t * ) = _IotMqtt_FreePacket; + + IotLogDebug( "(MQTT connection %p, %s operation %p) Destroying operation.", + pMqttConnection, + IotMqtt_OperationType( pOperation->u.operation.type ), + pOperation ); + + /* The job reference count must be between 0 and 2. */ + IotMqtt_Assert( ( pOperation->u.operation.jobReference >= 0 ) && + ( pOperation->u.operation.jobReference <= 2 ) ); + + /* Jobs to be destroyed should be removed from the MQTT connection's + * lists. */ + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + + if( IotLink_IsLinked( &( pOperation->link ) ) == true ) + { + IotLogDebug( "(MQTT connection %p, %s operation %p) Removed operation from connection lists.", + pMqttConnection, + IotMqtt_OperationType( pOperation->u.operation.type ), + pOperation, + pMqttConnection ); + + IotListDouble_Remove( &( pOperation->link ) ); + } + else + { + IotLogDebug( "(MQTT connection %p, %s operation %p) Operation was not present in connection lists.", + pMqttConnection, + IotMqtt_OperationType( pOperation->u.operation.type ), + pOperation ); + } + + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + + /* Free any allocated MQTT packet. */ + if( pOperation->u.operation.pMqttPacket != NULL ) + { + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->pSerializer != NULL ) + { + if( pMqttConnection->pSerializer->freePacket != NULL ) + { + freePacket = pMqttConnection->pSerializer->freePacket; + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + + freePacket( pOperation->u.operation.pMqttPacket ); + + IotLogDebug( "(MQTT connection %p, %s operation %p) MQTT packet freed.", + pMqttConnection, + IotMqtt_OperationType( pOperation->u.operation.type ), + pOperation ); + } + else + { + IotLogDebug( "(MQTT connection %p, %s operation %p) Operation has no allocated MQTT packet.", + pMqttConnection, + IotMqtt_OperationType( pOperation->u.operation.type ), + pOperation ); + } + + /* Check if a wait semaphore was created for this operation. */ + if( ( pOperation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE ) + { + IotSemaphore_Destroy( &( pOperation->u.operation.notify.waitSemaphore ) ); + + IotLogDebug( "(MQTT connection %p, %s operation %p) Wait semaphore destroyed.", + pMqttConnection, + IotMqtt_OperationType( pOperation->u.operation.type ), + pOperation ); + } + else + { + EMPTY_ELSE_MARKER; + } + + IotLogDebug( "(MQTT connection %p, %s operation %p) Operation record destroyed.", + pMqttConnection, + IotMqtt_OperationType( pOperation->u.operation.type ), + pOperation ); + + /* Free the memory used to hold operation data. */ + IotMqtt_FreeOperation( pOperation ); + + /* Decrement the MQTT connection's reference count after destroying an + * operation. */ + _IotMqtt_DecrementConnectionReferences( pMqttConnection ); +} + +/*-----------------------------------------------------------*/ + +void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, + IotTaskPoolJob_t pKeepAliveJob, + void * pContext ) +{ + bool status = true; + IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; + size_t bytesSent = 0; + + /* Retrieve the MQTT connection from the context. */ + _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) pContext; + + /* Check parameters. */ + IotMqtt_Assert( pTaskPool == IOT_SYSTEM_TASKPOOL ); + IotMqtt_Assert( pKeepAliveJob == pMqttConnection->keepAliveJob ); + + /* Check that keep-alive interval is valid. The MQTT spec states its maximum + * value is 65,535 seconds. */ + IotMqtt_Assert( pMqttConnection->keepAliveMs <= 65535000 ); + + /* Only two values are valid for the next keep alive job delay. */ + IotMqtt_Assert( ( pMqttConnection->nextKeepAliveMs == pMqttConnection->keepAliveMs ) || + ( pMqttConnection->nextKeepAliveMs == IOT_MQTT_RESPONSE_WAIT_MS ) ); + + IotLogDebug( "(MQTT connection %p) Keep-alive job started.", pMqttConnection ); + + /* Re-create the keep-alive job for rescheduling. This should never fail. */ + taskPoolStatus = IotTaskPool_CreateJob( _IotMqtt_ProcessKeepAlive, + pContext, + IotTaskPool_GetJobStorageFromHandle( pKeepAliveJob ), + &pKeepAliveJob ); + IotMqtt_Assert( taskPoolStatus == IOT_TASKPOOL_SUCCESS ); + + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + + /* Determine whether to send a PINGREQ or check for PINGRESP. */ + if( pMqttConnection->nextKeepAliveMs == pMqttConnection->keepAliveMs ) + { + IotLogDebug( "(MQTT connection %p) Sending PINGREQ.", pMqttConnection ); + + /* Because PINGREQ may be used to keep the MQTT connection alive, it is + * more important than other operations. Bypass the queue of jobs for + * operations by directly sending the PINGREQ in this job. */ + bytesSent = pMqttConnection->pNetworkInterface->send( pMqttConnection->pNetworkConnection, + pMqttConnection->pPingreqPacket, + pMqttConnection->pingreqPacketSize ); + + if( bytesSent != pMqttConnection->pingreqPacketSize ) + { + IotLogError( "(MQTT connection %p) Failed to send PINGREQ.", pMqttConnection ); + status = false; + } + else + { + /* Assume the keep-alive will fail. The network receive callback will + * clear the failure flag upon receiving a PINGRESP. */ + pMqttConnection->keepAliveFailure = true; + + /* Schedule a check for PINGRESP. */ + pMqttConnection->nextKeepAliveMs = IOT_MQTT_RESPONSE_WAIT_MS; + + IotLogDebug( "(MQTT connection %p) PINGREQ sent. Scheduling check for PINGRESP in %d ms.", + pMqttConnection, + IOT_MQTT_RESPONSE_WAIT_MS ); + } + } + else + { + IotLogDebug( "(MQTT connection %p) Checking for PINGRESP.", pMqttConnection ); + + if( pMqttConnection->keepAliveFailure == false ) + { + IotLogDebug( "(MQTT connection %p) PINGRESP was received.", pMqttConnection ); + + /* PINGRESP was received. Schedule the next PINGREQ transmission. */ + pMqttConnection->nextKeepAliveMs = pMqttConnection->keepAliveMs; + } + else + { + IotLogError( "(MQTT connection %p) Failed to receive PINGRESP within %d ms.", + pMqttConnection, + IOT_MQTT_RESPONSE_WAIT_MS ); + + /* The network receive callback did not clear the failure flag. */ + status = false; + } + } + + /* When a PINGREQ is successfully sent, reschedule this job to check for a + * response shortly. */ + if( status == true ) + { + taskPoolStatus = IotTaskPool_ScheduleDeferred( pTaskPool, + pKeepAliveJob, + pMqttConnection->nextKeepAliveMs ); + + if( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) + { + IotLogDebug( "(MQTT connection %p) Next keep-alive job in %d ms.", + pMqttConnection, + IOT_MQTT_RESPONSE_WAIT_MS ); + } + else + { + IotLogError( "(MQTT connection %p) Failed to reschedule keep-alive job, error %s.", + pMqttConnection, + IotTaskPool_strerror( taskPoolStatus ) ); + + status = false; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Close the connection on failures. */ + if( status == false ) + { + _IotMqtt_CloseNetworkConnection( IOT_MQTT_KEEP_ALIVE_TIMEOUT, + pMqttConnection ); + } + else + { + EMPTY_ELSE_MARKER; + } + + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); +} + +/*-----------------------------------------------------------*/ + +void _IotMqtt_ProcessIncomingPublish( IotTaskPool_t pTaskPool, + IotTaskPoolJob_t pPublishJob, + void * pContext ) +{ + _mqttOperation_t * pOperation = pContext; + IotMqttCallbackParam_t callbackParam = { .mqttConnection = NULL }; + + /* Check parameters. The task pool and job parameter is not used when asserts + * are disabled. */ + ( void ) pTaskPool; + ( void ) pPublishJob; + IotMqtt_Assert( pTaskPool == IOT_SYSTEM_TASKPOOL ); + IotMqtt_Assert( pOperation->incomingPublish == true ); + IotMqtt_Assert( pPublishJob == pOperation->job ); + + /* Remove the operation from the pending processing list. */ + IotMutex_Lock( &( pOperation->pMqttConnection->referencesMutex ) ); + + if( IotLink_IsLinked( &( pOperation->link ) ) == true ) + { + IotListDouble_Remove( &( pOperation->link ) ); + } + else + { + EMPTY_ELSE_MARKER; + } + + IotMutex_Unlock( &( pOperation->pMqttConnection->referencesMutex ) ); + + /* Process the current PUBLISH. */ + callbackParam.u.message.info = pOperation->u.publish.publishInfo; + + _IotMqtt_InvokeSubscriptionCallback( pOperation->pMqttConnection, + &callbackParam ); + + /* Free any buffers associated with the current PUBLISH message. */ + if( pOperation->u.publish.pReceivedData != NULL ) + { + IotMqtt_FreeMessage( ( void * ) pOperation->u.publish.pReceivedData ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Free the incoming PUBLISH operation. */ + IotMqtt_FreeOperation( pOperation ); +} + +/*-----------------------------------------------------------*/ + +void _IotMqtt_ProcessSend( IotTaskPool_t pTaskPool, + IotTaskPoolJob_t pSendJob, + void * pContext ) +{ + size_t bytesSent = 0; + bool destroyOperation = false, waitable = false, networkPending = false; + _mqttOperation_t * pOperation = ( _mqttOperation_t * ) pContext; + _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; + + /* Check parameters. The task pool and job parameter is not used when asserts + * are disabled. */ + ( void ) pTaskPool; + ( void ) pSendJob; + IotMqtt_Assert( pTaskPool == IOT_SYSTEM_TASKPOOL ); + IotMqtt_Assert( pSendJob == pOperation->job ); + + /* The given operation must have an allocated packet and be waiting for a status. */ + IotMqtt_Assert( pOperation->u.operation.pMqttPacket != NULL ); + IotMqtt_Assert( pOperation->u.operation.packetSize != 0 ); + IotMqtt_Assert( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); + + /* Check if this operation is waitable. */ + waitable = ( pOperation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE; + + /* Check PUBLISH retry counts and limits. */ + if( pOperation->u.operation.retry.limit > 0 ) + { + if( _checkRetryLimit( pOperation ) == false ) + { + pOperation->u.operation.status = IOT_MQTT_RETRY_NO_RESPONSE; + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Send an operation that is waiting for a response. */ + if( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ) + { + IotLogDebug( "(MQTT connection %p, %s operation %p) Sending MQTT packet.", + pMqttConnection, + IotMqtt_OperationType( pOperation->u.operation.type ), + pOperation ); + + /* Transmit the MQTT packet from the operation over the network. */ + bytesSent = pMqttConnection->pNetworkInterface->send( pMqttConnection->pNetworkConnection, + pOperation->u.operation.pMqttPacket, + pOperation->u.operation.packetSize ); + + /* Check transmission status. */ + if( bytesSent != pOperation->u.operation.packetSize ) + { + pOperation->u.operation.status = IOT_MQTT_NETWORK_ERROR; + } + else + { + /* DISCONNECT operations are considered successful upon successful + * transmission. In addition, non-waitable operations with no callback + * may also be considered successful. */ + if( pOperation->u.operation.type == IOT_MQTT_DISCONNECT ) + { + /* DISCONNECT operations are always waitable. */ + IotMqtt_Assert( waitable == true ); + + pOperation->u.operation.status = IOT_MQTT_SUCCESS; + } + else if( waitable == false ) + { + if( pOperation->u.operation.notify.callback.function == NULL ) + { + pOperation->u.operation.status = IOT_MQTT_SUCCESS; + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Check if this operation requires further processing. */ + if( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ) + { + /* Check if this operation should be scheduled for retransmission. */ + if( pOperation->u.operation.retry.limit > 0 ) + { + if( _scheduleNextRetry( pOperation ) == false ) + { + pOperation->u.operation.status = IOT_MQTT_SCHEDULING_ERROR; + } + else + { + /* A successfully scheduled PUBLISH retry is awaiting a response + * from the network. */ + networkPending = true; + } + } + else + { + /* Decrement reference count to signal completion of send job. Check + * if the operation should be destroyed. */ + if( waitable == true ) + { + destroyOperation = _IotMqtt_DecrementOperationReferences( pOperation, false ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* If the operation should not be destroyed, transfer it from the + * pending processing to the pending response list. */ + if( destroyOperation == false ) + { + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + + /* Operation must be linked. */ + IotMqtt_Assert( IotLink_IsLinked( &( pOperation->link ) ) ); + + /* Transfer to pending response list. */ + IotListDouble_Remove( &( pOperation->link ) ); + IotListDouble_InsertHead( &( pMqttConnection->pendingResponse ), + &( pOperation->link ) ); + + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + + /* This operation is now awaiting a response from the network. */ + networkPending = true; + } + else + { + EMPTY_ELSE_MARKER; + } + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Destroy the operation or notify of completion if necessary. */ + if( destroyOperation == true ) + { + _IotMqtt_DestroyOperation( pOperation ); + } + else + { + /* Do not check the operation status if a network response is pending, + * since a network response could modify the status. */ + if( networkPending == false ) + { + /* Notify of operation completion if this job set a status. */ + if( pOperation->u.operation.status != IOT_MQTT_STATUS_PENDING ) + { + _IotMqtt_Notify( pOperation ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + } +} + +/*-----------------------------------------------------------*/ + +void _IotMqtt_ProcessCompletedOperation( IotTaskPool_t pTaskPool, + IotTaskPoolJob_t pOperationJob, + void * pContext ) +{ + _mqttOperation_t * pOperation = ( _mqttOperation_t * ) pContext; + IotMqttCallbackParam_t callbackParam = { 0 }; + + /* Check parameters. The task pool and job parameter is not used when asserts + * are disabled. */ + ( void ) pTaskPool; + ( void ) pOperationJob; + IotMqtt_Assert( pTaskPool == IOT_SYSTEM_TASKPOOL ); + IotMqtt_Assert( pOperationJob == pOperation->job ); + + /* The operation's callback function and status must be set. */ + IotMqtt_Assert( pOperation->u.operation.notify.callback.function != NULL ); + IotMqtt_Assert( pOperation->u.operation.status != IOT_MQTT_STATUS_PENDING ); + + callbackParam.mqttConnection = pOperation->pMqttConnection; + callbackParam.u.operation.type = pOperation->u.operation.type; + callbackParam.u.operation.reference = pOperation; + callbackParam.u.operation.result = pOperation->u.operation.status; + + /* Invoke the user callback function. */ + pOperation->u.operation.notify.callback.function( pOperation->u.operation.notify.callback.pCallbackContext, + &callbackParam ); + + /* Attempt to destroy the operation once the user callback returns. */ + if( _IotMqtt_DecrementOperationReferences( pOperation, false ) == true ) + { + _IotMqtt_DestroyOperation( pOperation ); + } + else + { + EMPTY_ELSE_MARKER; + } +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t _IotMqtt_ScheduleOperation( _mqttOperation_t * pOperation, + IotTaskPoolRoutine_t jobRoutine, + uint32_t delay ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; + + /* Check that job routine is valid. */ + IotMqtt_Assert( ( jobRoutine == _IotMqtt_ProcessSend ) || + ( jobRoutine == _IotMqtt_ProcessCompletedOperation ) || + ( jobRoutine == _IotMqtt_ProcessIncomingPublish ) ); + + /* Creating a new job should never fail when parameters are valid. */ + taskPoolStatus = IotTaskPool_CreateJob( jobRoutine, + pOperation, + &( pOperation->jobStorage ), + &( pOperation->job ) ); + IotMqtt_Assert( taskPoolStatus == IOT_TASKPOOL_SUCCESS ); + + /* Schedule the new job with a delay. */ + taskPoolStatus = IotTaskPool_ScheduleDeferred( IOT_SYSTEM_TASKPOOL, + pOperation->job, + delay ); + + if( taskPoolStatus != IOT_TASKPOOL_SUCCESS ) + { + /* Scheduling a newly-created job should never be invalid or illegal. */ + IotMqtt_Assert( taskPoolStatus != IOT_TASKPOOL_BAD_PARAMETER ); + IotMqtt_Assert( taskPoolStatus != IOT_TASKPOOL_ILLEGAL_OPERATION ); + + IotLogWarn( "(MQTT connection %p, %s operation %p) Failed to schedule operation job, error %s.", + pOperation->pMqttConnection, + IotMqtt_OperationType( pOperation->u.operation.type ), + pOperation, + IotTaskPool_strerror( taskPoolStatus ) ); + + status = IOT_MQTT_SCHEDULING_ERROR; + } + else + { + EMPTY_ELSE_MARKER; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +_mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, + IotMqttOperationType_t type, + const uint16_t * pPacketIdentifier ) +{ + bool waitable = false; + IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; + _mqttOperation_t * pResult = NULL; + IotLink_t * pResultLink = NULL; + _operationMatchParam_t param = { .type = type, .pPacketIdentifier = pPacketIdentifier }; + + if( pPacketIdentifier != NULL ) + { + IotLogDebug( "(MQTT connection %p) Searching for operation %s pending response " + "with packet identifier %hu.", + pMqttConnection, + IotMqtt_OperationType( type ), + *pPacketIdentifier ); + } + else + { + IotLogDebug( "(MQTT connection %p) Searching for operation %s pending response.", + pMqttConnection, + IotMqtt_OperationType( type ) ); + } + + /* Find and remove the first matching element in the list. */ + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + pResultLink = IotListDouble_FindFirstMatch( &( pMqttConnection->pendingResponse ), + NULL, + _mqttOperation_match, + ¶m ); + + /* Check if a match was found. */ + if( pResultLink != NULL ) + { + /* Get operation pointer and check if it is waitable. */ + pResult = IotLink_Container( _mqttOperation_t, pResultLink, link ); + waitable = ( pResult->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE; + + /* Check if the matched operation is a PUBLISH with retry. If it is, cancel + * the retry job. */ + if( pResult->u.operation.retry.limit > 0 ) + { + taskPoolStatus = IotTaskPool_TryCancel( IOT_SYSTEM_TASKPOOL, + pResult->job, + NULL ); + + /* If the retry job could not be canceled, then it is currently + * executing. Ignore the operation. */ + if( taskPoolStatus != IOT_TASKPOOL_SUCCESS ) + { + pResult = NULL; + } + else + { + /* Check job reference counts. A waitable operation should have a + * count of 2; a non-waitable operation should have a count of 1. */ + IotMqtt_Assert( pResult->u.operation.jobReference == ( 1 + ( waitable == true ) ) ); + } + } + else + { + /* An operation with no retry in the pending responses list should + * always have a job reference of 1. */ + IotMqtt_Assert( pResult->u.operation.jobReference == 1 ); + + /* Increment job references of a waitable operation to prevent Wait from + * destroying this operation if it times out. */ + if( waitable == true ) + { + ( pResult->u.operation.jobReference )++; + + IotLogDebug( "(MQTT connection %p, %s operation %p) Job reference changed from %ld to %ld.", + pMqttConnection, + IotMqtt_OperationType( type ), + pResult, + ( long int ) ( pResult->u.operation.jobReference - 1 ), + ( long int ) ( pResult->u.operation.jobReference ) ); + } + } + } + else + { + EMPTY_ELSE_MARKER; + } + + if( pResult != NULL ) + { + IotLogDebug( "(MQTT connection %p) Found operation %s." , + pMqttConnection, + IotMqtt_OperationType( type ) ); + + /* Remove the matched operation from the list. */ + IotListDouble_Remove( &( pResult->link ) ); + } + else + { + IotLogDebug( "(MQTT connection %p) Operation %s not found.", + pMqttConnection, + IotMqtt_OperationType( type ) ); + } + + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + + return pResult; +} + +/*-----------------------------------------------------------*/ + +void _IotMqtt_Notify( _mqttOperation_t * pOperation ) +{ + IotMqttError_t status = IOT_MQTT_SCHEDULING_ERROR; + _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; + + /* Check if operation is waitable. */ + bool waitable = ( pOperation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE; + + /* Remove any lingering subscriptions if a SUBSCRIBE failed. Rejected + * subscriptions are removed by the deserializer, so not removed here. */ + if( pOperation->u.operation.type == IOT_MQTT_SUBSCRIBE ) + { + switch( pOperation->u.operation.status ) + { + case IOT_MQTT_SUCCESS: + break; + + case IOT_MQTT_SERVER_REFUSED: + break; + + default: + _IotMqtt_RemoveSubscriptionByPacket( pOperation->pMqttConnection, + pOperation->u.operation.packetIdentifier, + -1 ); + break; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Schedule callback invocation for non-waitable operation. */ + if( waitable == false ) + { + /* Non-waitable operation should have job reference of 1. */ + IotMqtt_Assert( pOperation->u.operation.jobReference == 1 ); + + /* Schedule an invocation of the callback. */ + if( pOperation->u.operation.notify.callback.function != NULL ) + { + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + + status = _IotMqtt_ScheduleOperation( pOperation, + _IotMqtt_ProcessCompletedOperation, + 0 ); + + if( status == IOT_MQTT_SUCCESS ) + { + IotLogDebug( "(MQTT connection %p, %s operation %p) Callback scheduled.", + pOperation->pMqttConnection, + IotMqtt_OperationType( pOperation->u.operation.type ), + pOperation ); + + /* Place the scheduled operation back in the list of operations pending + * processing. */ + if( IotLink_IsLinked( &( pOperation->link ) ) == true ) + { + IotListDouble_Remove( &( pOperation->link ) ); + } + else + { + EMPTY_ELSE_MARKER; + } + + IotListDouble_InsertHead( &( pMqttConnection->pendingProcessing ), + &( pOperation->link ) ); + } + else + { + IotLogWarn( "(MQTT connection %p, %s operation %p) Failed to schedule callback.", + pOperation->pMqttConnection, + IotMqtt_OperationType( pOperation->u.operation.type ), + pOperation ); + } + + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Operations that weren't scheduled may be destroyed. */ + if( status == IOT_MQTT_SCHEDULING_ERROR ) + { + /* Decrement reference count of operations not scheduled. */ + if( _IotMqtt_DecrementOperationReferences( pOperation, false ) == true ) + { + _IotMqtt_DestroyOperation( pOperation ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Post to a waitable operation's semaphore. */ + if( waitable == true ) + { + IotLogDebug( "(MQTT connection %p, %s operation %p) Waitable operation " + "notified of completion.", + pOperation->pMqttConnection, + IotMqtt_OperationType( pOperation->u.operation.type ), + pOperation ); + + IotSemaphore_Post( &( pOperation->u.operation.notify.waitSemaphore ) ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + IotMqtt_Assert( status == IOT_MQTT_SUCCESS ); + } +} + +/*-----------------------------------------------------------*/ diff --git a/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/src/iot_mqtt_serialize.c b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/src/iot_mqtt_serialize.c new file mode 100644 index 000000000..f42c80c6a --- /dev/null +++ b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/src/iot_mqtt_serialize.c @@ -0,0 +1,1939 @@ +/* + * Amazon FreeRTOS MQTT V2.0.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +/** + * @file iot_mqtt_serialize.c + * @brief Implements functions that generate and decode MQTT network packets. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Standard includes. */ +#include + +/* Error handling include. */ +#include "private/iot_error.h" + +/* MQTT internal includes. */ +#include "private/iot_mqtt_internal.h" + +/* Platform layer includes. */ +#include "platform/iot_threads.h" + +/* Atomic operations. */ +#include "iot_atomic.h" + +/*-----------------------------------------------------------*/ + +/* + * Macros for reading the high and low byte of a 2-byte unsigned int. + */ +#define UINT16_HIGH_BYTE( x ) ( ( uint8_t ) ( x >> 8 ) ) /**< @brief Get high byte. */ +#define UINT16_LOW_BYTE( x ) ( ( uint8_t ) ( x & 0x00ff ) ) /**< @brief Get low byte. */ + +/** + * @brief Macro for decoding a 2-byte unsigned int from a sequence of bytes. + * + * @param[in] ptr A uint8_t* that points to the high byte. + */ +#define UINT16_DECODE( ptr ) \ + ( uint16_t ) ( ( ( ( uint16_t ) ( *( ptr ) ) ) << 8 ) | \ + ( ( uint16_t ) ( *( ptr + 1 ) ) ) ) + +/** + * @brief Macro for setting a bit in a 1-byte unsigned int. + * + * @param[in] x The unsigned int to set. + * @param[in] position Which bit to set. + */ +#define UINT8_SET_BIT( x, position ) ( x = ( uint8_t ) ( x | ( 0x01 << position ) ) ) + +/** + * @brief Macro for checking if a bit is set in a 1-byte unsigned int. + * + * @param[in] x The unsigned int to check. + * @param[in] position Which bit to check. + */ +#define UINT8_CHECK_BIT( x, position ) ( ( x & ( 0x01 << position ) ) == ( 0x01 << position ) ) + +/* + * Positions of each flag in the "Connect Flag" field of an MQTT CONNECT + * packet. + */ +#define MQTT_CONNECT_FLAG_CLEAN ( 1 ) /**< @brief Clean session. */ +#define MQTT_CONNECT_FLAG_WILL ( 2 ) /**< @brief Will present. */ +#define MQTT_CONNECT_FLAG_WILL_QOS1 ( 3 ) /**< @brief Will QoS1. */ +#define MQTT_CONNECT_FLAG_WILL_QOS2 ( 4 ) /**< @brief Will QoS2. */ +#define MQTT_CONNECT_FLAG_WILL_RETAIN ( 5 ) /**< @brief Will retain. */ +#define MQTT_CONNECT_FLAG_PASSWORD ( 6 ) /**< @brief Password present. */ +#define MQTT_CONNECT_FLAG_USERNAME ( 7 ) /**< @brief Username present. */ + +/* + * Positions of each flag in the first byte of an MQTT PUBLISH packet's + * fixed header. + */ +#define MQTT_PUBLISH_FLAG_RETAIN ( 0 ) /**< @brief Message retain flag. */ +#define MQTT_PUBLISH_FLAG_QOS1 ( 1 ) /**< @brief Publish QoS 1. */ +#define MQTT_PUBLISH_FLAG_QOS2 ( 2 ) /**< @brief Publish QoS 2. */ +#define MQTT_PUBLISH_FLAG_DUP ( 3 ) /**< @brief Duplicate message. */ + +/** + * @brief The constant specifying MQTT version 3.1.1. Placed in the CONNECT packet. + */ +#define MQTT_VERSION_3_1_1 ( ( uint8_t ) 4U ) + +/** + * @brief Per the MQTT 3.1.1 spec, the largest "Remaining Length" of an MQTT + * packet is this value. + */ +#define MQTT_MAX_REMAINING_LENGTH ( 268435455UL ) + +/** + * @brief The maximum possible size of a CONNECT packet. + * + * All strings in a CONNECT packet are constrained to 2-byte lengths, giving a + * maximum length smaller than the max "Remaining Length" constant above. + */ +#define MQTT_PACKET_CONNECT_MAX_SIZE ( 327700UL ) + +/* + * Constants relating to CONNACK packets, defined by MQTT 3.1.1 spec. + */ +#define MQTT_PACKET_CONNACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief A CONNACK packet always has a "Remaining length" of 2. */ +#define MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ( ( uint8_t ) 0x01 ) /**< @brief The "Session Present" bit is always the lowest bit. */ + +/* + * Constants relating to PUBLISH and PUBACK packets, defined by MQTT + * 3.1.1 spec. + */ +#define MQTT_PACKET_PUBACK_SIZE ( 4 ) /**< @brief A PUBACK packet is always 4 bytes in size. */ +#define MQTT_PACKET_PUBACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief A PUBACK packet always has a "Remaining length" of 2. */ + +/* + * Constants relating to SUBACK and UNSUBACK packets, defined by MQTT + * 3.1.1 spec. + */ +#define MQTT_PACKET_SUBACK_MINIMUM_SIZE ( 5 ) /**< @brief The size of the smallest valid SUBACK packet. */ +#define MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief An UNSUBACK packet always has a "Remaining length" of 2. */ + +/* + * Constants relating to PINGREQ and PINGRESP packets, defined by MQTT 3.1.1 spec. + */ +#define MQTT_PACKET_PINGREQ_SIZE ( 2 ) /**< @brief A PINGREQ packet is always 2 bytes in size. */ +#define MQTT_PACKET_PINGRESP_REMAINING_LENGTH ( 0 ) /**< @brief A PINGRESP packet always has a "Remaining length" of 0. */ + +/* + * Constants relating to DISCONNECT packets, defined by MQTT 3.1.1 spec. + */ +#define MQTT_PACKET_DISCONNECT_SIZE ( 2 ) /**< @brief A DISCONNECT packet is always 2 bytes in size. */ + +/* Username for metrics with AWS IoT. */ +#if AWS_IOT_MQTT_ENABLE_METRICS == 1 || DOXYGEN == 1 + #ifndef AWS_IOT_METRICS_USERNAME + +/** + * @brief Specify C SDK and version. + */ + #define AWS_IOT_METRICS_USERNAME "?SDK=C&Version=4.0.0" + +/** + * @brief The length of #AWS_IOT_METRICS_USERNAME. + */ + #define AWS_IOT_METRICS_USERNAME_LENGTH ( ( uint16_t ) sizeof( AWS_IOT_METRICS_USERNAME ) - 1 ) + #endif /* ifndef AWS_IOT_METRICS_USERNAME */ +#endif /* if AWS_IOT_MQTT_ENABLE_METRICS == 1 || DOXYGEN == 1 */ + +/*-----------------------------------------------------------*/ + +/** + * @brief Generate and return a 2-byte packet identifier. + * + * This packet identifier will be nonzero. + * + * @return The packet identifier. + */ +static uint16_t _nextPacketIdentifier( void ); + +/** + * @brief Calculate the number of bytes required to encode an MQTT + * "Remaining length" field. + * + * @param[in] length The value of the "Remaining length" to encode. + * + * @return The size of the encoding of length. This is always `1`, `2`, `3`, or `4`. + */ +static size_t _remainingLengthEncodedSize( size_t length ); + +/** + * @brief Encode the "Remaining length" field per MQTT spec. + * + * @param[out] pDestination Where to write the encoded "Remaining length". + * @param[in] length The "Remaining length" to encode. + * + * @return Pointer to the end of the encoded "Remaining length", which is 1-4 + * bytes greater than `pDestination`. + * + * @warning This function does not check the size of `pDestination`! Ensure that + * `pDestination` is large enough to hold the encoded "Remaining length" using + * the function #_remainingLengthEncodedSize to avoid buffer overflows. + */ +static uint8_t * _encodeRemainingLength( uint8_t * pDestination, + size_t length ); + +/** + * @brief Encode a C string as a UTF-8 string, per MQTT 3.1.1 spec. + * + * @param[out] pDestination Where to write the encoded string. + * @param[in] source The string to encode. + * @param[in] sourceLength The length of source. + * + * @return Pointer to the end of the encoded string, which is `sourceLength+2` + * bytes greater than `pDestination`. + * + * @warning This function does not check the size of `pDestination`! Ensure that + * `pDestination` is large enough to hold `sourceLength+2` bytes to avoid a buffer + * overflow. + */ +static uint8_t * _encodeString( uint8_t * pDestination, + const char * source, + uint16_t sourceLength ); + +/** + * @brief Calculate the size and "Remaining length" of a CONNECT packet generated + * from the given parameters. + * + * @param[in] pConnectInfo User-provided CONNECT information struct. + * @param[out] pRemainingLength Output for calculated "Remaining length" field. + * @param[out] pPacketSize Output for calculated total packet size. + * + * @return `true` if the packet is within the length allowed by MQTT 3.1.1 spec; `false` + * otherwise. If this function returns `false`, the output parameters should be ignored. + */ +static bool _connectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, + size_t * pRemainingLength, + size_t * pPacketSize ); + +/** + * @brief Calculate the size and "Remaining length" of a PUBLISH packet generated + * from the given parameters. + * + * @param[in] pPublishInfo User-provided PUBLISH information struct. + * @param[out] pRemainingLength Output for calculated "Remaining length" field. + * @param[out] pPacketSize Output for calculated total packet size. + * + * @return `true` if the packet is within the length allowed by MQTT 3.1.1 spec; `false` + * otherwise. If this function returns `false`, the output parameters should be ignored. + */ +static bool _publishPacketSize( const IotMqttPublishInfo_t * pPublishInfo, + size_t * pRemainingLength, + size_t * pPacketSize ); + +/** + * @brief Calculate the size and "Remaining length" of a SUBSCRIBE or UNSUBSCRIBE + * packet generated from the given parameters. + * + * @param[in] type Either IOT_MQTT_SUBSCRIBE or IOT_MQTT_UNSUBSCRIBE. + * @param[in] pSubscriptionList User-provided array of subscriptions. + * @param[in] subscriptionCount Size of `pSubscriptionList`. + * @param[out] pRemainingLength Output for calculated "Remaining length" field. + * @param[out] pPacketSize Output for calculated total packet size. + * + * @return `true` if the packet is within the length allowed by MQTT 3.1.1 spec; `false` + * otherwise. If this function returns `false`, the output parameters should be ignored. + */ +static bool _subscriptionPacketSize( IotMqttOperationType_t type, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + size_t * pRemainingLength, + size_t * pPacketSize ); + +/*-----------------------------------------------------------*/ + +#if LIBRARY_LOG_LEVEL > IOT_LOG_NONE + +/** + * @brief If logging is enabled, define a log configuration that only prints the log + * string. This is used when printing out details of deserialized MQTT packets. + */ + static const IotLogConfig_t _logHideAll = + { + .hideLibraryName = true, + .hideLogLevel = true, + .hideTimestring = true + }; +#endif + +/*-----------------------------------------------------------*/ + +static uint16_t _nextPacketIdentifier( void ) +{ + /* MQTT specifies 2 bytes for the packet identifier; however, operating on + * 32-bit integers is generally faster. */ + static uint32_t nextPacketIdentifier = 1; + + /* The next packet identifier will be greater by 2. This prevents packet + * identifiers from ever being 0, which is not allowed by MQTT 3.1.1. Packet + * identifiers will follow the sequence 1,3,5...65535,1,3,5... */ + return ( uint16_t ) Atomic_Add_u32( &nextPacketIdentifier, 2 ); +} + +/*-----------------------------------------------------------*/ + +static size_t _remainingLengthEncodedSize( size_t length ) +{ + size_t encodedSize = 0; + + /* length should have already been checked before calling this function. */ + IotMqtt_Assert( length <= MQTT_MAX_REMAINING_LENGTH ); + + /* Determine how many bytes are needed to encode length. + * The values below are taken from the MQTT 3.1.1 spec. */ + + /* 1 byte is needed to encode lengths between 0 and 127. */ + if( length < 128 ) + { + encodedSize = 1; + } + /* 2 bytes are needed to encode lengths between 128 and 16,383. */ + else if( length < 16384 ) + { + encodedSize = 2; + } + /* 3 bytes are needed to encode lengths between 16,384 and 2,097,151. */ + else if( length < 2097152 ) + { + encodedSize = 3; + } + /* 4 bytes are needed to encode lengths between 2,097,152 and 268,435,455. */ + else + { + encodedSize = 4; + } + + return encodedSize; +} + +/*-----------------------------------------------------------*/ + +static uint8_t * _encodeRemainingLength( uint8_t * pDestination, + size_t length ) +{ + uint8_t lengthByte = 0, * pLengthEnd = pDestination; + + /* This algorithm is copied from the MQTT v3.1.1 spec. */ + do + { + lengthByte = length % 128; + length = length / 128; + + /* Set the high bit of this byte, indicating that there's more data. */ + if( length > 0 ) + { + UINT8_SET_BIT( lengthByte, 7 ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Output a single encoded byte. */ + *pLengthEnd = lengthByte; + pLengthEnd++; + } while( length > 0 ); + + return pLengthEnd; +} + +/*-----------------------------------------------------------*/ + +static uint8_t * _encodeString( uint8_t * pDestination, + const char * source, + uint16_t sourceLength ) +{ + /* The first byte of a UTF-8 string is the high byte of the string length. */ + *pDestination = UINT16_HIGH_BYTE( sourceLength ); + pDestination++; + + /* The second byte of a UTF-8 string is the low byte of the string length. */ + *pDestination = UINT16_LOW_BYTE( sourceLength ); + pDestination++; + + /* Copy the string into pDestination. */ + ( void ) memcpy( pDestination, source, sourceLength ); + + /* Return the pointer to the end of the encoded string. */ + pDestination += sourceLength; + + return pDestination; +} + +/*-----------------------------------------------------------*/ + +static bool _connectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, + size_t * pRemainingLength, + size_t * pPacketSize ) +{ + bool status = true; + size_t connectPacketSize = 0, remainingLength = 0; + + /* The CONNECT packet will always include a 10-byte variable header. */ + connectPacketSize += 10U; + + /* Add the length of the client identifier if provided. */ + if( pConnectInfo->clientIdentifierLength > 0 ) + { + connectPacketSize += pConnectInfo->clientIdentifierLength + sizeof( uint16_t ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Add the lengths of the will message and topic name if provided. */ + if( pConnectInfo->pWillInfo != NULL ) + { + connectPacketSize += pConnectInfo->pWillInfo->topicNameLength + sizeof( uint16_t ) + + pConnectInfo->pWillInfo->payloadLength + sizeof( uint16_t ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Depending on the status of metrics, add the length of the metrics username + * or the user-provided username. */ + if( pConnectInfo->awsIotMqttMode == true ) + { + #if AWS_IOT_MQTT_ENABLE_METRICS == 1 + connectPacketSize += AWS_IOT_METRICS_USERNAME_LENGTH + sizeof( uint16_t ); + #endif + } + else + { + /* Add the lengths of the username and password if provided and not + * connecting to an AWS IoT MQTT server. */ + if( pConnectInfo->pUserName != NULL ) + { + connectPacketSize += pConnectInfo->userNameLength + sizeof( uint16_t ); + } + else + { + EMPTY_ELSE_MARKER; + } + + if( pConnectInfo->pPassword != NULL ) + { + connectPacketSize += pConnectInfo->passwordLength + sizeof( uint16_t ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + + /* At this point, the "Remaining Length" field of the MQTT CONNECT packet has + * been calculated. */ + remainingLength = connectPacketSize; + + /* Calculate the full size of the MQTT CONNECT packet by adding the size of + * the "Remaining Length" field plus 1 byte for the "Packet Type" field. */ + connectPacketSize += 1 + _remainingLengthEncodedSize( connectPacketSize ); + + /* Check that the CONNECT packet is within the bounds of the MQTT spec. */ + if( connectPacketSize > MQTT_PACKET_CONNECT_MAX_SIZE ) + { + status = false; + } + else + { + *pRemainingLength = remainingLength; + *pPacketSize = connectPacketSize; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static bool _publishPacketSize( const IotMqttPublishInfo_t * pPublishInfo, + size_t * pRemainingLength, + size_t * pPacketSize ) +{ + bool status = true; + size_t publishPacketSize = 0, payloadLimit = 0; + + /* The variable header of a PUBLISH packet always contains the topic name. */ + publishPacketSize += pPublishInfo->topicNameLength + sizeof( uint16_t ); + + /* The variable header of a QoS 1 or 2 PUBLISH packet contains a 2-byte + * packet identifier. */ + if( pPublishInfo->qos > IOT_MQTT_QOS_0 ) + { + publishPacketSize += sizeof( uint16_t ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Calculate the maximum allowed size of the payload for the given parameters. + * This calculation excludes the "Remaining length" encoding, whose size is not + * yet known. */ + payloadLimit = MQTT_MAX_REMAINING_LENGTH - publishPacketSize - 1; + + /* Ensure that the given payload fits within the calculated limit. */ + if( pPublishInfo->payloadLength > payloadLimit ) + { + status = false; + } + else + { + /* Add the length of the PUBLISH payload. At this point, the "Remaining length" + * has been calculated. */ + publishPacketSize += pPublishInfo->payloadLength; + + /* Now that the "Remaining length" is known, recalculate the payload limit + * based on the size of its encoding. */ + payloadLimit -= _remainingLengthEncodedSize( publishPacketSize ); + + /* Check that the given payload fits within the size allowed by MQTT spec. */ + if( pPublishInfo->payloadLength > payloadLimit ) + { + status = false; + } + else + { + /* Set the "Remaining length" output parameter and calculate the full + * size of the PUBLISH packet. */ + *pRemainingLength = publishPacketSize; + + publishPacketSize += 1 + _remainingLengthEncodedSize( publishPacketSize ); + *pPacketSize = publishPacketSize; + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static bool _subscriptionPacketSize( IotMqttOperationType_t type, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + size_t * pRemainingLength, + size_t * pPacketSize ) +{ + bool status = true; + size_t i = 0, subscriptionPacketSize = 0; + + /* Only SUBSCRIBE and UNSUBSCRIBE operations should call this function. */ + IotMqtt_Assert( ( type == IOT_MQTT_SUBSCRIBE ) || ( type == IOT_MQTT_UNSUBSCRIBE ) ); + + /* The variable header of a subscription packet consists of a 2-byte packet + * identifier. */ + subscriptionPacketSize += sizeof( uint16_t ); + + /* Sum the lengths of all subscription topic filters; add 1 byte for each + * subscription's QoS if type is IOT_MQTT_SUBSCRIBE. */ + for( i = 0; i < subscriptionCount; i++ ) + { + /* Add the length of the topic filter. */ + subscriptionPacketSize += pSubscriptionList[ i ].topicFilterLength + sizeof( uint16_t ); + + /* Only SUBSCRIBE packets include the QoS. */ + if( type == IOT_MQTT_SUBSCRIBE ) + { + subscriptionPacketSize += 1; + } + else + { + EMPTY_ELSE_MARKER; + } + } + + /* At this point, the "Remaining length" has been calculated. Return error + * if the "Remaining length" exceeds what is allowed by MQTT 3.1.1. Otherwise, + * set the output parameter.*/ + if( subscriptionPacketSize > MQTT_MAX_REMAINING_LENGTH ) + { + status = false; + } + else + { + *pRemainingLength = subscriptionPacketSize; + + /* Calculate the full size of the subscription packet by adding the size of the + * "Remaining length" field plus 1 byte for the "Packet type" field. Set the + * pPacketSize output parameter. */ + subscriptionPacketSize += 1 + _remainingLengthEncodedSize( subscriptionPacketSize ); + *pPacketSize = subscriptionPacketSize; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +uint8_t _IotMqtt_GetPacketType( void * pNetworkConnection, + const IotNetworkInterface_t * pNetworkInterface ) +{ + uint8_t packetType = 0xff; + + /* The MQTT packet type is in the first byte of the packet. */ + ( void ) _IotMqtt_GetNextByte( pNetworkConnection, + pNetworkInterface, + &packetType ); + + return packetType; +} + +/*-----------------------------------------------------------*/ + +size_t _IotMqtt_GetRemainingLength( void * pNetworkConnection, + const IotNetworkInterface_t * pNetworkInterface ) +{ + uint8_t encodedByte = 0; + size_t remainingLength = 0, multiplier = 1, bytesDecoded = 0, expectedSize = 0; + + /* This algorithm is copied from the MQTT v3.1.1 spec. */ + do + { + if( multiplier > 2097152 ) /* 128 ^ 3 */ + { + remainingLength = MQTT_REMAINING_LENGTH_INVALID; + break; + } + else + { + if( _IotMqtt_GetNextByte( pNetworkConnection, + pNetworkInterface, + &encodedByte ) == true ) + { + remainingLength += ( encodedByte & 0x7F ) * multiplier; + multiplier *= 128; + bytesDecoded++; + } + else + { + remainingLength = MQTT_REMAINING_LENGTH_INVALID; + break; + } + } + } while( ( encodedByte & 0x80 ) != 0 ); + + /* Check that the decoded remaining length conforms to the MQTT specification. */ + if( remainingLength != MQTT_REMAINING_LENGTH_INVALID ) + { + expectedSize = _remainingLengthEncodedSize( remainingLength ); + + if( bytesDecoded != expectedSize ) + { + remainingLength = MQTT_REMAINING_LENGTH_INVALID; + } + else + { + /* Valid remaining length should be at most 4 bytes. */ + IotMqtt_Assert( bytesDecoded <= 4 ); + } + } + else + { + EMPTY_ELSE_MARKER; + } + + return remainingLength; +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectInfo, + uint8_t ** pConnectPacket, + size_t * pPacketSize ) +{ + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + uint8_t connectFlags = 0; + size_t remainingLength = 0, connectPacketSize = 0; + uint8_t * pBuffer = NULL; + + /* Calculate the "Remaining length" field and total packet size. If it exceeds + * what is allowed in the MQTT standard, return an error. */ + if( _connectPacketSize( pConnectInfo, &remainingLength, &connectPacketSize ) == false ) + { + IotLogError( "Connect packet length exceeds %lu, which is the maximum" + " size allowed by MQTT 3.1.1.", + MQTT_PACKET_CONNECT_MAX_SIZE ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Total size of the connect packet should be larger than the "Remaining length" + * field. */ + IotMqtt_Assert( connectPacketSize > remainingLength ); + + /* Allocate memory to hold the CONNECT packet. */ + pBuffer = IotMqtt_MallocMessage( connectPacketSize ); + + /* Check that sufficient memory was allocated. */ + if( pBuffer == NULL ) + { + IotLogError( "Failed to allocate memory for CONNECT packet." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Set the output parameters. The remainder of this function always succeeds. */ + *pConnectPacket = pBuffer; + *pPacketSize = connectPacketSize; + + /* The first byte in the CONNECT packet is the control packet type. */ + *pBuffer = MQTT_PACKET_TYPE_CONNECT; + pBuffer++; + + /* The remaining length of the CONNECT packet is encoded starting from the + * second byte. The remaining length does not include the length of the fixed + * header or the encoding of the remaining length. */ + pBuffer = _encodeRemainingLength( pBuffer, remainingLength ); + + /* The string "MQTT" is placed at the beginning of the CONNECT packet's variable + * header. This string is 4 bytes long. */ + pBuffer = _encodeString( pBuffer, "MQTT", 4 ); + + /* The MQTT protocol version is the second byte of the variable header. */ + *pBuffer = MQTT_VERSION_3_1_1; + pBuffer++; + + /* Set the CONNECT flags based on the given parameters. */ + if( pConnectInfo->cleanSession == true ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_CLEAN ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Username and password depend on MQTT mode. */ + if( pConnectInfo->awsIotMqttMode == true ) + { + /* Set the username flag for AWS IoT metrics. The AWS IoT MQTT server + * never uses a password. */ + #if AWS_IOT_MQTT_ENABLE_METRICS == 1 + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_USERNAME ); + #endif + } + else + { + /* Set the flags for username and password if provided. */ + if( pConnectInfo->pUserName != NULL ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_USERNAME ); + } + else + { + EMPTY_ELSE_MARKER; + } + + if( pConnectInfo->pPassword != NULL ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_PASSWORD ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + + /* Set will flag if an LWT is provided. */ + if( pConnectInfo->pWillInfo != NULL ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL ); + + /* Flags only need to be changed for will QoS 1 and 2. */ + switch( pConnectInfo->pWillInfo->qos ) + { + case IOT_MQTT_QOS_1: + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_QOS1 ); + break; + + case IOT_MQTT_QOS_2: + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_QOS2 ); + break; + + default: + break; + } + + if( pConnectInfo->pWillInfo->retain == true ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_RETAIN ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + *pBuffer = connectFlags; + pBuffer++; + + /* Write the 2 bytes of the keep alive interval into the CONNECT packet. */ + *pBuffer = UINT16_HIGH_BYTE( pConnectInfo->keepAliveSeconds ); + *( pBuffer + 1 ) = UINT16_LOW_BYTE( pConnectInfo->keepAliveSeconds ); + pBuffer += 2; + + /* Write the client identifier into the CONNECT packet. */ + pBuffer = _encodeString( pBuffer, + pConnectInfo->pClientIdentifier, + pConnectInfo->clientIdentifierLength ); + + /* Write the will topic name and message into the CONNECT packet if provided. */ + if( pConnectInfo->pWillInfo != NULL ) + { + pBuffer = _encodeString( pBuffer, + pConnectInfo->pWillInfo->pTopicName, + pConnectInfo->pWillInfo->topicNameLength ); + + pBuffer = _encodeString( pBuffer, + pConnectInfo->pWillInfo->pPayload, + ( uint16_t ) pConnectInfo->pWillInfo->payloadLength ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* If metrics are enabled, write the metrics username into the CONNECT packet. + * Otherwise, write the username and password only when not connecting to an + * AWS IoT MQTT server. */ + if( pConnectInfo->awsIotMqttMode == true ) + { + #if AWS_IOT_MQTT_ENABLE_METRICS == 1 + IotLogInfo( "Anonymous metrics (SDK language, SDK version) will be provided to AWS IoT. " + "Recompile with AWS_IOT_MQTT_ENABLE_METRICS set to 0 to disable." ); + + pBuffer = _encodeString( pBuffer, + AWS_IOT_METRICS_USERNAME, + AWS_IOT_METRICS_USERNAME_LENGTH ); + #endif + } + else + { + if( pConnectInfo->pUserName != NULL ) + { + pBuffer = _encodeString( pBuffer, + pConnectInfo->pUserName, + pConnectInfo->userNameLength ); + } + else + { + EMPTY_ELSE_MARKER; + } + + if( pConnectInfo->pPassword != NULL ) + { + pBuffer = _encodeString( pBuffer, + pConnectInfo->pPassword, + pConnectInfo->passwordLength ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + + /* Ensure that the difference between the end and beginning of the buffer + * is equal to connectPacketSize, i.e. pBuffer did not overflow. */ + IotMqtt_Assert( ( size_t ) ( pBuffer - *pConnectPacket ) == connectPacketSize ); + + /* Print out the serialized CONNECT packet for debugging purposes. */ + IotLog_PrintBuffer( "MQTT CONNECT packet:", *pConnectPacket, connectPacketSize ); + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ) +{ + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + const uint8_t * pRemainingData = pConnack->pRemainingData; + + /* If logging is enabled, declare the CONNACK response code strings. The + * fourth byte of CONNACK indexes into this array for the corresponding response. */ + #if LIBRARY_LOG_LEVEL > IOT_LOG_NONE + static const char * pConnackResponses[ 6 ] = + { + "Connection accepted.", /* 0 */ + "Connection refused: unacceptable protocol version.", /* 1 */ + "Connection refused: identifier rejected.", /* 2 */ + "Connection refused: server unavailable", /* 3 */ + "Connection refused: bad user name or password.", /* 4 */ + "Connection refused: not authorized." /* 5 */ + }; + #endif + + /* Check that the control packet type is 0x20. */ + if( pConnack->type != MQTT_PACKET_TYPE_CONNACK ) + { + IotLog( IOT_LOG_ERROR, + &_logHideAll, + "Bad control packet type 0x%02x.", + pConnack->type ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* According to MQTT 3.1.1, the second byte of CONNACK must specify a + * "Remaining length" of 2. */ + if( pConnack->remainingLength != MQTT_PACKET_CONNACK_REMAINING_LENGTH ) + { + IotLog( IOT_LOG_ERROR, + &_logHideAll, + "CONNACK does not have remaining length of %d.", + MQTT_PACKET_CONNACK_REMAINING_LENGTH ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Check the reserved bits in CONNACK. The high 7 bits of the second byte + * in CONNACK must be 0. */ + if( ( pRemainingData[ 0 ] | 0x01 ) != 0x01 ) + { + IotLog( IOT_LOG_ERROR, + &_logHideAll, + "Reserved bits in CONNACK incorrect." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Determine if the "Session Present" bit it set. This is the lowest bit of + * the second byte in CONNACK. */ + if( ( pRemainingData[ 0 ] & MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) + == MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) + { + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "CONNACK session present bit set." ); + + /* MQTT 3.1.1 specifies that the fourth byte in CONNACK must be 0 if the + * "Session Present" bit is set. */ + if( pRemainingData[ 1 ] != 0 ) + { + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "CONNACK session present bit not set." ); + } + + /* In MQTT 3.1.1, only values 0 through 5 are valid CONNACK response codes. */ + if( pRemainingData[ 1 ] > 5 ) + { + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "CONNACK response %hhu is not valid.", + pRemainingData[ 1 ] ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Print the appropriate message for the CONNACK response code if logs are + * enabled. */ + #if LIBRARY_LOG_LEVEL > IOT_LOG_NONE + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "%s", + pConnackResponses[ pRemainingData[ 1 ] ] ); + #endif + + /* A nonzero CONNACK response code means the connection was refused. */ + if( pRemainingData[ 1 ] > 0 ) + { + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_SERVER_REFUSED ); + } + else + { + EMPTY_ELSE_MARKER; + } + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t _IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishInfo, + uint8_t ** pPublishPacket, + size_t * pPacketSize, + uint16_t * pPacketIdentifier, + uint8_t ** pPacketIdentifierHigh ) +{ + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + uint8_t publishFlags = 0; + uint16_t packetIdentifier = 0; + size_t remainingLength = 0, publishPacketSize = 0; + uint8_t * pBuffer = NULL; + + /* Calculate the "Remaining length" field and total packet size. If it exceeds + * what is allowed in the MQTT standard, return an error. */ + if( _publishPacketSize( pPublishInfo, &remainingLength, &publishPacketSize ) == false ) + { + IotLogError( "Publish packet remaining length exceeds %lu, which is the " + "maximum size allowed by MQTT 3.1.1.", + MQTT_MAX_REMAINING_LENGTH ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Total size of the publish packet should be larger than the "Remaining length" + * field. */ + IotMqtt_Assert( publishPacketSize > remainingLength ); + + /* Allocate memory to hold the PUBLISH packet. */ + pBuffer = IotMqtt_MallocMessage( publishPacketSize ); + + /* Check that sufficient memory was allocated. */ + if( pBuffer == NULL ) + { + IotLogError( "Failed to allocate memory for PUBLISH packet." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Set the output parameters. The remainder of this function always succeeds. */ + *pPublishPacket = pBuffer; + *pPacketSize = publishPacketSize; + + /* The first byte of a PUBLISH packet contains the packet type and flags. */ + publishFlags = MQTT_PACKET_TYPE_PUBLISH; + + if( pPublishInfo->qos == IOT_MQTT_QOS_1 ) + { + UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ); + } + else if( pPublishInfo->qos == IOT_MQTT_QOS_2 ) + { + UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS2 ); + } + else + { + EMPTY_ELSE_MARKER; + } + + if( pPublishInfo->retain == true ) + { + UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_RETAIN ); + } + else + { + EMPTY_ELSE_MARKER; + } + + *pBuffer = publishFlags; + pBuffer++; + + /* The "Remaining length" is encoded from the second byte. */ + pBuffer = _encodeRemainingLength( pBuffer, remainingLength ); + + /* The topic name is placed after the "Remaining length". */ + pBuffer = _encodeString( pBuffer, + pPublishInfo->pTopicName, + pPublishInfo->topicNameLength ); + + /* A packet identifier is required for QoS 1 and 2 messages. */ + if( pPublishInfo->qos > IOT_MQTT_QOS_0 ) + { + /* Get the next packet identifier. It should always be nonzero. */ + packetIdentifier = _nextPacketIdentifier(); + IotMqtt_Assert( packetIdentifier != 0 ); + + /* Set the packet identifier output parameters. */ + *pPacketIdentifier = packetIdentifier; + + if( pPacketIdentifierHigh != NULL ) + { + *pPacketIdentifierHigh = pBuffer; + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Place the packet identifier into the PUBLISH packet. */ + *pBuffer = UINT16_HIGH_BYTE( packetIdentifier ); + *( pBuffer + 1 ) = UINT16_LOW_BYTE( packetIdentifier ); + pBuffer += 2; + } + else + { + EMPTY_ELSE_MARKER; + } + + /* The payload is placed after the packet identifier. */ + if( pPublishInfo->payloadLength > 0 ) + { + ( void ) memcpy( pBuffer, pPublishInfo->pPayload, pPublishInfo->payloadLength ); + pBuffer += pPublishInfo->payloadLength; + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Ensure that the difference between the end and beginning of the buffer + * is equal to publishPacketSize, i.e. pBuffer did not overflow. */ + IotMqtt_Assert( ( size_t ) ( pBuffer - *pPublishPacket ) == publishPacketSize ); + + /* Print out the serialized PUBLISH packet for debugging purposes. */ + IotLog_PrintBuffer( "MQTT PUBLISH packet:", *pPublishPacket, publishPacketSize ); + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +void _IotMqtt_PublishSetDup( uint8_t * pPublishPacket, + uint8_t * pPacketIdentifierHigh, + uint16_t * pNewPacketIdentifier ) +{ + uint16_t newPacketIdentifier = 0; + + /* For an AWS IoT MQTT server, change the packet identifier. */ + if( pPacketIdentifierHigh != NULL ) + { + /* Output parameter for new packet identifier must be provided. */ + IotMqtt_Assert( pNewPacketIdentifier != NULL ); + + /* Generate a new packet identifier. */ + newPacketIdentifier = _nextPacketIdentifier(); + + IotLogDebug( "Changing PUBLISH packet identifier %hu to %hu.", + UINT16_DECODE( pPacketIdentifierHigh ), + newPacketIdentifier ); + + /* Replace the packet identifier. */ + *pPacketIdentifierHigh = UINT16_HIGH_BYTE( newPacketIdentifier ); + *( pPacketIdentifierHigh + 1 ) = UINT16_LOW_BYTE( newPacketIdentifier ); + *pNewPacketIdentifier = newPacketIdentifier; + } + else + { + /* For a compliant MQTT 3.1.1 server, set the DUP flag. */ + UINT8_SET_BIT( *pPublishPacket, MQTT_PUBLISH_FLAG_DUP ); + + IotLogDebug( "PUBLISH DUP flag set." ); + } +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) +{ + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IotMqttPublishInfo_t * pOutput = &( pPublish->u.pIncomingPublish->u.publish.publishInfo ); + uint8_t publishFlags = 0; + const uint8_t * pVariableHeader = pPublish->pRemainingData, * pPacketIdentifierHigh = NULL; + + /* The flags are the lower 4 bits of the first byte in PUBLISH. */ + publishFlags = pPublish->type; + + /* Parse the Retain bit. */ + pOutput->retain = UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_RETAIN ); + + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Retain bit is %d.", pOutput->retain ); + + /* Check for QoS 2. */ + if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS2 ) == true ) + { + /* PUBLISH packet is invalid if both QoS 1 and QoS 2 bits are set. */ + if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ) == true ) + { + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Bad QoS: 3." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } + + pOutput->qos = IOT_MQTT_QOS_2; + } + /* Check for QoS 1. */ + else if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ) == true ) + { + pOutput->qos = IOT_MQTT_QOS_1; + } + /* If the PUBLISH isn't QoS 1 or 2, then it's QoS 0. */ + else + { + pOutput->qos = IOT_MQTT_QOS_0; + } + + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "QoS is %d.", pOutput->qos ); + + /* Parse the DUP bit. */ + if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_DUP ) == true ) + { + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "DUP is 1." ); + } + else + { + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "DUP is 0." ); + } + + /* Sanity checks for "Remaining length". */ + if( pOutput->qos == IOT_MQTT_QOS_0 ) + { + /* A QoS 0 PUBLISH must have a remaining length of at least 3 to accommodate + * topic name length (2 bytes) and topic name (at least 1 byte). */ + if( pPublish->remainingLength < 3 ) + { + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "QoS 0 PUBLISH cannot have a remaining length less than 3." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + /* A QoS 1 or 2 PUBLISH must have a remaining length of at least 5 to + * accommodate a packet identifier as well as the topic name length and + * topic name. */ + if( pPublish->remainingLength < 5 ) + { + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "QoS 1 or 2 PUBLISH cannot have a remaining length less than 5." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + + /* Extract the topic name starting from the first byte of the variable header. + * The topic name string starts at byte 3 in the variable header. */ + pOutput->topicNameLength = UINT16_DECODE( pVariableHeader ); + + /* Sanity checks for topic name length and "Remaining length". */ + if( pOutput->qos == IOT_MQTT_QOS_0 ) + { + /* Check that the "Remaining length" is at least as large as the variable + * header. */ + if( pPublish->remainingLength < pOutput->topicNameLength + sizeof( uint16_t ) ) + { + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Remaining length cannot be less than variable header length." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + /* Check that the "Remaining length" is at least as large as the variable + * header. */ + if( pPublish->remainingLength < pOutput->topicNameLength + 2 * sizeof( uint16_t ) ) + { + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Remaining length cannot be less than variable header length." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + + /* Parse the topic. */ + pOutput->pTopicName = ( const char * ) ( pVariableHeader + sizeof( uint16_t ) ); + + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Topic name length %hu: %.*s", + pOutput->topicNameLength, + pOutput->topicNameLength, + pOutput->pTopicName ); + + /* Extract the packet identifier for QoS 1 or 2 PUBLISH packets. Packet + * identifier starts immediately after the topic name. */ + pPacketIdentifierHigh = ( const uint8_t * ) ( pOutput->pTopicName + pOutput->topicNameLength ); + + if( pOutput->qos > IOT_MQTT_QOS_0 ) + { + pPublish->packetIdentifier = UINT16_DECODE( pPacketIdentifierHigh ); + + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Packet identifier %hu.", pPublish->packetIdentifier ); + + /* Packet identifier cannot be 0. */ + if( pPublish->packetIdentifier == 0 ) + { + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Calculate the length of the payload. QoS 1 or 2 PUBLISH packets contain + * a packet identifer, but QoS 0 PUBLISH packets do not. */ + if( pOutput->qos == IOT_MQTT_QOS_0 ) + { + pOutput->payloadLength = ( uint16_t ) ( pPublish->remainingLength - pOutput->topicNameLength - sizeof( uint16_t ) ); + pOutput->pPayload = pPacketIdentifierHigh; + } + else + { + pOutput->payloadLength = ( uint16_t ) ( pPublish->remainingLength - pOutput->topicNameLength - 2 * sizeof( uint16_t ) ); + pOutput->pPayload = pPacketIdentifierHigh + sizeof( uint16_t ); + } + + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Payload length %hu.", pOutput->payloadLength ); + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t _IotMqtt_SerializePuback( uint16_t packetIdentifier, + uint8_t ** pPubackPacket, + size_t * pPacketSize ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + + /* Allocate memory for PUBACK. */ + uint8_t * pBuffer = IotMqtt_MallocMessage( MQTT_PACKET_PUBACK_SIZE ); + + if( pBuffer == NULL ) + { + IotLogError( "Failed to allocate memory for PUBACK packet" ); + + status = IOT_MQTT_NO_MEMORY; + } + else + { + /* Set the output parameters. The remainder of this function always succeeds. */ + *pPubackPacket = pBuffer; + *pPacketSize = MQTT_PACKET_PUBACK_SIZE; + + /* Set the 4 bytes in PUBACK. */ + pBuffer[ 0 ] = MQTT_PACKET_TYPE_PUBACK; + pBuffer[ 1 ] = MQTT_PACKET_PUBACK_REMAINING_LENGTH; + pBuffer[ 2 ] = UINT16_HIGH_BYTE( packetIdentifier ); + pBuffer[ 3 ] = UINT16_LOW_BYTE( packetIdentifier ); + + /* Print out the serialized PUBACK packet for debugging purposes. */ + IotLog_PrintBuffer( "MQTT PUBACK packet:", *pPubackPacket, MQTT_PACKET_PUBACK_SIZE ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t _IotMqtt_DeserializePuback( _mqttPacket_t * pPuback ) +{ + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + + /* Check the "Remaining length" of the received PUBACK. */ + if( pPuback->remainingLength != MQTT_PACKET_PUBACK_REMAINING_LENGTH ) + { + IotLog( IOT_LOG_ERROR, + &_logHideAll, + "PUBACK does not have remaining length of %d.", + MQTT_PACKET_PUBACK_REMAINING_LENGTH ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Extract the packet identifier (third and fourth bytes) from PUBACK. */ + pPuback->packetIdentifier = UINT16_DECODE( pPuback->pRemainingData ); + + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Packet identifier %hu.", pPuback->packetIdentifier ); + + /* Packet identifier cannot be 0. */ + if( pPuback->packetIdentifier == 0 ) + { + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Check that the control packet type is 0x40 (this must be done after the + * packet identifier is parsed). */ + if( pPuback->type != MQTT_PACKET_TYPE_PUBACK ) + { + IotLog( IOT_LOG_ERROR, + &_logHideAll, + "Bad control packet type 0x%02x.", + pPuback->type ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t _IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint8_t ** pSubscribePacket, + size_t * pPacketSize, + uint16_t * pPacketIdentifier ) +{ + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + size_t i = 0, subscribePacketSize = 0, remainingLength = 0; + uint16_t packetIdentifier = 0; + uint8_t * pBuffer = NULL; + + /* Calculate the "Remaining length" field and total packet size. If it exceeds + * what is allowed in the MQTT standard, return an error. */ + if( _subscriptionPacketSize( IOT_MQTT_SUBSCRIBE, + pSubscriptionList, + subscriptionCount, + &remainingLength, + &subscribePacketSize ) == false ) + { + IotLogError( "Subscribe packet remaining length exceeds %lu, which is the " + "maximum size allowed by MQTT 3.1.1.", + MQTT_MAX_REMAINING_LENGTH ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Total size of the subscribe packet should be larger than the "Remaining length" + * field. */ + IotMqtt_Assert( subscribePacketSize > remainingLength ); + + /* Allocate memory to hold the SUBSCRIBE packet. */ + pBuffer = IotMqtt_MallocMessage( subscribePacketSize ); + + /* Check that sufficient memory was allocated. */ + if( pBuffer == NULL ) + { + IotLogError( "Failed to allocate memory for SUBSCRIBE packet." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Set the output parameters. The remainder of this function always succeeds. */ + *pSubscribePacket = pBuffer; + *pPacketSize = subscribePacketSize; + + /* The first byte in SUBSCRIBE is the packet type. */ + *pBuffer = MQTT_PACKET_TYPE_SUBSCRIBE; + pBuffer++; + + /* Encode the "Remaining length" starting from the second byte. */ + pBuffer = _encodeRemainingLength( pBuffer, remainingLength ); + + /* Get the next packet identifier. It should always be nonzero. */ + packetIdentifier = _nextPacketIdentifier(); + *pPacketIdentifier = packetIdentifier; + IotMqtt_Assert( packetIdentifier != 0 ); + + /* Place the packet identifier into the SUBSCRIBE packet. */ + *pBuffer = UINT16_HIGH_BYTE( packetIdentifier ); + *( pBuffer + 1 ) = UINT16_LOW_BYTE( packetIdentifier ); + pBuffer += 2; + + /* Serialize each subscription topic filter and QoS. */ + for( i = 0; i < subscriptionCount; i++ ) + { + pBuffer = _encodeString( pBuffer, + pSubscriptionList[ i ].pTopicFilter, + pSubscriptionList[ i ].topicFilterLength ); + + /* Place the QoS in the SUBSCRIBE packet. */ + *pBuffer = ( uint8_t ) ( pSubscriptionList[ i ].qos ); + pBuffer++; + } + + /* Ensure that the difference between the end and beginning of the buffer + * is equal to subscribePacketSize, i.e. pBuffer did not overflow. */ + IotMqtt_Assert( ( size_t ) ( pBuffer - *pSubscribePacket ) == subscribePacketSize ); + + /* Print out the serialized SUBSCRIBE packet for debugging purposes. */ + IotLog_PrintBuffer( "MQTT SUBSCRIBE packet:", *pSubscribePacket, subscribePacketSize ); + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t _IotMqtt_DeserializeSuback( _mqttPacket_t * pSuback ) +{ + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + size_t i = 0, remainingLength = pSuback->remainingLength; + uint8_t subscriptionStatus = 0; + const uint8_t * pVariableHeader = pSuback->pRemainingData; + + /* A SUBACK must have a remaining length of at least 3 to accommodate the + * packet identifer and at least 1 return code. */ + if( remainingLength < 3 ) + { + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "SUBACK cannot have a remaining length less than 3." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Extract the packet identifier (first 2 bytes of variable header) from SUBACK. */ + pSuback->packetIdentifier = UINT16_DECODE( pVariableHeader ); + + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Packet identifier %hu.", pSuback->packetIdentifier ); + + /* Check that the control packet type is 0x90 (this must be done after the + * packet identifier is parsed). */ + if( pSuback->type != MQTT_PACKET_TYPE_SUBACK ) + { + IotLog( IOT_LOG_ERROR, + &_logHideAll, + "Bad control packet type 0x%02x.", + pSuback->type ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Iterate through each status byte in the SUBACK packet. */ + for( i = 0; i < remainingLength - sizeof( uint16_t ); i++ ) + { + /* Read a single status byte in SUBACK. */ + subscriptionStatus = *( pVariableHeader + sizeof( uint16_t ) + i ); + + /* MQTT 3.1.1 defines the following values as status codes. */ + switch( subscriptionStatus ) + { + case 0x00: + case 0x01: + case 0x02: + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Topic filter %lu accepted, max QoS %hhu.", + ( unsigned long ) i, subscriptionStatus ); + break; + + case 0x80: + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Topic filter %lu refused.", ( unsigned long ) i ); + + /* Remove a rejected subscription from the subscription manager. */ + _IotMqtt_RemoveSubscriptionByPacket( pSuback->u.pMqttConnection, + pSuback->packetIdentifier, + ( int32_t ) i ); + + status = IOT_MQTT_SERVER_REFUSED; + + break; + + default: + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Bad SUBSCRIBE status %hhu.", subscriptionStatus ); + + status = IOT_MQTT_BAD_RESPONSE; + + break; + } + + /* Stop parsing the subscription statuses if a bad response was received. */ + if( status == IOT_MQTT_BAD_RESPONSE ) + { + break; + } + else + { + EMPTY_ELSE_MARKER; + } + } + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t _IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint8_t ** pUnsubscribePacket, + size_t * pPacketSize, + uint16_t * pPacketIdentifier ) +{ + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + size_t i = 0, unsubscribePacketSize = 0, remainingLength = 0; + uint16_t packetIdentifier = 0; + uint8_t * pBuffer = NULL; + + /* Calculate the "Remaining length" field and total packet size. If it exceeds + * what is allowed in the MQTT standard, return an error. */ + if( _subscriptionPacketSize( IOT_MQTT_UNSUBSCRIBE, + pSubscriptionList, + subscriptionCount, + &remainingLength, + &unsubscribePacketSize ) == false ) + { + IotLogError( "Unsubscribe packet remaining length exceeds %lu, which is the " + "maximum size allowed by MQTT 3.1.1.", + MQTT_MAX_REMAINING_LENGTH ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Total size of the unsubscribe packet should be larger than the "Remaining length" + * field. */ + IotMqtt_Assert( unsubscribePacketSize > remainingLength ); + + /* Allocate memory to hold the UNSUBSCRIBE packet. */ + pBuffer = IotMqtt_MallocMessage( unsubscribePacketSize ); + + /* Check that sufficient memory was allocated. */ + if( pBuffer == NULL ) + { + IotLogError( "Failed to allocate memory for UNSUBSCRIBE packet." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Set the output parameters. The remainder of this function always succeeds. */ + *pUnsubscribePacket = pBuffer; + *pPacketSize = unsubscribePacketSize; + + /* The first byte in UNSUBSCRIBE is the packet type. */ + *pBuffer = MQTT_PACKET_TYPE_UNSUBSCRIBE; + pBuffer++; + + /* Encode the "Remaining length" starting from the second byte. */ + pBuffer = _encodeRemainingLength( pBuffer, remainingLength ); + + /* Get the next packet identifier. It should always be nonzero. */ + packetIdentifier = _nextPacketIdentifier(); + *pPacketIdentifier = packetIdentifier; + IotMqtt_Assert( packetIdentifier != 0 ); + + /* Place the packet identifier into the UNSUBSCRIBE packet. */ + *pBuffer = UINT16_HIGH_BYTE( packetIdentifier ); + *( pBuffer + 1 ) = UINT16_LOW_BYTE( packetIdentifier ); + pBuffer += 2; + + /* Serialize each subscription topic filter. */ + for( i = 0; i < subscriptionCount; i++ ) + { + pBuffer = _encodeString( pBuffer, + pSubscriptionList[ i ].pTopicFilter, + pSubscriptionList[ i ].topicFilterLength ); + } + + /* Ensure that the difference between the end and beginning of the buffer + * is equal to unsubscribePacketSize, i.e. pBuffer did not overflow. */ + IotMqtt_Assert( ( size_t ) ( pBuffer - *pUnsubscribePacket ) == unsubscribePacketSize ); + + /* Print out the serialized UNSUBSCRIBE packet for debugging purposes. */ + IotLog_PrintBuffer( "MQTT UNSUBSCRIBE packet:", *pUnsubscribePacket, unsubscribePacketSize ); + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t _IotMqtt_DeserializeUnsuback( _mqttPacket_t * pUnsuback ) +{ + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + + /* Check the "Remaining length" (second byte) of the received UNSUBACK. */ + if( pUnsuback->remainingLength != MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ) + { + IotLog( IOT_LOG_ERROR, + &_logHideAll, + "UNSUBACK does not have remaining length of %d.", + MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Extract the packet identifier (third and fourth bytes) from UNSUBACK. */ + pUnsuback->packetIdentifier = UINT16_DECODE( pUnsuback->pRemainingData ); + + /* Packet identifier cannot be 0. */ + if( pUnsuback->packetIdentifier == 0 ) + { + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } + + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Packet identifier %hu.", pUnsuback->packetIdentifier ); + + /* Check that the control packet type is 0xb0 (this must be done after the + * packet identifier is parsed). */ + if( pUnsuback->type != MQTT_PACKET_TYPE_UNSUBACK ) + { + IotLog( IOT_LOG_ERROR, + &_logHideAll, + "Bad control packet type 0x%02x.", + pUnsuback->type ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t _IotMqtt_SerializePingreq( uint8_t ** pPingreqPacket, + size_t * pPacketSize ) +{ + /* PINGREQ packets are always the same. */ + static const uint8_t pPingreq[ MQTT_PACKET_PINGREQ_SIZE ] = + { + MQTT_PACKET_TYPE_PINGREQ, + 0x00 + }; + + /* Set the output parameters. */ + *pPingreqPacket = ( uint8_t * ) pPingreq; + *pPacketSize = MQTT_PACKET_PINGREQ_SIZE; + + /* Print out the PINGREQ packet for debugging purposes. */ + IotLog_PrintBuffer( "MQTT PINGREQ packet:", pPingreq, MQTT_PACKET_PINGREQ_SIZE ); + + return IOT_MQTT_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t _IotMqtt_DeserializePingresp( _mqttPacket_t * pPingresp ) +{ + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + + /* Check that the control packet type is 0xd0. */ + if( pPingresp->type != MQTT_PACKET_TYPE_PINGRESP ) + { + IotLog( IOT_LOG_ERROR, + &_logHideAll, + "Bad control packet type 0x%02x.", + pPingresp->type ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Check the "Remaining length" (second byte) of the received PINGRESP. */ + if( pPingresp->remainingLength != MQTT_PACKET_PINGRESP_REMAINING_LENGTH ) + { + IotLog( IOT_LOG_ERROR, + &_logHideAll, + "PINGRESP does not have remaining length of %d.", + MQTT_PACKET_PINGRESP_REMAINING_LENGTH ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t _IotMqtt_SerializeDisconnect( uint8_t ** pDisconnectPacket, + size_t * pPacketSize ) +{ + /* DISCONNECT packets are always the same. */ + static const uint8_t pDisconnect[ MQTT_PACKET_DISCONNECT_SIZE ] = + { + MQTT_PACKET_TYPE_DISCONNECT, + 0x00 + }; + + /* Set the output parameters. */ + *pDisconnectPacket = ( uint8_t * ) pDisconnect; + *pPacketSize = MQTT_PACKET_DISCONNECT_SIZE; + + /* Print out the DISCONNECT packet for debugging purposes. */ + IotLog_PrintBuffer( "MQTT DISCONNECT packet:", pDisconnect, MQTT_PACKET_DISCONNECT_SIZE ); + + return IOT_MQTT_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +void _IotMqtt_FreePacket( uint8_t * pPacket ) +{ + uint8_t packetType = *pPacket; + + /* Don't call free on DISCONNECT and PINGREQ; those are allocated from static + * memory. */ + if( packetType != MQTT_PACKET_TYPE_DISCONNECT ) + { + if( packetType != MQTT_PACKET_TYPE_PINGREQ ) + { + IotMqtt_FreeMessage( pPacket ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } +} + +/*-----------------------------------------------------------*/ diff --git a/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/src/iot_mqtt_static_memory.c b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/src/iot_mqtt_static_memory.c new file mode 100644 index 000000000..000fcbadc --- /dev/null +++ b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/src/iot_mqtt_static_memory.c @@ -0,0 +1,207 @@ +/* + * Amazon FreeRTOS MQTT V2.0.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +/** + * @file iot_mqtt_static_memory.c + * @brief Implementation of MQTT static memory functions. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* This file should only be compiled if dynamic memory allocation is forbidden. */ +#if IOT_STATIC_MEMORY_ONLY == 1 + +/* Standard includes. */ +#include +#include +#include + +/* Static memory include. */ +#include "private/iot_static_memory.h" + +/* MQTT internal include. */ +#include "private/iot_mqtt_internal.h" + +/*-----------------------------------------------------------*/ + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Provide default values for undefined configuration constants. + */ +#ifndef IOT_MQTT_CONNECTIONS + #define IOT_MQTT_CONNECTIONS ( 1 ) +#endif +#ifndef IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS + #define IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ( 10 ) +#endif +#ifndef IOT_MQTT_SUBSCRIPTIONS + #define IOT_MQTT_SUBSCRIPTIONS ( 8 ) +#endif +/** @endcond */ + +/* Validate static memory configuration settings. */ +#if IOT_MQTT_CONNECTIONS <= 0 + #error "IOT_MQTT_CONNECTIONS cannot be 0 or negative." +#endif +#if IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS <= 0 + #error "IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS cannot be 0 or negative." +#endif +#if IOT_MQTT_SUBSCRIPTIONS <= 0 + #error "IOT_MQTT_SUBSCRIPTIONS cannot be 0 or negative." +#endif + +/** + * @brief The size of a static memory MQTT subscription. + * + * Since the pTopic member of #_mqttSubscription_t is variable-length, the constant + * #AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH is used for the length of + * #_mqttSubscription_t.pTopicFilter. + */ +#define MQTT_SUBSCRIPTION_SIZE ( sizeof( _mqttSubscription_t ) + AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ) + +/*-----------------------------------------------------------*/ + +/* + * Static memory buffers and flags, allocated and zeroed at compile-time. + */ +static bool _pInUseMqttConnections[ IOT_MQTT_CONNECTIONS ] = { 0 }; /**< @brief MQTT connection in-use flags. */ +static _mqttConnection_t _pMqttConnections[ IOT_MQTT_CONNECTIONS ] = { { 0 } }; /**< @brief MQTT connections. */ + +static bool _pInUseMqttOperations[ IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ] = { 0 }; /**< @brief MQTT operation in-use flags. */ +static _mqttOperation_t _pMqttOperations[ IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ] = { { .link = { 0 } } }; /**< @brief MQTT operations. */ + +static bool _pInUseMqttSubscriptions[ IOT_MQTT_SUBSCRIPTIONS ] = { 0 }; /**< @brief MQTT subscription in-use flags. */ +static char _pMqttSubscriptions[ IOT_MQTT_SUBSCRIPTIONS ][ MQTT_SUBSCRIPTION_SIZE ] = { { 0 } }; /**< @brief MQTT subscriptions. */ + +/*-----------------------------------------------------------*/ + +void * IotMqtt_MallocConnection( size_t size ) +{ + int32_t freeIndex = -1; + void * pNewConnection = NULL; + + /* Check size argument. */ + if( size == sizeof( _mqttConnection_t ) ) + { + /* Find a free MQTT connection. */ + freeIndex = IotStaticMemory_FindFree( _pInUseMqttConnections, + IOT_MQTT_CONNECTIONS ); + + if( freeIndex != -1 ) + { + pNewConnection = &( _pMqttConnections[ freeIndex ] ); + } + } + + return pNewConnection; +} + +/*-----------------------------------------------------------*/ + +void IotMqtt_FreeConnection( void * ptr ) +{ + /* Return the in-use MQTT connection. */ + IotStaticMemory_ReturnInUse( ptr, + _pMqttConnections, + _pInUseMqttConnections, + IOT_MQTT_CONNECTIONS, + sizeof( _mqttConnection_t ) ); +} + +/*-----------------------------------------------------------*/ + +void * IotMqtt_MallocOperation( size_t size ) +{ + int32_t freeIndex = -1; + void * pNewOperation = NULL; + + /* Check size argument. */ + if( size == sizeof( _mqttOperation_t ) ) + { + /* Find a free MQTT operation. */ + freeIndex = IotStaticMemory_FindFree( _pInUseMqttOperations, + IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ); + + if( freeIndex != -1 ) + { + pNewOperation = &( _pMqttOperations[ freeIndex ] ); + } + } + + return pNewOperation; +} + +/*-----------------------------------------------------------*/ + +void IotMqtt_FreeOperation( void * ptr ) +{ + /* Return the in-use MQTT operation. */ + IotStaticMemory_ReturnInUse( ptr, + _pMqttOperations, + _pInUseMqttOperations, + IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS, + sizeof( _mqttOperation_t ) ); +} + +/*-----------------------------------------------------------*/ + +void * IotMqtt_MallocSubscription( size_t size ) +{ + int32_t freeIndex = -1; + void * pNewSubscription = NULL; + + if( size <= MQTT_SUBSCRIPTION_SIZE ) + { + /* Get the index of a free MQTT subscription. */ + freeIndex = IotStaticMemory_FindFree( _pInUseMqttSubscriptions, + IOT_MQTT_SUBSCRIPTIONS ); + + if( freeIndex != -1 ) + { + pNewSubscription = &( _pMqttSubscriptions[ freeIndex ][ 0 ] ); + } + } + + return pNewSubscription; +} + +/*-----------------------------------------------------------*/ + +void IotMqtt_FreeSubscription( void * ptr ) +{ + /* Return the in-use MQTT subscription. */ + IotStaticMemory_ReturnInUse( ptr, + _pMqttSubscriptions, + _pInUseMqttSubscriptions, + IOT_MQTT_SUBSCRIPTIONS, + MQTT_SUBSCRIPTION_SIZE ); +} + +/*-----------------------------------------------------------*/ + +#endif diff --git a/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/src/iot_mqtt_subscription.c b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/src/iot_mqtt_subscription.c new file mode 100644 index 000000000..99664de05 --- /dev/null +++ b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/src/iot_mqtt_subscription.c @@ -0,0 +1,648 @@ +/* + * Amazon FreeRTOS MQTT V2.0.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +/** + * @file iot_mqtt_subscription.c + * @brief Implements functions that manage subscriptions for an MQTT connection. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Standard includes. */ +#include +#include + +/* Error handling include. */ +#include "private/iot_error.h" + +/* MQTT internal include. */ +#include "private/iot_mqtt_internal.h" + +/* Platform layer includes. */ +#include "platform/iot_threads.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief First parameter to #_topicMatch. + */ +typedef struct _topicMatchParams +{ + const char * pTopicName; /**< @brief The topic name to parse. */ + uint16_t topicNameLength; /**< @brief Length of #_topicMatchParams_t.pTopicName. */ + bool exactMatchOnly; /**< @brief Whether to allow wildcards or require exact matches. */ +} _topicMatchParams_t; + +/** + * @brief First parameter to #_packetMatch. + */ +typedef struct _packetMatchParams +{ + uint16_t packetIdentifier; /**< Packet identifier to match. */ + int32_t order; /**< Order to match. Set to `-1` to ignore. */ +} _packetMatchParams_t; + +/*-----------------------------------------------------------*/ + +/** + * @brief Matches a topic name (from a publish) with a topic filter (from a + * subscription). + * + * @param[in] pSubscriptionLink Pointer to the link member of an #_mqttSubscription_t. + * @param[in] pMatch Pointer to a #_topicMatchParams_t. + * + * @return `true` if the arguments match the subscription topic filter; `false` + * otherwise. + */ +static bool _topicMatch( const IotLink_t * pSubscriptionLink, + void * pMatch ); + +/** + * @brief Matches a packet identifier and order. + * + * @param[in] pSubscriptionLink Pointer to the link member of an #_mqttSubscription_t. + * @param[in] pMatch Pointer to a #_packetMatchParams_t. + * + * @return `true` if the arguments match the subscription's packet info; `false` + * otherwise. + */ +static bool _packetMatch( const IotLink_t * pSubscriptionLink, + void * pMatch ); + +/*-----------------------------------------------------------*/ + +static bool _topicMatch( const IotLink_t * pSubscriptionLink, + void * pMatch ) +{ + IOT_FUNCTION_ENTRY( bool, false ); + uint16_t nameIndex = 0, filterIndex = 0; + + /* Because this function is called from a container function, the given link + * must never be NULL. */ + IotMqtt_Assert( pSubscriptionLink != NULL ); + + _mqttSubscription_t * pSubscription = IotLink_Container( _mqttSubscription_t, + pSubscriptionLink, + link ); + _topicMatchParams_t * pParam = ( _topicMatchParams_t * ) pMatch; + + /* Extract the relevant strings and lengths from parameters. */ + const char * pTopicName = pParam->pTopicName; + const char * pTopicFilter = pSubscription->pTopicFilter; + const uint16_t topicNameLength = pParam->topicNameLength; + const uint16_t topicFilterLength = pSubscription->topicFilterLength; + + /* Check for an exact match. */ + if( topicNameLength == topicFilterLength ) + { + status = ( strncmp( pTopicName, pTopicFilter, topicNameLength ) == 0 ); + + IOT_GOTO_CLEANUP(); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* If the topic lengths are different but an exact match is required, return + * false. */ + if( pParam->exactMatchOnly == true ) + { + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + EMPTY_ELSE_MARKER; + } + + while( ( nameIndex < topicNameLength ) && ( filterIndex < topicFilterLength ) ) + { + /* Check if the character in the topic name matches the corresponding + * character in the topic filter string. */ + if( pTopicName[ nameIndex ] == pTopicFilter[ filterIndex ] ) + { + /* Handle special corner cases as documented by the MQTT protocol spec. */ + + /* Filter "sport/#" also matches "sport" since # includes the parent level. */ + if( nameIndex == topicNameLength - 1 ) + { + if( filterIndex == topicFilterLength - 3 ) + { + if( pTopicFilter[ filterIndex + 1 ] == '/' ) + { + if( pTopicFilter[ filterIndex + 2 ] == '#' ) + { + IOT_SET_AND_GOTO_CLEANUP( true ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Filter "sport/+" also matches the "sport/" but not "sport". */ + if( nameIndex == topicNameLength - 1 ) + { + if( filterIndex == topicFilterLength - 2 ) + { + if( pTopicFilter[ filterIndex + 1 ] == '+' ) + { + IOT_SET_AND_GOTO_CLEANUP( true ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + /* Check for wildcards. */ + if( pTopicFilter[ filterIndex ] == '+' ) + { + /* Move topic name index to the end of the current level. + * This is identified by '/'. */ + while( nameIndex < topicNameLength && pTopicName[ nameIndex ] != '/' ) + { + nameIndex++; + } + + /* Increment filter index to skip '/'. */ + filterIndex++; + continue; + } + else if( pTopicFilter[ filterIndex ] == '#' ) + { + /* Subsequent characters don't need to be checked if the for the + * multi-level wildcard. */ + IOT_SET_AND_GOTO_CLEANUP( true ); + } + else + { + /* Any character mismatch other than '+' or '#' means the topic + * name does not match the topic filter. */ + IOT_SET_AND_GOTO_CLEANUP( false ); + } + } + + /* Increment indexes. */ + nameIndex++; + filterIndex++; + } + + /* If the end of both strings has been reached, they match. */ + if( ( nameIndex == topicNameLength ) && ( filterIndex == topicFilterLength ) ) + { + IOT_SET_AND_GOTO_CLEANUP( true ); + } + else + { + EMPTY_ELSE_MARKER; + } + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +static bool _packetMatch( const IotLink_t * pSubscriptionLink, + void * pMatch ) +{ + bool match = false; + + /* Because this function is called from a container function, the given link + * must never be NULL. */ + IotMqtt_Assert( pSubscriptionLink != NULL ); + + _mqttSubscription_t * pSubscription = IotLink_Container( _mqttSubscription_t, + pSubscriptionLink, + link ); + _packetMatchParams_t * pParam = ( _packetMatchParams_t * ) pMatch; + + /* Compare packet identifiers. */ + if( pParam->packetIdentifier == pSubscription->packetInfo.identifier ) + { + /* Compare orders if order is not -1. */ + if( pParam->order == -1 ) + { + match = true; + } + else + { + match = ( ( size_t ) pParam->order ) == pSubscription->packetInfo.order; + } + } + + /* If this subscription should be removed, check the reference count. */ + if( match == true ) + { + /* Reference count must not be negative. */ + IotMqtt_Assert( pSubscription->references >= 0 ); + + /* If the reference count is positive, this subscription cannot be + * removed yet because there are subscription callbacks using it. */ + if( pSubscription->references > 0 ) + { + match = false; + + /* Set the unsubscribed flag. The last active subscription callback + * will remove and clean up this subscription. */ + pSubscription->unsubscribed = true; + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + return match; +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t _IotMqtt_AddSubscriptions( _mqttConnection_t * pMqttConnection, + uint16_t subscribePacketIdentifier, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + size_t i = 0; + _mqttSubscription_t * pNewSubscription = NULL; + IotLink_t * pSubscriptionLink = NULL; + _topicMatchParams_t topicMatchParams = { .exactMatchOnly = true }; + + IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); + + for( i = 0; i < subscriptionCount; i++ ) + { + /* Check if this topic filter is already registered. */ + topicMatchParams.pTopicName = pSubscriptionList[ i ].pTopicFilter; + topicMatchParams.topicNameLength = pSubscriptionList[ i ].topicFilterLength; + pSubscriptionLink = IotListDouble_FindFirstMatch( &( pMqttConnection->subscriptionList ), + NULL, + _topicMatch, + &topicMatchParams ); + + if( pSubscriptionLink != NULL ) + { + pNewSubscription = IotLink_Container( _mqttSubscription_t, pSubscriptionLink, link ); + + /* The lengths of exactly matching topic filters must match. */ + IotMqtt_Assert( pNewSubscription->topicFilterLength == pSubscriptionList[ i ].topicFilterLength ); + + /* Replace the callback and packet info with the new parameters. */ + pNewSubscription->callback = pSubscriptionList[ i ].callback; + pNewSubscription->packetInfo.identifier = subscribePacketIdentifier; + pNewSubscription->packetInfo.order = i; + } + else + { + /* Allocate memory for a new subscription. */ + pNewSubscription = IotMqtt_MallocSubscription( sizeof( _mqttSubscription_t ) + + pSubscriptionList[ i ].topicFilterLength ); + + if( pNewSubscription == NULL ) + { + status = IOT_MQTT_NO_MEMORY; + break; + } + else + { + /* Clear the new subscription. */ + ( void ) memset( pNewSubscription, + 0x00, + sizeof( _mqttSubscription_t ) + pSubscriptionList[ i ].topicFilterLength ); + + /* Set the members of the new subscription and add it to the list. */ + pNewSubscription->packetInfo.identifier = subscribePacketIdentifier; + pNewSubscription->packetInfo.order = i; + pNewSubscription->callback = pSubscriptionList[ i ].callback; + pNewSubscription->topicFilterLength = pSubscriptionList[ i ].topicFilterLength; + ( void ) memcpy( pNewSubscription->pTopicFilter, + pSubscriptionList[ i ].pTopicFilter, + ( size_t ) ( pSubscriptionList[ i ].topicFilterLength ) ); + + IotListDouble_InsertHead( &( pMqttConnection->subscriptionList ), + &( pNewSubscription->link ) ); + } + } + } + + IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); + + /* If memory allocation failed, remove all previously added subscriptions. */ + if( status != IOT_MQTT_SUCCESS ) + { + _IotMqtt_RemoveSubscriptionByTopicFilter( pMqttConnection, + pSubscriptionList, + i ); + } + else + { + EMPTY_ELSE_MARKER; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +void _IotMqtt_InvokeSubscriptionCallback( _mqttConnection_t * pMqttConnection, + IotMqttCallbackParam_t * pCallbackParam ) +{ + _mqttSubscription_t * pSubscription = NULL; + IotLink_t * pCurrentLink = NULL, * pNextLink = NULL; + void * pCallbackContext = NULL; + + void ( * callbackFunction )( void *, + IotMqttCallbackParam_t * ) = NULL; + _topicMatchParams_t topicMatchParams = + { + .pTopicName = pCallbackParam->u.message.info.pTopicName, + .topicNameLength = pCallbackParam->u.message.info.topicNameLength, + .exactMatchOnly = false + }; + + /* Prevent any other thread from modifying the subscription list while this + * function is searching. */ + IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); + + /* Search the subscription list for all matching subscriptions starting at + * the list head. */ + while( true ) + { + pCurrentLink = IotListDouble_FindFirstMatch( &( pMqttConnection->subscriptionList ), + pCurrentLink, + _topicMatch, + &topicMatchParams ); + + /* No subscription found. Exit loop. */ + if( pCurrentLink == NULL ) + { + break; + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Subscription found. Calculate pointer to subscription object. */ + pSubscription = IotLink_Container( _mqttSubscription_t, pCurrentLink, link ); + + /* Subscription validation should not have allowed a NULL callback function. */ + IotMqtt_Assert( pSubscription->callback.function != NULL ); + + /* Increment the subscription's reference count. */ + ( pSubscription->references )++; + + /* Copy the necessary members of the subscription before releasing the + * subscription list mutex. */ + pCallbackContext = pSubscription->callback.pCallbackContext; + callbackFunction = pSubscription->callback.function; + + /* Unlock the subscription list mutex. */ + IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); + + /* Set the members of the callback parameter. */ + pCallbackParam->mqttConnection = pMqttConnection; + pCallbackParam->u.message.pTopicFilter = pSubscription->pTopicFilter; + pCallbackParam->u.message.topicFilterLength = pSubscription->topicFilterLength; + + /* Invoke the subscription callback. */ + callbackFunction( pCallbackContext, pCallbackParam ); + + /* Lock the subscription list mutex to decrement the reference count. */ + IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); + + /* Decrement the reference count. It must still be positive. */ + ( pSubscription->references )--; + IotMqtt_Assert( pSubscription->references >= 0 ); + + /* Save the pointer to the next link in case this subscription is freed. */ + pNextLink = pCurrentLink->pNext; + + /* Remove this subscription if it has no references and the unsubscribed + * flag is set. */ + if( pSubscription->unsubscribed == true ) + { + /* An unsubscribed subscription should have been removed from the list. */ + IotMqtt_Assert( IotLink_IsLinked( &( pSubscription->link ) ) == false ); + + /* Free subscriptions with no references. */ + if( pSubscription->references == 0 ) + { + IotMqtt_FreeSubscription( pSubscription ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Move current link pointer. */ + pCurrentLink = pNextLink; + } + + IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); + + _IotMqtt_DecrementConnectionReferences( pMqttConnection ); +} + +/*-----------------------------------------------------------*/ + +void _IotMqtt_RemoveSubscriptionByPacket( _mqttConnection_t * pMqttConnection, + uint16_t packetIdentifier, + int32_t order ) +{ + const _packetMatchParams_t packetMatchParams = + { + .packetIdentifier = packetIdentifier, + .order = order + }; + + IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); + IotListDouble_RemoveAllMatches( &( pMqttConnection->subscriptionList ), + _packetMatch, + ( void * ) ( &packetMatchParams ), + IotMqtt_FreeSubscription, + offsetof( _mqttSubscription_t, link ) ); + IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); +} + +/*-----------------------------------------------------------*/ + +void _IotMqtt_RemoveSubscriptionByTopicFilter( _mqttConnection_t * pMqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount ) +{ + size_t i = 0; + _mqttSubscription_t * pSubscription = NULL; + IotLink_t * pSubscriptionLink = NULL; + _topicMatchParams_t topicMatchParams = { 0 }; + + /* Prevent any other thread from modifying the subscription list while this + * function is running. */ + IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); + + /* Find and remove each topic filter from the list. */ + for( i = 0; i < subscriptionCount; i++ ) + { + topicMatchParams.pTopicName = pSubscriptionList[ i ].pTopicFilter; + topicMatchParams.topicNameLength = pSubscriptionList[ i ].topicFilterLength; + topicMatchParams.exactMatchOnly = true; + + pSubscriptionLink = IotListDouble_FindFirstMatch( &( pMqttConnection->subscriptionList ), + NULL, + _topicMatch, + &topicMatchParams ); + + if( pSubscriptionLink != NULL ) + { + pSubscription = IotLink_Container( _mqttSubscription_t, pSubscriptionLink, link ); + + /* Reference count must not be negative. */ + IotMqtt_Assert( pSubscription->references >= 0 ); + + /* Remove subscription from list. */ + IotListDouble_Remove( pSubscriptionLink ); + + /* Check the reference count. This subscription cannot be removed if + * there are subscription callbacks using it. */ + if( pSubscription->references > 0 ) + { + /* Set the unsubscribed flag. The last active subscription callback + * will remove and clean up this subscription. */ + pSubscription->unsubscribed = true; + } + else + { + /* Free a subscription with no references. */ + IotMqtt_FreeSubscription( pSubscription ); + } + } + else + { + EMPTY_ELSE_MARKER; + } + } + + IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); +} + +/*-----------------------------------------------------------*/ + +bool IotMqtt_IsSubscribed( IotMqttConnection_t mqttConnection, + const char * pTopicFilter, + uint16_t topicFilterLength, + IotMqttSubscription_t * pCurrentSubscription ) +{ + bool status = false; + _mqttSubscription_t * pSubscription = NULL; + IotLink_t * pSubscriptionLink = NULL; + _topicMatchParams_t topicMatchParams = + { + .pTopicName = pTopicFilter, + .topicNameLength = topicFilterLength, + .exactMatchOnly = true + }; + + /* Prevent any other thread from modifying the subscription list while this + * function is running. */ + IotMutex_Lock( &( mqttConnection->subscriptionMutex ) ); + + /* Search for a matching subscription. */ + pSubscriptionLink = IotListDouble_FindFirstMatch( &( mqttConnection->subscriptionList ), + NULL, + _topicMatch, + &topicMatchParams ); + + /* Check if a matching subscription was found. */ + if( pSubscriptionLink != NULL ) + { + pSubscription = IotLink_Container( _mqttSubscription_t, pSubscriptionLink, link ); + + /* Copy the matching subscription to the output parameter. */ + if( pCurrentSubscription != NULL ) + { + pCurrentSubscription->pTopicFilter = pTopicFilter; + pCurrentSubscription->topicFilterLength = topicFilterLength; + pCurrentSubscription->qos = IOT_MQTT_QOS_0; + pCurrentSubscription->callback = pSubscription->callback; + } + else + { + EMPTY_ELSE_MARKER; + } + + status = true; + } + else + { + EMPTY_ELSE_MARKER; + } + + IotMutex_Unlock( &( mqttConnection->subscriptionMutex ) ); + + return status; +} + +/*-----------------------------------------------------------*/ + +/* Provide access to internal functions and variables if testing. */ +#if IOT_BUILD_TESTS == 1 + #include "iot_test_access_mqtt_subscription.c" +#endif diff --git a/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/src/iot_mqtt_validate.c b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/src/iot_mqtt_validate.c new file mode 100644 index 000000000..8556959d8 --- /dev/null +++ b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/src/iot_mqtt_validate.c @@ -0,0 +1,593 @@ +/* + * Amazon FreeRTOS MQTT V2.0.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +/** + * @file iot_mqtt_validate.c + * @brief Implements functions that validate the structs of the MQTT library. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Error handling include. */ +#include "private/iot_error.h" + +/* MQTT internal include. */ +#include "private/iot_mqtt_internal.h" + +/*-----------------------------------------------------------*/ + +bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) +{ + IOT_FUNCTION_ENTRY( bool, true ); + + /* Check for NULL. */ + if( pConnectInfo == NULL ) + { + IotLogError( "MQTT connection information cannot be NULL." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Check that a client identifier was set. */ + if( pConnectInfo->pClientIdentifier == NULL ) + { + IotLogError( "Client identifier must be set." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Check for a zero-length client identifier. Zero-length client identifiers + * are not allowed with clean sessions. */ + if( pConnectInfo->clientIdentifierLength == 0 ) + { + IotLogWarn( "A zero-length client identifier was provided." ); + + if( pConnectInfo->cleanSession == true ) + { + IotLogError( "A zero-length client identifier cannot be used with a clean session." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Check that the number of persistent session subscriptions is valid. */ + if( pConnectInfo->cleanSession == false ) + { + if( pConnectInfo->pPreviousSubscriptions != NULL ) + { + if( pConnectInfo->previousSubscriptionCount == 0 ) + { + IotLogError( "Previous subscription count cannot be 0." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* In MQTT 3.1.1, servers are not obligated to accept client identifiers longer + * than 23 characters. */ + if( pConnectInfo->clientIdentifierLength > 23 ) + { + IotLogWarn( "A client identifier length of %hu is longer than 23, which is " + "the longest client identifier a server must accept.", + pConnectInfo->clientIdentifierLength ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Check for compatibility with the AWS IoT MQTT service limits. */ + if( pConnectInfo->awsIotMqttMode == true ) + { + /* Check that client identifier is within the service limit. */ + if( pConnectInfo->clientIdentifierLength > AWS_IOT_MQTT_SERVER_MAX_CLIENTID ) + { + IotLogError( "AWS IoT does not support client identifiers longer than %d bytes.", + AWS_IOT_MQTT_SERVER_MAX_CLIENTID ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Check for compliance with AWS IoT keep-alive limits. */ + if( pConnectInfo->keepAliveSeconds == 0 ) + { + IotLogWarn( "AWS IoT does not support disabling keep-alive. Default keep-alive " + "of %d seconds will be used.", + AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ); + } + else if( pConnectInfo->keepAliveSeconds < AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE ) + { + IotLogWarn( "AWS IoT does not support keep-alive intervals less than %d seconds. " + "An interval of %d seconds will be used.", + AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE, + AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE ); + } + else if( pConnectInfo->keepAliveSeconds > AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ) + { + IotLogWarn( "AWS IoT does not support keep-alive intervals greater than %d seconds. " + "An interval of %d seconds will be used.", + AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE, + AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +bool _IotMqtt_ValidatePublish( bool awsIotMqttMode, + const IotMqttPublishInfo_t * pPublishInfo ) +{ + IOT_FUNCTION_ENTRY( bool, true ); + + /* Check for NULL. */ + if( pPublishInfo == NULL ) + { + IotLogError( "Publish information cannot be NULL." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Check topic name for NULL or zero-length. */ + if( pPublishInfo->pTopicName == NULL ) + { + IotLogError( "Publish topic name must be set." ); + } + else + { + EMPTY_ELSE_MARKER; + } + + if( pPublishInfo->topicNameLength == 0 ) + { + IotLogError( "Publish topic name length cannot be 0." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Only allow NULL payloads with zero length. */ + if( pPublishInfo->pPayload == NULL ) + { + if( pPublishInfo->payloadLength != 0 ) + { + IotLogError( "Nonzero payload length cannot have a NULL payload." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Check for a valid QoS. */ + if( pPublishInfo->qos != IOT_MQTT_QOS_0 ) + { + if( pPublishInfo->qos != IOT_MQTT_QOS_1 ) + { + IotLogError( "Publish QoS must be either 0 or 1." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Check the retry parameters. */ + if( pPublishInfo->retryLimit > 0 ) + { + if( pPublishInfo->retryMs == 0 ) + { + IotLogError( "Publish retry time must be positive." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Check for compatibility with AWS IoT MQTT server. */ + if( awsIotMqttMode == true ) + { + /* Check for retained message. */ + if( pPublishInfo->retain == true ) + { + IotLogError( "AWS IoT does not support retained publish messages." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Check topic name length. */ + if( pPublishInfo->topicNameLength > AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ) + { + IotLogError( "AWS IoT does not support topic names longer than %d bytes.", + AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +bool _IotMqtt_ValidateOperation( IotMqttOperation_t operation ) +{ + IOT_FUNCTION_ENTRY( bool, true ); + + /* Check for NULL. */ + if( operation == NULL ) + { + IotLogError( "Operation reference cannot be NULL." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Check that reference is waitable. */ + if( ( operation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) != IOT_MQTT_FLAG_WAITABLE ) + { + IotLogError( "Operation is not waitable." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + EMPTY_ELSE_MARKER; + } + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, + bool awsIotMqttMode, + const IotMqttSubscription_t * pListStart, + size_t listSize ) +{ + IOT_FUNCTION_ENTRY( bool, true ); + size_t i = 0; + uint16_t j = 0; + const IotMqttSubscription_t * pListElement = NULL; + + /* Operation must be either subscribe or unsubscribe. */ + IotMqtt_Assert( ( operation == IOT_MQTT_SUBSCRIBE ) || + ( operation == IOT_MQTT_UNSUBSCRIBE ) ); + + /* Check for empty list. */ + if( pListStart == NULL ) + { + IotLogError( "Subscription list pointer cannot be NULL." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + EMPTY_ELSE_MARKER; + } + + if( listSize == 0 ) + { + IotLogError( "Empty subscription list." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* AWS IoT supports at most 8 topic filters in a single SUBSCRIBE packet. */ + if( awsIotMqttMode == true ) + { + if( listSize > AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ) + { + IotLogError( "AWS IoT does not support more than %d topic filters per " + "subscription request.", + AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + for( i = 0; i < listSize; i++ ) + { + pListElement = &( pListStart[ i ] ); + + /* Check for a valid QoS and callback function when subscribing. */ + if( operation == IOT_MQTT_SUBSCRIBE ) + { + if( pListElement->qos != IOT_MQTT_QOS_0 ) + { + if( pListElement->qos != IOT_MQTT_QOS_1 ) + { + IotLogError( "Subscription QoS must be either 0 or 1." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + if( pListElement->callback.function == NULL ) + { + IotLogError( "Callback function must be set." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Check subscription topic filter. */ + if( pListElement->pTopicFilter == NULL ) + { + IotLogError( "Subscription topic filter cannot be NULL." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + EMPTY_ELSE_MARKER; + } + + if( pListElement->topicFilterLength == 0 ) + { + IotLogError( "Subscription topic filter length cannot be 0." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Check for compatibility with AWS IoT MQTT server. */ + if( awsIotMqttMode == true ) + { + /* Check topic filter length. */ + if( pListElement->topicFilterLength > AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ) + { + IotLogError( "AWS IoT does not support topic filters longer than %d bytes.", + AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Check that the wildcards '+' and '#' are being used correctly. */ + for( j = 0; j < pListElement->topicFilterLength; j++ ) + { + switch( pListElement->pTopicFilter[ j ] ) + { + /* Check that the single level wildcard '+' is used correctly. */ + case '+': + + /* Unless '+' is the first character in the filter, it must be preceded by '/'. */ + if( j > 0 ) + { + if( pListElement->pTopicFilter[ j - 1 ] != '/' ) + { + IotLogError( "Invalid topic filter %.*s -- '+' must be preceded by '/'.", + pListElement->topicFilterLength, + pListElement->pTopicFilter ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Unless '+' is the last character in the filter, it must be succeeded by '/'. */ + if( j < pListElement->topicFilterLength - 1 ) + { + if( pListElement->pTopicFilter[ j + 1 ] != '/' ) + { + IotLogError( "Invalid topic filter %.*s -- '+' must be succeeded by '/'.", + pListElement->topicFilterLength, + pListElement->pTopicFilter ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + break; + + /* Check that the multi-level wildcard '#' is used correctly. */ + case '#': + + /* '#' must be the last character in the filter. */ + if( j != pListElement->topicFilterLength - 1 ) + { + IotLogError( "Invalid topic filter %.*s -- '#' must be the last character.", + pListElement->topicFilterLength, + pListElement->pTopicFilter ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Unless '#' is standalone, it must be preceded by '/'. */ + if( pListElement->topicFilterLength > 1 ) + { + if( pListElement->pTopicFilter[ j - 1 ] != '/' ) + { + IotLogError( "Invalid topic filter %.*s -- '#' must be preceded by '/'.", + pListElement->topicFilterLength, + pListElement->pTopicFilter ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + break; + + default: + break; + } + } + } + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ diff --git a/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/src/private/iot_mqtt_internal.h b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/src/private/iot_mqtt_internal.h new file mode 100644 index 000000000..80aef1c60 --- /dev/null +++ b/FreeRTOS-Plus/Source/FreeRTOS-Plus-IoT-SDK/c_sdk/standard/mqtt/src/private/iot_mqtt_internal.h @@ -0,0 +1,915 @@ +/* + * Amazon FreeRTOS MQTT V2.0.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +/** + * @file iot_mqtt_internal.h + * @brief Internal header of MQTT library. This header should not be included in + * typical application code. + */ + +#ifndef IOT_MQTT_INTERNAL_H_ +#define IOT_MQTT_INTERNAL_H_ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Linear containers (lists and queues) include. */ +#include "iot_linear_containers.h" + +/* MQTT include. */ +#include "iot_mqtt.h" + +/* Task pool include. */ +#include "iot_taskpool.h" + +/** + * @def IotMqtt_Assert( expression ) + * @brief Assertion macro for the MQTT library. + * + * Set @ref IOT_MQTT_ENABLE_ASSERTS to `1` to enable assertions in the MQTT + * library. + * + * @param[in] expression Expression to be evaluated. + */ +#if IOT_MQTT_ENABLE_ASSERTS == 1 + #ifndef IotMqtt_Assert + #include + #define IotMqtt_Assert( expression ) assert( expression ) + #endif +#else + #define IotMqtt_Assert( expression ) +#endif + +/* Configure logs for MQTT functions. */ +#ifdef IOT_LOG_LEVEL_MQTT + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_MQTT +#else + #ifdef IOT_LOG_LEVEL_GLOBAL + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL + #else + #define LIBRARY_LOG_LEVEL IOT_LOG_NONE + #endif +#endif + +#define LIBRARY_LOG_NAME ( "MQTT" ) +#include "iot_logging_setup.h" + +/* + * Provide default values for undefined memory allocation functions based on + * the usage of dynamic memory allocation. + */ +#if IOT_STATIC_MEMORY_ONLY == 1 + #include "private/iot_static_memory.h" + +/** + * @brief Allocate an #_mqttConnection_t. This function should have the same + * signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + void * IotMqtt_MallocConnection( size_t size ); + +/** + * @brief Free an #_mqttConnection_t. This function should have the same + * signature as [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + void IotMqtt_FreeConnection( void * ptr ); + +/** + * @brief Allocate memory for an MQTT packet. This function should have the + * same signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + #define IotMqtt_MallocMessage Iot_MallocMessageBuffer + +/** + * @brief Free an MQTT packet. This function should have the same signature + * as [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + #define IotMqtt_FreeMessage Iot_FreeMessageBuffer + +/** + * @brief Allocate an #_mqttOperation_t. This function should have the same + * signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + void * IotMqtt_MallocOperation( size_t size ); + +/** + * @brief Free an #_mqttOperation_t. This function should have the same + * signature as [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + void IotMqtt_FreeOperation( void * ptr ); + +/** + * @brief Allocate an #_mqttSubscription_t. This function should have the + * same signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + void * IotMqtt_MallocSubscription( size_t size ); + +/** + * @brief Free an #_mqttSubscription_t. This function should have the same + * signature as [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + void IotMqtt_FreeSubscription( void * ptr ); +#else /* if IOT_STATIC_MEMORY_ONLY == 1 */ + #include + + #ifndef IotMqtt_MallocConnection + #define IotMqtt_MallocConnection malloc + #endif + + #ifndef IotMqtt_FreeConnection + #define IotMqtt_FreeConnection free + #endif + + #ifndef IotMqtt_MallocMessage + #define IotMqtt_MallocMessage malloc + #endif + + #ifndef IotMqtt_FreeMessage + #define IotMqtt_FreeMessage free + #endif + + #ifndef IotMqtt_MallocOperation + #define IotMqtt_MallocOperation malloc + #endif + + #ifndef IotMqtt_FreeOperation + #define IotMqtt_FreeOperation free + #endif + + #ifndef IotMqtt_MallocSubscription + #define IotMqtt_MallocSubscription malloc + #endif + + #ifndef IotMqtt_FreeSubscription + #define IotMqtt_FreeSubscription free + #endif +#endif /* if IOT_STATIC_MEMORY_ONLY == 1 */ + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Provide default values for undefined configuration constants. + */ +#ifndef AWS_IOT_MQTT_ENABLE_METRICS + #define AWS_IOT_MQTT_ENABLE_METRICS ( 1 ) +#endif +#ifndef IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES + #define IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES ( 0 ) +#endif +#ifndef IOT_MQTT_RESPONSE_WAIT_MS + #define IOT_MQTT_RESPONSE_WAIT_MS ( 1000 ) +#endif +#ifndef IOT_MQTT_RETRY_MS_CEILING + #define IOT_MQTT_RETRY_MS_CEILING ( 60000 ) +#endif +/** @endcond */ + +/** + * @brief Marks the empty statement of an `else` branch. + * + * Does nothing, but allows test coverage to detect branches not taken. By default, + * this is defined to nothing. When running code coverage testing, this is defined + * to an assembly NOP. + */ +#ifndef EMPTY_ELSE_MARKER + #define EMPTY_ELSE_MARKER +#endif + +/* + * Constants related to limits defined in AWS Service Limits. + * + * For details, see + * https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html + * + * Used to validate parameters if when connecting to an AWS IoT MQTT server. + */ +#define AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE ( 30 ) /**< @brief Minumum keep-alive interval accepted by AWS IoT. */ +#define AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ( 1200 ) /**< @brief Maximum keep-alive interval accepted by AWS IoT. */ +#define AWS_IOT_MQTT_SERVER_MAX_CLIENTID ( 128 ) /**< @brief Maximum length of client identifier accepted by AWS IoT. */ +#define AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ( 256 ) /**< @brief Maximum length of topic names or filters accepted by AWS IoT. */ +#define AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ( 8 ) /**< @brief Maximum number of topic filters in a single SUBSCRIBE packet. */ + +/* + * MQTT control packet type and flags. Always the first byte of an MQTT + * packet. + * + * For details, see + * http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/csprd02/mqtt-v3.1.1-csprd02.html#_Toc385349757 + */ +#define MQTT_PACKET_TYPE_CONNECT ( ( uint8_t ) 0x10U ) /**< @brief CONNECT (client-to-server). */ +#define MQTT_PACKET_TYPE_CONNACK ( ( uint8_t ) 0x20U ) /**< @brief CONNACK (server-to-client). */ +#define MQTT_PACKET_TYPE_PUBLISH ( ( uint8_t ) 0x30U ) /**< @brief PUBLISH (bi-directional). */ +#define MQTT_PACKET_TYPE_PUBACK ( ( uint8_t ) 0x40U ) /**< @brief PUBACK (server-to-client). */ +#define MQTT_PACKET_TYPE_SUBSCRIBE ( ( uint8_t ) 0x82U ) /**< @brief SUBSCRIBE (client-to-server). */ +#define MQTT_PACKET_TYPE_SUBACK ( ( uint8_t ) 0x90U ) /**< @brief SUBACK (server-to-client). */ +#define MQTT_PACKET_TYPE_UNSUBSCRIBE ( ( uint8_t ) 0xa2U ) /**< @brief UNSUBSCRIBE (client-to-server). */ +#define MQTT_PACKET_TYPE_UNSUBACK ( ( uint8_t ) 0xb0U ) /**< @brief UNSUBACK (server-to-client). */ +#define MQTT_PACKET_TYPE_PINGREQ ( ( uint8_t ) 0xc0U ) /**< @brief PINGREQ (client-to-server). */ +#define MQTT_PACKET_TYPE_PINGRESP ( ( uint8_t ) 0xd0U ) /**< @brief PINGRESP (server-to-client). */ +#define MQTT_PACKET_TYPE_DISCONNECT ( ( uint8_t ) 0xe0U ) /**< @brief DISCONNECT (client-to-server). */ + +/** + * @brief A value that represents an invalid remaining length. + * + * This value is greater than what is allowed by the MQTT specification. + */ +#define MQTT_REMAINING_LENGTH_INVALID ( ( size_t ) 268435456 ) + +/*---------------------- MQTT internal data structures ----------------------*/ + +/** + * @brief Represents an MQTT connection. + */ +typedef struct _mqttConnection +{ + bool awsIotMqttMode; /**< @brief Specifies if this connection is to an AWS IoT MQTT server. */ + bool ownNetworkConnection; /**< @brief Whether this MQTT connection owns its network connection. */ + void * pNetworkConnection; /**< @brief References the transport-layer network connection. */ + const IotNetworkInterface_t * pNetworkInterface; /**< @brief Network interface provided to @ref mqtt_function_connect. */ + IotMqttCallbackInfo_t disconnectCallback; /**< @brief A function to invoke when this connection is disconnected. */ + + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + const IotMqttSerializer_t * pSerializer; /**< @brief MQTT packet serializer overrides. */ + #endif + + bool disconnected; /**< @brief Tracks if this connection has been disconnected. */ + IotMutex_t referencesMutex; /**< @brief Recursive mutex. Grants access to connection state and operation lists. */ + int32_t references; /**< @brief Counts callbacks and operations using this connection. */ + IotListDouble_t pendingProcessing; /**< @brief List of operations waiting to be processed by a task pool routine. */ + IotListDouble_t pendingResponse; /**< @brief List of processed operations awaiting a server response. */ + + IotListDouble_t subscriptionList; /**< @brief Holds subscriptions associated with this connection. */ + IotMutex_t subscriptionMutex; /**< @brief Grants exclusive access to the subscription list. */ + + bool keepAliveFailure; /**< @brief Failure flag for keep-alive operation. */ + uint32_t keepAliveMs; /**< @brief Keep-alive interval in milliseconds. Its max value (per spec) is 65,535,000. */ + uint32_t nextKeepAliveMs; /**< @brief Relative delay for next keep-alive job. */ + IotTaskPoolJobStorage_t keepAliveJobStorage; /**< @brief Task pool job for processing this connection's keep-alive. */ + IotTaskPoolJob_t keepAliveJob; /**< @brief Task pool job for processing this connection's keep-alive. */ + uint8_t * pPingreqPacket; /**< @brief An MQTT PINGREQ packet, allocated if keep-alive is active. */ + size_t pingreqPacketSize; /**< @brief The size of an allocated PINGREQ packet. */ +} _mqttConnection_t; + +/** + * @brief Represents a subscription stored in an MQTT connection. + */ +typedef struct _mqttSubscription +{ + IotLink_t link; /**< @brief List link member. */ + + int32_t references; /**< @brief How many subscription callbacks are using this subscription. */ + + /** + * @brief Tracks whether @ref mqtt_function_unsubscribe has been called for + * this subscription. + * + * If there are active subscription callbacks, @ref mqtt_function_unsubscribe + * cannot remove this subscription. Instead, it will set this flag, which + * schedules the removal of this subscription once all subscription callbacks + * terminate. + */ + bool unsubscribed; + + struct + { + uint16_t identifier; /**< @brief Packet identifier. */ + size_t order; /**< @brief Order in the packet's list of subscriptions. */ + } packetInfo; /**< @brief Information about the SUBSCRIBE packet that registered this subscription. */ + + IotMqttCallbackInfo_t callback; /**< @brief Callback information for this subscription. */ + + uint16_t topicFilterLength; /**< @brief Length of #_mqttSubscription_t.pTopicFilter. */ + char pTopicFilter[]; /**< @brief The subscription topic filter. */ +} _mqttSubscription_t; + +/** + * @brief Internal structure representing a single MQTT operation, such as + * CONNECT, SUBSCRIBE, PUBLISH, etc. + * + * Queues of these structures keeps track of all in-progress MQTT operations. + */ +typedef struct _mqttOperation +{ + /* Pointers to neighboring queue elements. */ + IotLink_t link; /**< @brief List link member. */ + + bool incomingPublish; /**< @brief Set to true if this operation an incoming PUBLISH. */ + _mqttConnection_t * pMqttConnection; /**< @brief MQTT connection associated with this operation. */ + + IotTaskPoolJobStorage_t jobStorage; /**< @brief Task pool job storage associated with this operation. */ + IotTaskPoolJob_t job; /**< @brief Task pool job associated with this operation. */ + + union + { + /* If incomingPublish is false, this struct is valid. */ + struct + { + /* Basic operation information. */ + int32_t jobReference; /**< @brief Tracks if a job is using this operation. Must always be 0, 1, or 2. */ + IotMqttOperationType_t type; /**< @brief What operation this structure represents. */ + uint32_t flags; /**< @brief Flags passed to the function that created this operation. */ + uint16_t packetIdentifier; /**< @brief The packet identifier used with this operation. */ + + /* Serialized packet and size. */ + uint8_t * pMqttPacket; /**< @brief The MQTT packet to send over the network. */ + uint8_t * pPacketIdentifierHigh; /**< @brief The location of the high byte of the packet identifier in the MQTT packet. */ + size_t packetSize; /**< @brief Size of `pMqttPacket`. */ + + /* How to notify of an operation's completion. */ + union + { + IotSemaphore_t waitSemaphore; /**< @brief Semaphore to be used with @ref mqtt_function_wait. */ + IotMqttCallbackInfo_t callback; /**< @brief User-provided callback function and parameter. */ + } notify; /**< @brief How to notify of this operation's completion. */ + IotMqttError_t status; /**< @brief Result of this operation. This is reported once a response is received. */ + + struct + { + uint32_t count; + uint32_t limit; + uint32_t nextPeriod; + } retry; + } operation; + + /* If incomingPublish is true, this struct is valid. */ + struct + { + IotMqttPublishInfo_t publishInfo; /**< @brief Deserialized PUBLISH. */ + const void * pReceivedData; /**< @brief Any buffer associated with this PUBLISH that should be freed. */ + } publish; + } u; /**< @brief Valid member depends on _mqttOperation_t.incomingPublish. */ +} _mqttOperation_t; + +/** + * @brief Represents an MQTT packet received from the network. + * + * This struct is used to hold parameters for the deserializers so that all + * deserializers have the same function signature. + */ +typedef struct _mqttPacket +{ + union + { + /** + * @brief (Input) MQTT connection associated with this packet. Only used + * when deserializing SUBACKs. + */ + _mqttConnection_t * pMqttConnection; + + /** + * @brief (Output) Operation representing an incoming PUBLISH. Only used + * when deserializing PUBLISHes. + */ + _mqttOperation_t * pIncomingPublish; + } u; /**< @brief Valid member depends on packet being decoded. */ + + uint8_t * pRemainingData; /**< @brief (Input) The remaining data in MQTT packet. */ + size_t remainingLength; /**< @brief (Input) Length of the remaining data in the MQTT packet. */ + uint16_t packetIdentifier; /**< @brief (Output) MQTT packet identifier. */ + uint8_t type; /**< @brief (Input) A value identifying the packet type. */ +} _mqttPacket_t; + +/*-------------------- MQTT struct validation functions ---------------------*/ + +/** + * @brief Check that an #IotMqttConnectInfo_t is valid. + * + * @param[in] pConnectInfo The #IotMqttConnectInfo_t to validate. + * + * @return `true` if `pConnectInfo` is valid; `false` otherwise. + */ +bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ); + +/** + * @brief Check that an #IotMqttPublishInfo_t is valid. + * + * @param[in] awsIotMqttMode Specifies if this PUBLISH packet is being sent to + * an AWS IoT MQTT server. + * @param[in] pPublishInfo The #IotMqttPublishInfo_t to validate. + * + * @return `true` if `pPublishInfo` is valid; `false` otherwise. + */ +bool _IotMqtt_ValidatePublish( bool awsIotMqttMode, + const IotMqttPublishInfo_t * pPublishInfo ); + +/** + * @brief Check that an #IotMqttOperation_t is valid and waitable. + * + * @param[in] operation The #IotMqttOperation_t to validate. + * + * @return `true` if `operation` is valid; `false` otherwise. + */ +bool _IotMqtt_ValidateOperation( IotMqttOperation_t operation ); + +/** + * @brief Check that a list of #IotMqttSubscription_t is valid. + * + * @param[in] operation Either #IOT_MQTT_SUBSCRIBE or #IOT_MQTT_UNSUBSCRIBE. + * Some parameters are not validated for #IOT_MQTT_UNSUBSCRIBE. + * @param[in] awsIotMqttMode Specifies if this SUBSCRIBE packet is being sent to + * an AWS IoT MQTT server. + * @param[in] pListStart First element of the list to validate. + * @param[in] listSize Number of elements in the subscription list. + * + * @return `true` if every element in the list is valid; `false` otherwise. + */ +bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, + bool awsIotMqttMode, + const IotMqttSubscription_t * pListStart, + size_t listSize ); + +/*-------------------- MQTT packet serializer functions ---------------------*/ + +/** + * @brief Get the MQTT packet type from a stream of bytes off the network. + * + * @param[in] pNetworkConnection Reference to the network connection. + * @param[in] pNetworkInterface Function pointers used to interact with the + * network. + * + * @return One of the server-to-client MQTT packet types. + * + * @note This function is only used for incoming packets, and may not work + * correctly for outgoing packets. + */ +uint8_t _IotMqtt_GetPacketType( void * pNetworkConnection, + const IotNetworkInterface_t * pNetworkInterface ); + +/** + * @brief Get the remaining length from a stream of bytes off the network. + * + * @param[in] pNetworkConnection Reference to the network connection. + * @param[in] pNetworkInterface Function pointers used to interact with the + * network. + * + * @return The remaining length; #MQTT_REMAINING_LENGTH_INVALID on error. + */ +size_t _IotMqtt_GetRemainingLength( void * pNetworkConnection, + const IotNetworkInterface_t * pNetworkInterface ); + +/** + * @brief Generate a CONNECT packet from the given parameters. + * + * @param[in] pConnectInfo User-provided CONNECT information. + * @param[out] pConnectPacket Where the CONNECT packet is written. + * @param[out] pPacketSize Size of the packet written to `pConnectPacket`. + * + * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_NO_MEMORY. + */ +IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectInfo, + uint8_t ** pConnectPacket, + size_t * pPacketSize ); + +/** + * @brief Deserialize a CONNACK packet. + * + * Converts the packet from a stream of bytes to an #IotMqttError_t. Also + * prints out debug log messages about the packet. + * + * @param[in,out] pConnack Pointer to an MQTT packet struct representing a CONNACK. + * + * @return #IOT_MQTT_SUCCESS if CONNACK specifies that CONNECT was accepted; + * #IOT_MQTT_SERVER_REFUSED if CONNACK specifies that CONNECT was rejected; + * #IOT_MQTT_BAD_RESPONSE if the CONNACK packet doesn't follow MQTT spec. + */ +IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ); + +/** + * @brief Generate a PUBLISH packet from the given parameters. + * + * @param[in] pPublishInfo User-provided PUBLISH information. + * @param[out] pPublishPacket Where the PUBLISH packet is written. + * @param[out] pPacketSize Size of the packet written to `pPublishPacket`. + * @param[out] pPacketIdentifier The packet identifier generated for this PUBLISH. + * @param[out] pPacketIdentifierHigh Where the high byte of the packet identifier + * is written. + * + * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_NO_MEMORY. + */ +IotMqttError_t _IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishInfo, + uint8_t ** pPublishPacket, + size_t * pPacketSize, + uint16_t * pPacketIdentifier, + uint8_t ** pPacketIdentifierHigh ); + +/** + * @brief Set the DUP bit in a QoS 1 PUBLISH packet. + * + * @param[in] pPublishPacket Pointer to the PUBLISH packet to modify. + * @param[in] pPacketIdentifierHigh The high byte of any packet identifier to modify. + * @param[out] pNewPacketIdentifier Since AWS IoT does not support the DUP flag, + * a new packet identifier is generated and should be written here. This parameter + * is only used when connected to an AWS IoT MQTT server. + * + * @note See #IotMqttPublishInfo_t for caveats with retransmission to the + * AWS IoT MQTT server. + */ +void _IotMqtt_PublishSetDup( uint8_t * pPublishPacket, + uint8_t * pPacketIdentifierHigh, + uint16_t * pNewPacketIdentifier ); + +/** + * @brief Deserialize a PUBLISH packet received from the server. + * + * Converts the packet from a stream of bytes to an #IotMqttPublishInfo_t and + * extracts the packet identifier. Also prints out debug log messages about the + * packet. + * + * @param[in,out] pPublish Pointer to an MQTT packet struct representing a PUBLISH. + * + * @return #IOT_MQTT_SUCCESS if PUBLISH is valid; #IOT_MQTT_BAD_RESPONSE + * if the PUBLISH packet doesn't follow MQTT spec. + */ +IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ); + +/** + * @brief Generate a PUBACK packet for the given packet identifier. + * + * @param[in] packetIdentifier The packet identifier to place in PUBACK. + * @param[out] pPubackPacket Where the PUBACK packet is written. + * @param[out] pPacketSize Size of the packet written to `pPubackPacket`. + * + * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_NO_MEMORY. + */ +IotMqttError_t _IotMqtt_SerializePuback( uint16_t packetIdentifier, + uint8_t ** pPubackPacket, + size_t * pPacketSize ); + +/** + * @brief Deserialize a PUBACK packet. + * + * Converts the packet from a stream of bytes to an #IotMqttError_t and extracts + * the packet identifier. Also prints out debug log messages about the packet. + * + * @param[in,out] pPuback Pointer to an MQTT packet struct representing a PUBACK. + * + * @return #IOT_MQTT_SUCCESS if PUBACK is valid; #IOT_MQTT_BAD_RESPONSE + * if the PUBACK packet doesn't follow MQTT spec. + */ +IotMqttError_t _IotMqtt_DeserializePuback( _mqttPacket_t * pPuback ); + +/** + * @brief Generate a SUBSCRIBE packet from the given parameters. + * + * @param[in] pSubscriptionList User-provided array of subscriptions. + * @param[in] subscriptionCount Size of `pSubscriptionList`. + * @param[out] pSubscribePacket Where the SUBSCRIBE packet is written. + * @param[out] pPacketSize Size of the packet written to `pSubscribePacket`. + * @param[out] pPacketIdentifier The packet identifier generated for this SUBSCRIBE. + * + * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_NO_MEMORY. + */ +IotMqttError_t _IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint8_t ** pSubscribePacket, + size_t * pPacketSize, + uint16_t * pPacketIdentifier ); + +/** + * @brief Deserialize a SUBACK packet. + * + * Converts the packet from a stream of bytes to an #IotMqttError_t and extracts + * the packet identifier. Also prints out debug log messages about the packet. + * + * @param[in,out] pSuback Pointer to an MQTT packet struct representing a SUBACK. + * + * @return #IOT_MQTT_SUCCESS if SUBACK is valid; #IOT_MQTT_BAD_RESPONSE + * if the SUBACK packet doesn't follow MQTT spec. + */ +IotMqttError_t _IotMqtt_DeserializeSuback( _mqttPacket_t * pSuback ); + +/** + * @brief Generate an UNSUBSCRIBE packet from the given parameters. + * + * @param[in] pSubscriptionList User-provided array of subscriptions to remove. + * @param[in] subscriptionCount Size of `pSubscriptionList`. + * @param[out] pUnsubscribePacket Where the UNSUBSCRIBE packet is written. + * @param[out] pPacketSize Size of the packet written to `pUnsubscribePacket`. + * @param[out] pPacketIdentifier The packet identifier generated for this UNSUBSCRIBE. + * + * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_NO_MEMORY. + */ +IotMqttError_t _IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint8_t ** pUnsubscribePacket, + size_t * pPacketSize, + uint16_t * pPacketIdentifier ); + +/** + * @brief Deserialize a UNSUBACK packet. + * + * Converts the packet from a stream of bytes to an #IotMqttError_t and extracts + * the packet identifier. Also prints out debug log messages about the packet. + * + * @param[in,out] pUnsuback Pointer to an MQTT packet struct representing an UNSUBACK. + * + * @return #IOT_MQTT_SUCCESS if UNSUBACK is valid; #IOT_MQTT_BAD_RESPONSE + * if the UNSUBACK packet doesn't follow MQTT spec. + */ +IotMqttError_t _IotMqtt_DeserializeUnsuback( _mqttPacket_t * pUnsuback ); + +/** + * @brief Generate a PINGREQ packet. + * + * @param[out] pPingreqPacket Where the PINGREQ packet is written. + * @param[out] pPacketSize Size of the packet written to `pPingreqPacket`. + * + * @return Always returns #IOT_MQTT_SUCCESS. + */ +IotMqttError_t _IotMqtt_SerializePingreq( uint8_t ** pPingreqPacket, + size_t * pPacketSize ); + +/** + * @brief Deserialize a PINGRESP packet. + * + * Converts the packet from a stream of bytes to an #IotMqttError_t. Also + * prints out debug log messages about the packet. + * + * @param[in,out] pPingresp Pointer to an MQTT packet struct representing a PINGRESP. + * + * @return #IOT_MQTT_SUCCESS if PINGRESP is valid; #IOT_MQTT_BAD_RESPONSE + * if the PINGRESP packet doesn't follow MQTT spec. + */ +IotMqttError_t _IotMqtt_DeserializePingresp( _mqttPacket_t * pPingresp ); + +/** + * @brief Generate a DISCONNECT packet. + * + * @param[out] pDisconnectPacket Where the DISCONNECT packet is written. + * @param[out] pPacketSize Size of the packet written to `pDisconnectPacket`. + * + * @return Always returns #IOT_MQTT_SUCCESS. + */ +IotMqttError_t _IotMqtt_SerializeDisconnect( uint8_t ** pDisconnectPacket, + size_t * pPacketSize ); + +/** + * @brief Free a packet generated by the serializer. + * + * @param[in] pPacket The packet to free. + */ +void _IotMqtt_FreePacket( uint8_t * pPacket ); + +/*-------------------- MQTT operation record functions ----------------------*/ + +/** + * @brief Create a record for a new in-progress MQTT operation. + * + * @param[in] pMqttConnection The MQTT connection to associate with the operation. + * @param[in] flags Flags variable passed to a user-facing MQTT function. + * @param[in] pCallbackInfo User-provided callback function and parameter. + * @param[out] pNewOperation Set to point to the new operation on success. + * + * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_BAD_PARAMETER, or #IOT_MQTT_NO_MEMORY. + */ +IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, + uint32_t flags, + const IotMqttCallbackInfo_t * pCallbackInfo, + _mqttOperation_t ** pNewOperation ); + +/** + * @brief Decrement the job reference count of an MQTT operation and optionally + * cancel its job. + * + * Checks if the operation may be destroyed afterwards. + * + * @param[in] pOperation The MQTT operation with the job to cancel. + * @param[in] cancelJob Whether to attempt cancellation of the operation's job. + * + * @return `true` if the the operation may be safely destroyed; `false` otherwise. + */ +bool _IotMqtt_DecrementOperationReferences( _mqttOperation_t * pOperation, + bool cancelJob ); + +/** + * @brief Free resources used to record an MQTT operation. This is called when + * the operation completes. + * + * @param[in] pOperation The operation which completed. + */ +void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation ); + +/** + * @brief Task pool routine for processing an MQTT connection's keep-alive. + * + * @param[in] pTaskPool Pointer to the system task pool. + * @param[in] pKeepAliveJob Pointer the an MQTT connection's keep-alive job. + * @param[in] pContext Pointer to an MQTT connection, passed as an opaque context. + */ +void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, + IotTaskPoolJob_t pKeepAliveJob, + void * pContext ); + +/** + * @brief Task pool routine for processing an incoming PUBLISH message. + * + * @param[in] pTaskPool Pointer to the system task pool. + * @param[in] pPublishJob Pointer to the incoming PUBLISH operation's job. + * @param[in] pContext Pointer to the incoming PUBLISH operation, passed as an + * opaque context. + */ +void _IotMqtt_ProcessIncomingPublish( IotTaskPool_t pTaskPool, + IotTaskPoolJob_t pPublishJob, + void * pContext ); + +/** + * @brief Task pool routine for processing an MQTT operation to send. + * + * @param[in] pTaskPool Pointer to the system task pool. + * @param[in] pSendJob Pointer to an operation's job. + * @param[in] pContext Pointer to the operation to send, passed as an opaque + * context. + */ +void _IotMqtt_ProcessSend( IotTaskPool_t pTaskPool, + IotTaskPoolJob_t pSendJob, + void * pContext ); + +/** + * @brief Task pool routine for processing a completed MQTT operation. + * + * @param[in] pTaskPool Pointer to the system task pool. + * @param[in] pOperationJob Pointer to the completed operation's job. + * @param[in] pContext Pointer to the completed operation, passed as an opaque + * context. + */ +void _IotMqtt_ProcessCompletedOperation( IotTaskPool_t pTaskPool, + IotTaskPoolJob_t pOperationJob, + void * pContext ); + +/** + * @brief Schedule an operation for immediate processing. + * + * @param[in] pOperation The operation to schedule. + * @param[in] jobRoutine The routine to run for the job. Must be either + * #_IotMqtt_ProcessSend, #_IotMqtt_ProcessCompletedOperation, or + * #_IotMqtt_ProcessIncomingPublish. + * @param[in] delay A delay before the operation job should be executed. Pass + * `0` to execute ASAP. + * + * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_SCHEDULING_ERROR. + */ +IotMqttError_t _IotMqtt_ScheduleOperation( _mqttOperation_t * pOperation, + IotTaskPoolRoutine_t jobRoutine, + uint32_t delay ); + +/** + * @brief Search a list of MQTT operations pending responses using an operation + * name and packet identifier. Removes a matching operation from the list if found. + * + * @param[in] pMqttConnection The connection associated with the operation. + * @param[in] type The operation type to look for. + * @param[in] pPacketIdentifier A packet identifier to match. Pass `NULL` to ignore. + * + * @return Pointer to any matching operation; `NULL` if no match was found. + */ +_mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, + IotMqttOperationType_t type, + const uint16_t * pPacketIdentifier ); + +/** + * @brief Notify of a completed MQTT operation. + * + * @param[in] pOperation The MQTT operation which completed. + * + * Depending on the parameters passed to a user-facing MQTT function, the + * notification will cause @ref mqtt_function_wait to return or invoke a + * user-provided callback. + */ +void _IotMqtt_Notify( _mqttOperation_t * pOperation ); + +/*----------------- MQTT subscription management functions ------------------*/ + +/** + * @brief Add an array of subscriptions to the subscription manager. + * + * @param[in] pMqttConnection The MQTT connection associated with the subscriptions. + * @param[in] subscribePacketIdentifier Packet identifier for the subscriptions' + * SUBSCRIBE packet. + * @param[in] pSubscriptionList The first element in the array. + * @param[in] subscriptionCount Number of elements in `pSubscriptionList`. + * + * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_NO_MEMORY. + */ +IotMqttError_t _IotMqtt_AddSubscriptions( _mqttConnection_t * pMqttConnection, + uint16_t subscribePacketIdentifier, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount ); + +/** + * @brief Process a received PUBLISH from the server, invoking any subscription + * callbacks that have a matching topic filter. + * + * @param[in] pMqttConnection The MQTT connection associated with the received + * PUBLISH. + * @param[in] pCallbackParam The parameter to pass to a PUBLISH callback. + */ +void _IotMqtt_InvokeSubscriptionCallback( _mqttConnection_t * pMqttConnection, + IotMqttCallbackParam_t * pCallbackParam ); + +/** + * @brief Remove a single subscription from the subscription manager by + * packetIdentifier and order. + * + * @param[in] pMqttConnection The MQTT connection associated with the subscriptions. + * @param[in] packetIdentifier The packet identifier associated with the subscription's + * SUBSCRIBE packet. + * @param[in] order The order of the subscription in the SUBSCRIBE packet. + * Pass `-1` to ignore order and remove all subscriptions for `packetIdentifier`. + */ +void _IotMqtt_RemoveSubscriptionByPacket( _mqttConnection_t * pMqttConnection, + uint16_t packetIdentifier, + int32_t order ); + +/** + * @brief Remove an array of subscriptions from the subscription manager by + * topic filter. + * + * @param[in] pMqttConnection The MQTT connection associated with the subscriptions. + * @param[in] pSubscriptionList The first element in the array. + * @param[in] subscriptionCount Number of elements in `pSubscriptionList`. + */ +void _IotMqtt_RemoveSubscriptionByTopicFilter( _mqttConnection_t * pMqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount ); + +/*------------------ MQTT connection management functions -------------------*/ + +/** + * @brief Attempt to increment the reference count of an MQTT connection. + * + * @param[in] pMqttConnection The referenced MQTT connection. + * + * @return `true` if the reference count was incremented; `false` otherwise. The + * reference count will not be incremented for a disconnected connection. + */ +bool _IotMqtt_IncrementConnectionReferences( _mqttConnection_t * pMqttConnection ); + +/** + * @brief Decrement the reference count of an MQTT connection. + * + * Also destroys an unreferenced MQTT connection. + * + * @param[in] pMqttConnection The referenced MQTT connection. + */ +void _IotMqtt_DecrementConnectionReferences( _mqttConnection_t * pMqttConnection ); + +/** + * @brief Read the next available byte on a network connection. + * + * @param[in] pNetworkConnection Reference to the network connection. + * @param[in] pNetworkInterface Function pointers used to interact with the + * network. + * @param[out] pIncomingByte The byte read from the network. + * + * @return `true` if a byte was successfully received from the network; `false` + * otherwise. + */ +bool _IotMqtt_GetNextByte( void * pNetworkConnection, + const IotNetworkInterface_t * pNetworkInterface, + uint8_t * pIncomingByte ); + +/** + * @brief Closes the network connection associated with an MQTT connection. + * + * A network disconnect function must be set in the network interface for the + * network connection to be closed. + * + * @param[in] disconnectReason A reason to pass to the connection's disconnect + * callback. + * @param[in] pMqttConnection The MQTT connection with the network connection + * to close. + */ +void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason, + _mqttConnection_t * pMqttConnection ); + +#endif /* ifndef IOT_MQTT_INTERNAL_H_ */