diff --git a/.github/.cSpellWords.txt b/.github/.cSpellWords.txt index 526fa1220..1253a26b9 100644 --- a/.github/.cSpellWords.txt +++ b/.github/.cSpellWords.txt @@ -45,3 +45,5 @@ Vect VECT Werror Wunused +MQTTV +mqttv diff --git a/CTestTestfile.cmake b/CTestTestfile.cmake new file mode 100644 index 000000000..2c629cab0 --- /dev/null +++ b/CTestTestfile.cmake @@ -0,0 +1,8 @@ +# CMake generated Testfile for +# Source directory: /home/ubuntu/coreMQTT/test +# Build directory: /home/ubuntu/coreMQTT +# +# This file includes the relevant testing commands required for +# testing this directory and lists subdirectories to be tested as well. +subdirs("unit-test/MQTT") +subdirs("unit-test/MQTTv5") diff --git a/docs/doxygen/config.doxyfile b/docs/doxygen/config.doxyfile index 40634095f..7a011b5db 100644 --- a/docs/doxygen/config.doxyfile +++ b/docs/doxygen/config.doxyfile @@ -2295,7 +2295,8 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = DOXYGEN=1 +PREDEFINED = DOXYGEN=1 \ + MQTT_VERSION_5_ENABLED=1 # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The diff --git a/source/core_mqtt.c b/source/core_mqtt.c index 943e08e19..463d51a7f 100644 --- a/source/core_mqtt.c +++ b/source/core_mqtt.c @@ -90,6 +90,172 @@ */ #define CORE_MQTT_UNSUBSCRIBE_PER_TOPIC_VECTOR_LENGTH ( 2U ) +#if ( MQTT_VERSION_5_ENABLED ) + +/** + * @brief User Property id. + */ + #define MQTT_USER_PROPERTY_ID ( 0x26 ) + +/** + * @brief Authentication Method id. + */ + #define MQTT_AUTH_METHOD_ID ( 0x15 ) + +/** + * @brief Authentication Data id. + */ + #define MQTT_AUTH_DATA_ID ( 0x16 ) + +/** + * @brief Content Type id. + */ + #define MQTT_CONTENT_TYPE_ID ( 0x03 ) + +/** + * @brief Response Topic id. + */ + #define MQTT_RESPONSE_TOPIC_ID ( 0x08 ) + +/** + * @brief Correlation Data id. + */ + #define MQTT_CORRELATION_DATA_ID ( 0x09 ) + +/** + * @brief Correlation Data id. + */ + #define MQTT_REASON_STRING_ID ( 0x1F ) + + +/** + * @brief Size of the property id. + */ + #define CORE_MQTT_ID_SIZE ( 1U ) + + #if ( MQTT_USER_PROPERTY_ENABLED ) + +/** + * @brief Struct used to deserialize the will properties. + * + **/ + typedef struct UserPropertiesVector + { + /** + * @brief Used to encode user key length. + * + **/ + uint8_t serializedUserKeyLength[ MAX_USER_PROPERTY ][ 2 ]; + + /** + * @brief Used to encode user id. + * + **/ + uint8_t userId[ MAX_USER_PROPERTY ]; + + /** + * @brief Used to encode user value length. + * + **/ + uint8_t serializedUserValueLength[ MAX_USER_PROPERTY ][ 2 ]; + } UserPropertyVector_t; + #endif /* if ( MQTT_USER_PROPERTY_ENABLED ) */ + +/** + * @brief Struct used to deserialize the will properties. + * + **/ + typedef struct WillPropertiesVector + { + #if ( MQTT_USER_PROPERTY_ENABLED ) + + /** + * @brief Used to encode user property. + * + **/ + UserPropertyVector_t userProperty; + #endif + + /** + * @brief Used to encode content type length. + * + **/ + uint8_t serializedContentTypeLength[ 2 ]; + + /** + * @brief Used to encode content type id. + * + **/ + uint8_t contentTypeId; + + /** + * @brief Used to encode response topic length. + * + **/ + uint8_t serializedResponseTopicLength[ 2 ]; + + /** + * @brief Used to encode response topic id. + * + **/ + uint8_t responseTopicId; + + /** + * @brief Used to encode correlation data length. + * + **/ + uint8_t serializedCorrelationLength[ 2 ]; + + /** + * @brief Used to encode correlation data id. + * + **/ + uint8_t correlationDataId; + } PublishVector_t; + +/** + * @brief Struct used to deserialize the connect properties. + * + **/ + typedef struct ConnectPropertiesVector + { + #if ( MQTT_USER_PROPERTY_ENABLED ) + + /** + * @brief Used to encode user property. + * + **/ + UserPropertyVector_t userProperty; + #endif + + /** + * @brief Used to encode authentication method length. + * + **/ + uint8_t serializedAuthMethodLength[ 2 ]; + + /** + * @brief Used to authentication method id. + * + **/ + uint8_t authMethodId; + + /** + * @brief Used to encode authentication data length. + * + **/ + uint8_t serializedAuthDataLength[ 2 ]; + + /** + * @brief Used to authentication data id. + * + **/ + uint8_t authDataId; + } PropertiesVector_t; + + +#endif /* if ( MQTT_VERSION_5_ENABLED ) */ + /*-----------------------------------------------------------*/ /** @@ -443,7 +609,6 @@ static MQTTStatus_t receiveConnack( const MQTTContext_t * pContext, static MQTTStatus_t handleSessionResumption( MQTTContext_t * pContext, bool sessionPresent ); - /** * @brief Send the publish packet without copying the topic string and payload in * the buffer. @@ -547,8 +712,560 @@ static bool matchTopicFilter( const char * pTopicName, const char * pTopicFilter, uint16_t topicFilterLength ); + +#if ( MQTT_VERSION_5_ENABLED ) + +/** + * @brief Add a string ,its length and its id after serializing it in a manner outlined by + * the MQTT specification. + * + * @param[in] serializedLength Array of two bytes to which the vector will point. + * The array must remain in scope until the message has been sent. + * @param[in] string The string to be serialized. + * @param[in] length The length of the string to be serialized. + * @param[in] iterator The iterator pointing to the first element in the + * transport interface IO array. + * @param[out] updatedLength This parameter will be added to with the number of + * bytes added to the vector. + *@param[in] packetId Pointer to one byte used to add packet Id to the packet which must + * remain in scope until the message is sent. + * + * @return The number of vectors added. + */ + + static size_t addEncodedStringToVectorWithId( uint8_t serializedLength[ CORE_MQTT_SERIALIZED_LENGTH_FIELD_BYTES ], + const char * const string, + uint16_t length, + TransportOutVector_t * iterator, + size_t * updatedLength, + const uint8_t * packetId ); + #if ( MQTT_USER_PROPERTY_ENABLED ) + +/** + * @brief Serialize the user properties. + * + * @param[in] pUserProperty Properties to serialize + * @param[in] pUserVector vectors used to encode. + * @param[in] pTotalMessageLength The iterator pointing to the first element in the + * transport interface IO array. + * @param[out] pVectorIterator This parameter will be added to with the number of + * bytes added to the vector. + * + * @return The number of vectors added. + */ + + static size_t sendUserProperties( const MQTTUserProperties_t * pUserProperty, + UserPropertyVector_t * pUserVector, + size_t * pTotalMessageLength, + TransportOutVector_t ** pVectorIterator ); + + #endif + +/** + * @brief Serialize the variable length publish properties. + * + * @param[in] pPublishInfo Properties to serialize + * @param[in] pPublishVector Vectors used to encode. + * @param[in] pTotalMessageLength The iterator pointing to the first element in the + * transport interface IO array. + * @param[out] pVectorIterator This parameter will be added to with the number of + * bytes added to the vector. + * + * @return The number of vectors added. + */ + + static size_t sendPublishProperties( const MQTTPublishInfo_t * pPublishInfo, + PublishVector_t * pPublishVector, + size_t * pTotalMessageLength, + TransportOutVector_t ** pVectorIterator ); + +/** + * @brief Serialize the variable length connect properties. + * + * @param[in] pConnectProperties Properties to serialize + * @param[in] pPropertiesVector vectors used to encode. + * @param[in] pTotalMessageLength The iterator pointing to the first element in the + * transport interface IO array. + * @param[out] pVectorIterator This parameter will be added to with the number of + * bytes added to the vector. + * + * @return The number of vectors added. + */ + + static size_t sendConnectProperties( const MQTTConnectProperties_t * pConnectProperties, + PropertiesVector_t * pPropertiesVector, + size_t * pTotalMessageLength, + TransportOutVector_t ** pVectorIterator ); + +/** + * @brief Send acks for received QoS 1/2 publishes with properties. + * + * @param[in] pContext MQTT Connection context. + * @param[in] packetId packet ID of original PUBLISH. + * @param[in] publishState Current publish state in record. + * @param[in] pAckInfo Reason code and properties. + * + * + * @return #MQTTSuccess, #MQTTBadParameter, #MQTTIllegalState or #MQTTSendFailed. + */ + + static MQTTStatus_t sendPublishAcksWithProperty( MQTTContext_t * pContext, + uint16_t packetId, + MQTTPublishState_t publishState, + MQTTAckInfo_t * pAckInfo ); + +/** + * @brief Send acks for received QoS 1/2 publishes with properties. + * + * @param[in] pContext MQTT Connection context. + * @param[in] pDisconnectInfo Reason code and properties. + * @param[in] remainingLength Remaining length of the packet. + * @param[in] sessionExpiry Session expiry interval. + * + * + * + * @return #MQTTSuccess, #MQTTBadParameter, #MQTTIllegalState or #MQTTSendFailed. + */ + + static MQTTStatus_t sendDisconnectWithoutCopyV5( MQTTContext_t * pContext, + const MQTTAckInfo_t * pDisconnectInfo, + size_t remainingLength, + uint32_t sessionExpiry ); + +/*-----------------------------------------------------------*/ + + static size_t addEncodedStringToVectorWithId( uint8_t serializedLength[ CORE_MQTT_SERIALIZED_LENGTH_FIELD_BYTES ], + const char * const string, + uint16_t length, + TransportOutVector_t * iterator, + size_t * updatedLength, + const uint8_t * packetId ) + { + TransportOutVector_t * pLocalIterator = iterator; + size_t vectorsAdded = 0U; + + /* Encode the property Id. */ + pLocalIterator[ 0 ].iov_base = packetId; + pLocalIterator[ 0 ].iov_len = CORE_MQTT_ID_SIZE; + vectorsAdded++; + pLocalIterator++; + + ( *updatedLength ) = ( *updatedLength ) + CORE_MQTT_ID_SIZE; + + /* Encode the string. */ + vectorsAdded += addEncodedStringToVector( serializedLength, + string, + length, + pLocalIterator, + updatedLength ); + + return vectorsAdded; + } + #if ( MQTT_USER_PROPERTY_ENABLED ) + static size_t sendUserProperties( const MQTTUserProperties_t * pUserProperty, + UserPropertyVector_t * pUserVector, + size_t * pTotalMessageLength, + TransportOutVector_t ** pVectorIterator ) + { + size_t vectorsAdded = 0U; + size_t ioVectorLength = 0U; + TransportOutVector_t * iterator = *pVectorIterator; + uint32_t i = 0; + uint32_t size = pUserProperty->count; + const MQTTUserProperty_t * userProperty = pUserProperty->userProperty; + + for( ; i < size; i++ ) + { + pUserVector->userId[ i ] = MQTT_USER_PROPERTY_ID; + + /*Encode the key with the user property id.*/ + vectorsAdded = addEncodedStringToVectorWithId( pUserVector->serializedUserKeyLength[ i ], + userProperty[ i ].pKey, + userProperty[ i ].keyLength, + iterator, + pTotalMessageLength, &pUserVector->userId[ i ] ); + /* Update the iterator to point to the next empty slot. */ + iterator = &iterator[ vectorsAdded ]; + ioVectorLength += vectorsAdded; + + /*Encode the value*/ + vectorsAdded = addEncodedStringToVector( pUserVector->serializedUserValueLength[ i ], + userProperty[ i ].pValue, + userProperty[ i ].valueLength, + iterator, + pTotalMessageLength ); + /* Update the iterator to point to the next empty slot. */ + iterator = &iterator[ vectorsAdded ]; + ioVectorLength += vectorsAdded; + } + + *pVectorIterator = iterator; + return ioVectorLength; + } + #endif /* if ( MQTT_USER_PROPERTY_ENABLED ) */ + static size_t sendPublishProperties( const MQTTPublishInfo_t * pPublishInfo, + PublishVector_t * pPublishVector, + size_t * pTotalMessageLength, + TransportOutVector_t ** pVectorIterator ) + { + size_t vectorsAdded = 0U; + size_t ioVectorLength = 0U; + TransportOutVector_t * iterator = *pVectorIterator; + + pPublishVector->contentTypeId = MQTT_CONTENT_TYPE_ID; + pPublishVector->responseTopicId = MQTT_RESPONSE_TOPIC_ID; + pPublishVector->correlationDataId = MQTT_CORRELATION_DATA_ID; + + /* Encode the content type if provided.*/ + if( pPublishInfo->contentTypeLength != 0U ) + { + /* Serialize the content type string. */ + vectorsAdded = addEncodedStringToVectorWithId( pPublishVector->serializedContentTypeLength, + pPublishInfo->pContentType, + pPublishInfo->contentTypeLength, + iterator, + pTotalMessageLength, &pPublishVector->contentTypeId ); + /* Update the iterator to point to the next empty slot. */ + iterator = &iterator[ vectorsAdded ]; + ioVectorLength += vectorsAdded; + } + + /* Encode the response topic if provided. */ + if( pPublishInfo->responseTopicLength != 0U ) + { + /* Serialize the response topic string. */ + vectorsAdded = addEncodedStringToVectorWithId( pPublishVector->serializedResponseTopicLength, + pPublishInfo->pResponseTopic, + pPublishInfo->responseTopicLength, + iterator, + pTotalMessageLength, &pPublishVector->responseTopicId ); + /* Update the iterator to point to the next empty slot. */ + iterator = &iterator[ vectorsAdded ]; + ioVectorLength += vectorsAdded; + } + + /* Encode the correlation length if provided. */ + if( pPublishInfo->correlationLength != 0U ) + { + /* Serialize the correlation data string. */ + vectorsAdded = addEncodedStringToVectorWithId( pPublishVector->serializedCorrelationLength, + pPublishInfo->pCorrelationData, + pPublishInfo->correlationLength, + iterator, + pTotalMessageLength, &pPublishVector->correlationDataId ); + /* Update the iterator to point to the next empty slot. */ + iterator = &iterator[ vectorsAdded ]; + ioVectorLength += vectorsAdded; + } + + #if ( MQTT_USER_PROPERTY_ENABLED ) + /* Encode the user properties if provided. */ + if( pPublishInfo->pUserProperty != NULL ) + { + ioVectorLength += sendUserProperties( pPublishInfo->pUserProperty, &pPublishVector->userProperty, pTotalMessageLength, &iterator ); + } + #endif + + *pVectorIterator = iterator; + return ioVectorLength; + } + + + static size_t sendConnectProperties( const MQTTConnectProperties_t * pConnectProperties, + PropertiesVector_t * pPropertiesVector, + size_t * pTotalMessageLength, + TransportOutVector_t ** pVectorIterator ) + { + size_t vectorsAdded = 0U; + size_t ioVectorLength = 0U; + TransportOutVector_t * iterator = *pVectorIterator; + + pPropertiesVector->authMethodId = MQTT_AUTH_METHOD_ID; + pPropertiesVector->authDataId = MQTT_AUTH_DATA_ID; + #if ( MQTT_USER_PROPERTY_ENABLED ) + /* Encode the user properties if provided. */ + if( pConnectProperties->pOutgoingUserProperty != NULL ) + { + ioVectorLength += sendUserProperties( pConnectProperties->pOutgoingUserProperty, &pPropertiesVector->userProperty, pTotalMessageLength, &iterator ); + } + #endif + + /*Encode the authentication method and data if provided*/ + if( pConnectProperties->pOutgoingAuth != NULL ) + { + /* Serialize the authentication method string. */ + vectorsAdded = addEncodedStringToVectorWithId( pPropertiesVector->serializedAuthMethodLength, + pConnectProperties->pOutgoingAuth->pAuthMethod, + pConnectProperties->pOutgoingAuth->authMethodLength, + iterator, + pTotalMessageLength, &( pPropertiesVector->authMethodId ) ); + + /* Update the iterator to point to the next empty slot. */ + iterator = &iterator[ vectorsAdded ]; + ioVectorLength += vectorsAdded; + + if( pConnectProperties->pOutgoingAuth->authDataLength != 0U ) + { + /* Serialize the authentication data string. */ + vectorsAdded = addEncodedStringToVectorWithId( pPropertiesVector->serializedAuthDataLength, + pConnectProperties->pOutgoingAuth->pAuthData, + pConnectProperties->pOutgoingAuth->authDataLength, + iterator, + pTotalMessageLength, &( pPropertiesVector->authDataId ) ); + + /* Update the iterator to point to the next empty slot. */ + iterator = &iterator[ vectorsAdded ]; + ioVectorLength += vectorsAdded; + } + } + + *pVectorIterator = iterator; + return ioVectorLength; + } + + static MQTTStatus_t sendPublishAcksWithProperty( MQTTContext_t * pContext, + uint16_t packetId, + MQTTPublishState_t publishState, + MQTTAckInfo_t * pAckInfo ) + { + int32_t bytesSentOrError; + size_t vectorsAdded = 0U; + size_t ioVectorLength = 0U; + size_t totalMessageLength = 0U; + MQTTStatus_t status = MQTTSuccess; + MQTTPublishState_t newState = MQTTStateNull; + uint8_t packetTypeByte = 0U; + MQTTPubAckType_t packetType; + + /* Maximum number of bytes required by the fixed size properties and header. + * MQTT Control Byte 0 + 1 = 1 + * Remaining length (max) + 4 = 5 + * Packet Identifier + 2 = 7 + * Reason Code + 1 = 8 + * Property length (max) + 4 = 12 */ + uint8_t pubAckHeader[ 12 ]; + size_t remainingLength = 0U; + size_t packetSize = 0U; + /* The maximum vectors required to encode and send a publish ack. */ + TransportOutVector_t pIoVector[ 4 + MAX_USER_PROPERTY * 5 ]; + uint8_t serializedReasonStringLength[ 2 ]; + uint8_t reasonStringId = MQTT_REASON_STRING_ID; + + #if ( MQTT_USER_PROPERTY_ENABLED ) + UserPropertyVector_t userVector; + #endif + uint8_t * pIndex = pubAckHeader; + TransportOutVector_t * iterator = pIoVector; + assert( pContext != NULL ); + assert( pAckInfo != NULL ); + packetTypeByte = getAckTypeToSend( publishState ); + status = MQTTV5_GetAckPacketSize( pAckInfo, &remainingLength, &packetSize, pContext->pConnectProperties->serverMaxPacketSize ); + + if( ( packetTypeByte != 0U ) && ( status == MQTTSuccess ) ) + { + packetType = getAckFromPacketType( packetTypeByte ); + /* Only for fixed size fields. */ + pIndex = MQTTV5_SerializeAckFixed( pIndex, + packetTypeByte, + packetId, + remainingLength, + pAckInfo->propertyLength ); + iterator->iov_base = pubAckHeader; + /* More details at: https://github.com/FreeRTOS/coreMQTT/blob/main/MISRA.md#rule-182 */ + /* More details at: https://github.com/FreeRTOS/coreMQTT/blob/main/MISRA.md#rule-108 */ + /* coverity[misra_c_2012_rule_18_2_violation] */ + /* coverity[misra_c_2012_rule_10_8_violation] */ + iterator->iov_len = ( size_t ) ( pIndex - pubAckHeader ); + totalMessageLength += iterator->iov_len; + iterator++; + ioVectorLength++; + + /* Encode the reason string if provided. */ + if( pAckInfo->reasonStringLength != 0U ) + { + vectorsAdded = addEncodedStringToVectorWithId( serializedReasonStringLength, + pAckInfo->pReasonString, + pAckInfo->reasonStringLength, + iterator, + &totalMessageLength, &reasonStringId ); + /* Update the iterator to point to the next empty slot. */ + iterator = &iterator[ vectorsAdded ]; + ioVectorLength += vectorsAdded; + } + + #if ( MQTT_USER_PROPERTY_ENABLED ) + /*Encode the user properties if provided.*/ + if( pAckInfo->pUserProperty != NULL ) + { + ioVectorLength += sendUserProperties( pAckInfo->pUserProperty, &userVector, &totalMessageLength, &iterator ); + } + #endif + + bytesSentOrError = sendMessageVector( pContext, pIoVector, ioVectorLength ); + + if( bytesSentOrError != ( int32_t ) totalMessageLength ) + { + LogError( ( "Failed to send ACK packet: PacketType=%02x, " + "PacketSize=%lu.", + ( unsigned int ) packetTypeByte, + packetSize ) ); + status = MQTTSendFailed; + } + + if( status == MQTTSuccess ) + { + pContext->controlPacketSent = true; + + MQTT_PRE_STATE_UPDATE_HOOK( pContext ); + + status = MQTT_UpdateStateAck( pContext, + packetId, + packetType, + MQTT_SEND, + &newState ); + + MQTT_POST_STATE_UPDATE_HOOK( pContext ); + + if( status != MQTTSuccess ) + { + LogError( ( "Failed to update state of publish %hu.", + ( unsigned short ) packetId ) ); + } + } + } + else + { + status = MQTTBadParameter; + } + + return status; + } + + static MQTTStatus_t sendDisconnectWithoutCopyV5( MQTTContext_t * pContext, + const MQTTAckInfo_t * pDisconnectInfo, + size_t remainingLength, + uint32_t sessionExpiry ) + { + int32_t bytesSentOrError; + size_t vectorsAdded = 0U; + size_t ioVectorLength = 0U; + size_t totalMessageLength = 0U; + MQTTStatus_t status = MQTTSuccess; + + /* Maximum number of bytes required by the fixed size part of the CONNECT + * packet header according to the MQTT specification. + * MQTT Control Byte 0 + 1 = 1 + * Remaining length (max) + 4 = 5 + * Reason Code + 1 = 6 + * Property length (max) + 4 = 10 + * Session Expiry + 5 = 15 + */ + uint8_t fixedHeader[ 15 ]; + + /* The maximum vectors required to encode and send a disconnect packet. The + * breakdown is shown below. + * Fixed header 0 + 1 = 1 + * Reason String + 3 = 4 + * User Property + MAX_USER_PROPERTY*5* + * */ + TransportOutVector_t pIoVector[ 4 + MAX_USER_PROPERTY * 5 ]; + uint8_t serializedReasonStringLength[ 2 ]; + uint8_t reasonStringId = MQTT_REASON_STRING_ID; + + #if ( MQTT_USER_PROPERTY_ENABLED ) + UserPropertyVector_t userVector; + #endif + uint8_t * pIndex = fixedHeader; + TransportOutVector_t * iterator = pIoVector; + assert( pContext != NULL ); + assert( pDisconnectInfo != NULL ); + + /* Only for fixed size fields. */ + pIndex = MQTTV5_SerializeDisconnectFixed( pIndex, pDisconnectInfo, remainingLength, sessionExpiry ); + iterator->iov_base = fixedHeader; + /* More details at: https://github.com/FreeRTOS/coreMQTT/blob/main/MISRA.md#rule-182 */ + /* More details at: https://github.com/FreeRTOS/coreMQTT/blob/main/MISRA.md#rule-108 */ + /* coverity[misra_c_2012_rule_18_2_violation] */ + /* coverity[misra_c_2012_rule_10_8_violation] */ + iterator->iov_len = ( size_t ) ( pIndex - fixedHeader ); + totalMessageLength += iterator->iov_len; + iterator++; + ioVectorLength++; + + /* Encode the reason string if provided. */ + if( pDisconnectInfo->reasonStringLength != 0U ) + { + vectorsAdded = addEncodedStringToVectorWithId( serializedReasonStringLength, + pDisconnectInfo->pReasonString, + pDisconnectInfo->reasonStringLength, + iterator, + &totalMessageLength, &reasonStringId ); + /* Update the iterator to point to the next empty slot. */ + iterator = &iterator[ vectorsAdded ]; + ioVectorLength += vectorsAdded; + } + + #if ( MQTT_USER_PROPERTY_ENABLED ) + /*Encode the user properties if provided.*/ + if( pDisconnectInfo->pUserProperty != NULL ) + { + ioVectorLength += sendUserProperties( pDisconnectInfo->pUserProperty, &userVector, &totalMessageLength, &iterator ); + } + #endif + + bytesSentOrError = sendMessageVector( pContext, pIoVector, ioVectorLength ); + + if( bytesSentOrError != ( int32_t ) totalMessageLength ) + { + status = MQTTSendFailed; + LogError( ( "Failed to send disconnect packet." ) ); + } + + return status; + } + +#endif /* if ( MQTT_VERSION_5_ENABLED ) */ + /*-----------------------------------------------------------*/ +static size_t addEncodedStringToVector( uint8_t serializedLength[ CORE_MQTT_SERIALIZED_LENGTH_FIELD_BYTES ], + const char * const string, + uint16_t length, + TransportOutVector_t * iterator, + size_t * updatedLength ) +{ + size_t packetLength = 0U; + TransportOutVector_t * pLocalIterator = iterator; + size_t vectorsAdded = 0U; + + /* When length is non-zero, the string must be non-NULL. */ + assert( ( length != 0U ) ? ( string != NULL ) : true ); + + serializedLength[ 0 ] = ( ( uint8_t ) ( ( length ) >> 8 ) ); + serializedLength[ 1 ] = ( ( uint8_t ) ( ( length ) & 0x00ffU ) ); + + /* Add the serialized length of the string first. */ + pLocalIterator[ 0 ].iov_base = serializedLength; + pLocalIterator[ 0 ].iov_len = CORE_MQTT_SERIALIZED_LENGTH_FIELD_BYTES; + vectorsAdded++; + packetLength = CORE_MQTT_SERIALIZED_LENGTH_FIELD_BYTES; + + /* Sometimes the string can be NULL that is, of 0 length. In that case, + * only the length field should be encoded in the vector. */ + if( ( string != NULL ) && ( length != 0U ) ) + { + /* Then add the pointer to the string itself. */ + pLocalIterator[ 1 ].iov_base = string; + pLocalIterator[ 1 ].iov_len = length; + vectorsAdded++; + packetLength += length; + } + + ( *updatedLength ) = ( *updatedLength ) + packetLength; + + return vectorsAdded; +} + static bool matchEndWildcardsSpecialCases( const char * pTopicFilter, uint16_t topicFilterLength, uint16_t filterIndex ) @@ -1531,6 +2248,13 @@ static MQTTStatus_t handlePublishAcks( MQTTContext_t * pContext, MQTTEventCallback_t appCallback; MQTTDeserializedInfo_t deserializedInfo; + #if ( MQTT_VERSION_5_ENABLED ) + MQTTAckInfo_t ackInfo; + MQTTAckInfo_t nextAckInfo; + ( void ) memset( &ackInfo, 0x0, sizeof( ackInfo ) ); + ( void ) memset( &nextAckInfo, 0x0, sizeof( nextAckInfo ) ); + #endif + assert( pContext != NULL ); assert( pIncomingPacket != NULL ); assert( pContext->appCallback != NULL ); @@ -1538,7 +2262,11 @@ static MQTTStatus_t handlePublishAcks( MQTTContext_t * pContext, appCallback = pContext->appCallback; ackType = getAckFromPacketType( pIncomingPacket->type ); - status = MQTT_DeserializeAck( pIncomingPacket, &packetIdentifier, NULL ); + #if ( !MQTT_VERSION_5_ENABLED ) + status = MQTT_DeserializeAck( pIncomingPacket, &packetIdentifier, NULL ); + #else + status = MQTTV5_DeserializeAck( pIncomingPacket, &packetIdentifier, &ackInfo, pContext->pConnectProperties->requestProblemInfo, pContext->pConnectProperties->maxPacketSize ); + #endif LogInfo( ( "Ack packet deserialized with result: %s.", MQTT_Status_strerror( status ) ) ); @@ -1568,22 +2296,53 @@ static MQTTStatus_t handlePublishAcks( MQTTContext_t * pContext, } } - if( status == MQTTSuccess ) - { - /* Set fields of deserialized struct. */ - deserializedInfo.packetIdentifier = packetIdentifier; - deserializedInfo.deserializationResult = status; - deserializedInfo.pPublishInfo = NULL; + #if ( !MQTT_VERSION_5_ENABLED ) + if( status == MQTTSuccess ) + { + /* Set fields of deserialized struct. */ + deserializedInfo.packetIdentifier = packetIdentifier; + deserializedInfo.deserializationResult = status; + deserializedInfo.pPublishInfo = NULL; - /* Invoke application callback to hand the buffer over to application - * before sending acks. */ - appCallback( pContext, pIncomingPacket, &deserializedInfo ); + /* Invoke application callback to hand the buffer over to application + * before sending acks. */ + appCallback( pContext, pIncomingPacket, &deserializedInfo ); - /* Send PUBREL or PUBCOMP if necessary. */ - status = sendPublishAcks( pContext, - packetIdentifier, - publishRecordState ); - } + /* Send PUBREL or PUBCOMP if necessary. */ + status = sendPublishAcks( pContext, + packetIdentifier, + publishRecordState ); + } + #else /* if ( !MQTT_VERSION_5_ENABLED ) */ + if( status == MQTTSuccess ) + { + deserializedInfo.packetIdentifier = packetIdentifier; + deserializedInfo.deserializationResult = status; + deserializedInfo.pPublishInfo = NULL; + deserializedInfo.pAckInfo = &ackInfo; + deserializedInfo.pNextAckInfo = &nextAckInfo; + + /* Invoke application callback to hand the buffer over to application + * before sending acks. */ + appCallback( pContext, pIncomingPacket, &deserializedInfo ); + + /* Send PUBREL or PUBCOMP if necessary. */ + if( deserializedInfo.pNextAckInfo == NULL ) + { + status = sendPublishAcks( pContext, + packetIdentifier, + publishRecordState ); + } + else + { + MQTT_PRE_SEND_HOOK( pContext ); + + status = sendPublishAcksWithProperty( pContext, packetIdentifier, publishRecordState, deserializedInfo.pNextAckInfo ); + + MQTT_POST_SEND_HOOK( pContext ); + } + } + #endif /* if ( !MQTT_VERSION_5_ENABLED ) */ return status; } @@ -1776,22 +2535,48 @@ static MQTTStatus_t receiveSingleIteration( MQTTContext_t * pContext, { status = handleIncomingPublish( pContext, &incomingPacket ); } + + #if ( MQTT_VERSION_5_ENABLED ) + else if( ( incomingPacket.type == MQTT_PACKET_TYPE_DISCONNECT ) ) + { + assert( pContext->pConnectProperties != NULL ); + assert( pContext->pDisconnectInfo != NULL ); + status = MQTTV5_DeserializeDisconnect( &incomingPacket, + pContext->pDisconnectInfo, + &pContext->pConnectProperties->pServerRef, + &pContext->pConnectProperties->serverRefLength, + pContext->pConnectProperties->maxPacketSize ); + + if( status == MQTTSuccess ) + { + LogInfo( ( "Disconnected from the broker." ) ); + pContext->connectStatus = MQTTNotConnected; + + /* Reset the index and clean the buffer on a successful disconnect. */ + pContext->index = 0; + ( void ) memset( pContext->networkBuffer.pBuffer, 0, pContext->networkBuffer.size ); + } + } + #endif /* if ( MQTT_VERSION_5_ENABLED ) */ else { status = handleIncomingAck( pContext, &incomingPacket, manageKeepAlive ); } - /* Update the index to reflect the remaining bytes in the buffer. */ - pContext->index -= totalMQTTPacketLength; + if( incomingPacket.type != MQTT_PACKET_TYPE_DISCONNECT ) + { + /* Update the index to reflect the remaining bytes in the buffer. */ + pContext->index -= totalMQTTPacketLength; - /* Move the remaining bytes to the front of the buffer. */ - ( void ) memmove( pContext->networkBuffer.pBuffer, - &( pContext->networkBuffer.pBuffer[ totalMQTTPacketLength ] ), - pContext->index ); + /* Move the remaining bytes to the front of the buffer. */ + ( void ) memmove( pContext->networkBuffer.pBuffer, + &( pContext->networkBuffer.pBuffer[ totalMQTTPacketLength ] ), + pContext->index ); - if( status == MQTTSuccess ) - { - pContext->lastPacketRxTime = pContext->getTime(); + if( status == MQTTSuccess ) + { + pContext->lastPacketRxTime = pContext->getTime(); + } } } @@ -1858,44 +2643,6 @@ static MQTTStatus_t validateSubscribeUnsubscribeParams( const MQTTContext_t * pC /*-----------------------------------------------------------*/ -static size_t addEncodedStringToVector( uint8_t serializedLength[ CORE_MQTT_SERIALIZED_LENGTH_FIELD_BYTES ], - const char * const string, - uint16_t length, - TransportOutVector_t * iterator, - size_t * updatedLength ) -{ - size_t packetLength = 0U; - TransportOutVector_t * pLocalIterator = iterator; - size_t vectorsAdded = 0U; - - /* When length is non-zero, the string must be non-NULL. */ - assert( ( length != 0U ) ? ( string != NULL ) : true ); - - serializedLength[ 0 ] = ( ( uint8_t ) ( ( length ) >> 8 ) ); - serializedLength[ 1 ] = ( ( uint8_t ) ( ( length ) & 0x00ffU ) ); - - /* Add the serialized length of the string first. */ - pLocalIterator[ 0 ].iov_base = serializedLength; - pLocalIterator[ 0 ].iov_len = CORE_MQTT_SERIALIZED_LENGTH_FIELD_BYTES; - vectorsAdded++; - packetLength = CORE_MQTT_SERIALIZED_LENGTH_FIELD_BYTES; - - /* Sometimes the string can be NULL that is, of 0 length. In that case, - * only the length field should be encoded in the vector. */ - if( ( string != NULL ) && ( length != 0U ) ) - { - /* Then add the pointer to the string itself. */ - pLocalIterator[ 1 ].iov_base = string; - pLocalIterator[ 1 ].iov_len = length; - vectorsAdded++; - packetLength += length; - } - - ( *updatedLength ) = ( *updatedLength ) + packetLength; - - return vectorsAdded; -} - /*-----------------------------------------------------------*/ static MQTTStatus_t sendSubscribeWithoutCopy( MQTTContext_t * pContext, @@ -2113,7 +2860,30 @@ static MQTTStatus_t sendPublishWithoutCopy( MQTTContext_t * pContext, * Topic string + 1 = 2 * Packet ID (only when QoS > QoS0) + 1 = 3 * Payload + 1 = 4 */ - TransportOutVector_t pIoVector[ 4U ]; + #if ( !MQTT_VERSION_5_ENABLED ) + TransportOutVector_t pIoVector[ 4U ]; + #else + + /* + * Fixed sized properties + 1 = 5 + * Response topic + 3 = 8 + * Correlation data + 3 = 11 + * Content type + 3 = 14 + * User property 5 + */ + TransportOutVector_t pIoVector[ 5 * MAX_USER_PROPERTY + 14 ]; + + /* Maximum number of bytes required by the fixed size publish properties. + * Property length 0 + 4 = 4 + * Payload Format Indicator + 2 = 6 + * Message Expiry + 5 = 11 + * Topic Alias + 3 = 14 */ + uint8_t serializedProperty[ 14U ]; + uint8_t * pIndex; + PublishVector_t publishVector; + TransportOutVector_t * iterator; + #endif + /* The header is sent first. */ pIoVector[ 0U ].iov_base = pMqttHeader; @@ -2142,6 +2912,23 @@ static MQTTStatus_t sendPublishWithoutCopy( MQTTContext_t * pContext, totalMessageLength += sizeof( serializedPacketID ); } + #if ( MQTT_VERSION_5_ENABLED ) + /*Serialize the fixed publish properties.*/ + pIndex = serializedProperty; + iterator = &pIoVector[ ioVectorLength ]; + pIndex = MQTT_SerializePublishProperties( pPublishInfo, pIndex ); + iterator->iov_base = serializedProperty; + /* More details at: https://github.com/FreeRTOS/coreMQTT/blob/main/MISRA.md#rule-182 */ + /* More details at: https://github.com/FreeRTOS/coreMQTT/blob/main/MISRA.md#rule-108 */ + /* coverity[misra_c_2012_rule_18_2_violation] */ + /* coverity[misra_c_2012_rule_10_8_violation] */ + iterator->iov_len = ( size_t ) ( pIndex - serializedProperty ); + totalMessageLength += iterator->iov_len; + iterator++; + ioVectorLength++; + ioVectorLength += sendPublishProperties( pPublishInfo, &publishVector, &totalMessageLength, &iterator ); + #endif /* if ( MQTT_VERSION_5_ENABLED ) */ + /* Publish packets are allowed to contain no payload. */ if( pPublishInfo->payloadLength > 0U ) { @@ -2180,16 +2967,50 @@ static MQTTStatus_t sendConnectWithoutCopy( MQTTContext_t * pContext, uint8_t serializedPasswordLength[ 2 ]; size_t vectorsAdded; - /* Maximum number of bytes required by the 'fixed' part of the CONNECT - * packet header according to the MQTT specification. - * MQTT Control Byte 0 + 1 = 1 - * Remaining length (max) + 4 = 5 - * Protocol Name Length + 2 = 7 - * Protocol Name (MQTT) + 4 = 11 - * Protocol level + 1 = 12 - * Connect flags + 1 = 13 - * Keep alive + 2 = 15 */ - uint8_t connectPacketHeader[ 15U ]; + #if ( !MQTT_VERSION_5_ENABLED ) + + /* Maximum number of bytes required by the 'fixed' part of the CONNECT + * packet header according to the MQTT specification. + * MQTT Control Byte 0 + 1 = 1 + * Remaining length (max) + 4 = 5 + * Protocol Name Length + 2 = 7 + * Protocol Name (MQTT) + 4 = 11 + * Protocol level + 1 = 12 + * Connect flags + 1 = 13 + * Keep alive + 2 = 15 */ + uint8_t connectPacketHeader[ 15U ]; + #else + + /* Maximum number of bytes required by the fixed part of the CONNECT + * packet header according to the MQTT specification. + * MQTT Control Byte 0 + 1 = 1 + * Remaining length (max) + 4 = 5 + * Protocol Name Length + 2 = 7 + * Protocol Name (MQTT) + 4 = 11 + * Protocol level + 1 = 12 + * Connect flags + 1 = 13 + * Keep alive + 2 = 15 + * Properties length + 4 = 19 + * Session Expiry + 5 = 24 + * receive Maximum + 3 = 27 + * Max packet Size + 5 = 32 + * Topic Alias Maximum + 3 = 35 + * Request response Information + 2 = 37 + * Request problem Information + 2 = 39 + * Total- 39 + */ + + uint8_t connectPacketHeader[ 39U ]; + + /* Maximum number of bytes required by the fixed size will properties. + * Property length 0 + 4 = 4 + * Will delay + 5 = 9 + * Payload Format Indicator + 2 = 11 + * Message Expiry + 5 = 16 */ + uint8_t fixedSizeProperties[ 16U ]; + PublishVector_t willVector; + PropertiesVector_t propertiesVector; + #endif /* if ( !MQTT_VERSION_5_ENABLED ) */ /* The maximum vectors required to encode and send a connect packet. The * breakdown is shown below. @@ -2199,7 +3020,19 @@ static MQTTStatus_t sendConnectWithoutCopy( MQTTContext_t * pContext, * Will payload + 2 = 7 * Username + 2 = 9 * Password + 2 = 11 */ - TransportOutVector_t pIoVector[ 11U ]; + #if ( MQTT_VERSION_5_ENABLED == 0 ) + TransportOutVector_t pIoVector[ 11U ]; + #else + + /* + * + * User Property- 11 + 5* Max userProperty + * Authentication 17 + 5* Max userProperty + * Will Properties 27 + 10* Max userProperty + * + */ + TransportOutVector_t pIoVector[ 27 + 10 * MAX_USER_PROPERTY ]; + #endif iterator = pIoVector; pIndex = connectPacketHeader; @@ -2217,6 +3050,9 @@ static MQTTStatus_t sendConnectWithoutCopy( MQTTContext_t * pContext, pWillInfo, remainingLength ); + #if ( MQTT_VERSION_5_ENABLED ) + pIndex = MQTTV5_SerializeConnectProperties( pIndex, pContext->pConnectProperties ); + #endif assert( ( ( size_t ) ( pIndex - connectPacketHeader ) ) <= sizeof( connectPacketHeader ) ); /* The header gets sent first. */ @@ -2230,6 +3066,11 @@ static MQTTStatus_t sendConnectWithoutCopy( MQTTContext_t * pContext, iterator++; ioVectorLength++; + #if ( MQTT_VERSION_5_ENABLED ) + /*Encode the connect Properties if provided*/ + ioVectorLength += sendConnectProperties( pContext->pConnectProperties, &propertiesVector, &totalMessageLength, &iterator ); + #endif + /* Serialize the client ID. */ vectorsAdded = addEncodedStringToVector( serializedClientIDLength, pConnectInfo->pClientIdentifier, @@ -2243,6 +3084,21 @@ static MQTTStatus_t sendConnectWithoutCopy( MQTTContext_t * pContext, if( pWillInfo != NULL ) { + #if ( MQTT_VERSION_5_ENABLED ) + /*Serialize the will properties*/ + pIndex = fixedSizeProperties; + pIndex = MQTT_SerializePublishProperties( pWillInfo, pIndex ); + iterator->iov_base = fixedSizeProperties; + /* More details at: https://github.com/FreeRTOS/coreMQTT/blob/main/MISRA.md#rule-182 */ + /* More details at: https://github.com/FreeRTOS/coreMQTT/blob/main/MISRA.md#rule-108 */ + /* coverity[misra_c_2012_rule_18_2_violation] */ + /* coverity[misra_c_2012_rule_10_8_violation] */ + iterator->iov_len = ( size_t ) ( pIndex - fixedSizeProperties ); + totalMessageLength += iterator->iov_len; + iterator++; + ioVectorLength++; + ioVectorLength += sendPublishProperties( pWillInfo, &willVector, &totalMessageLength, &iterator ); + #endif /* if ( MQTT_VERSION_5_ENABLED ) */ /* Serialize the topic. */ vectorsAdded = addEncodedStringToVector( serializedTopicLength, pWillInfo->pTopicName, @@ -2254,7 +3110,6 @@ static MQTTStatus_t sendConnectWithoutCopy( MQTTContext_t * pContext, iterator = &iterator[ vectorsAdded ]; ioVectorLength += vectorsAdded; - /* Serialize the payload. Payload of last will and testament can be NULL. */ vectorsAdded = addEncodedStringToVector( serializedPayloadLength, pWillInfo->pPayload, @@ -2403,7 +3258,11 @@ static MQTTStatus_t receiveConnack( const MQTTContext_t * pContext, pIncomingPacket->pRemainingData = pContext->networkBuffer.pBuffer; /* Deserialize CONNACK. */ - status = MQTT_DeserializeAck( pIncomingPacket, NULL, pSessionPresent ); + #if ( MQTT_VERSION_5_ENABLED == 0 ) + status = MQTT_DeserializeAck( pIncomingPacket, NULL, pSessionPresent ); + #else + status = MQTTV5_DeserializeConnack( pContext->pConnectProperties, pIncomingPacket, pSessionPresent ); + #endif } /* If a clean session is requested, a session present should not be set by @@ -2518,6 +3377,15 @@ static MQTTStatus_t validatePublishParams( const MQTTContext_t * pContext, "to initialize and enable the use of QoS1/QoS2 publishes." ) ); status = MQTTBadParameter; } + + #if ( MQTT_VERSION_5_ENABLED ) + else if( pContext->pConnectProperties == NULL ) + { + LogError( ( "Argument cannot be NULL: pConnectProperties=%p. ", + ( void * ) pContext->pConnectProperties ) ); + status = MQTTBadParameter; + } + #endif else { /* MISRA else */ @@ -2678,7 +3546,7 @@ MQTTStatus_t MQTT_CancelCallback( const MQTTContext_t * pContext, MQTTStatus_t MQTT_Connect( MQTTContext_t * pContext, const MQTTConnectInfo_t * pConnectInfo, - const MQTTPublishInfo_t * pWillInfo, + MQTTPublishInfo_t * pWillInfo, uint32_t timeoutMs, bool * pSessionPresent ) { @@ -2700,14 +3568,26 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * pContext, if( status == MQTTSuccess ) { - /* Get MQTT connect packet size and remaining length. */ - status = MQTT_GetConnectPacketSize( pConnectInfo, - pWillInfo, - &remainingLength, - &packetSize ); - LogDebug( ( "CONNECT packet size is %lu and remaining length is %lu.", - ( unsigned long ) packetSize, - ( unsigned long ) remainingLength ) ); + #if ( MQTT_VERSION_5_ENABLED ) + /* Get MQTT connect packet size and remaining length. */ + status = MQTTV5_GetConnectPacketSize( pConnectInfo, + pWillInfo, + pContext->pConnectProperties, + &remainingLength, + &packetSize ); + LogDebug( ( "CONNECT packet size is %lu and remaining length is %lu.", + ( unsigned long ) packetSize, + ( unsigned long ) remainingLength ) ); + #else + /* Get MQTT connect packet size and remaining length. */ + status = MQTT_GetConnectPacketSize( pConnectInfo, + pWillInfo, + &remainingLength, + &packetSize ); + LogDebug( ( "CONNECT packet size is %lu and remaining length is %lu.", + ( unsigned long ) packetSize, + ( unsigned long ) remainingLength ) ); + #endif /* if ( MQTT_VERSION_5_ENABLED ) */ } if( status == MQTTSuccess ) @@ -2803,7 +3683,7 @@ MQTTStatus_t MQTT_Subscribe( MQTTContext_t * pContext, /*-----------------------------------------------------------*/ MQTTStatus_t MQTT_Publish( MQTTContext_t * pContext, - const MQTTPublishInfo_t * pPublishInfo, + MQTTPublishInfo_t * pPublishInfo, uint16_t packetId ) { size_t headerSize = 0UL; @@ -2831,9 +3711,24 @@ MQTTStatus_t MQTT_Publish( MQTTContext_t * pContext, if( status == MQTTSuccess ) { /* Get the remaining length and packet size.*/ - status = MQTT_GetPublishPacketSize( pPublishInfo, - &remainingLength, - &packetSize ); + #if ( !MQTT_VERSION_5_ENABLED ) + status = MQTT_GetPublishPacketSize( pPublishInfo, + &remainingLength, + &packetSize ); + #else + status = MQTTV5_ValidatePublishParams( pPublishInfo, + pContext->pConnectProperties->serverTopicAliasMax, + pContext->pConnectProperties->retainAvailable, + pContext->pConnectProperties->serverMaxQos ); + + if( status == MQTTSuccess ) + { + status = MQTTV5_GetPublishPacketSize( pPublishInfo, + &remainingLength, + &packetSize, + pContext->pConnectProperties->serverMaxPacketSize ); + } + #endif /* if ( !MQTT_VERSION_5_ENABLED ) */ } if( status == MQTTSuccess ) @@ -3388,4 +4283,53 @@ const char * MQTT_Status_strerror( MQTTStatus_t status ) return str; } +#if ( MQTT_VERSION_5_ENABLED ) + MQTTStatus_t MQTTV5_Disconnect( MQTTContext_t * pContext, + MQTTAckInfo_t * pDisconnectInfo, + uint32_t sessionExpiry ) + { + size_t packetSize = 0U; + size_t remainingLength = 0U; + MQTTStatus_t status = MQTTSuccess; + + /* Validate arguments. */ + if( ( pContext == NULL ) || ( pDisconnectInfo == NULL ) || ( pContext->pConnectProperties == NULL ) ) + { + LogError( ( "pContext, pDisconnectInfo and connect properties cannot be NULL." ) ); + status = MQTTBadParameter; + } + + if( status == MQTTSuccess ) + { + /* Get MQTT DISCONNECT packet size. */ + status = MQTTV5_GetDisconnectPacketSize( pDisconnectInfo, &remainingLength, &packetSize, pContext->pConnectProperties->serverMaxPacketSize, sessionExpiry, pContext->pConnectProperties->sessionExpiry ); + LogDebug( ( "MQTT DISCONNECT packet size is %lu.", + ( unsigned long ) packetSize ) ); + } + + if( status == MQTTSuccess ) + { + /* Take the mutex because the below call should not be interrupted. */ + MQTT_PRE_SEND_HOOK( pContext ); + + status = sendDisconnectWithoutCopyV5( pContext, pDisconnectInfo, remainingLength, sessionExpiry ); + + /* Give the mutex away. */ + MQTT_POST_SEND_HOOK( pContext ); + } + + if( status == MQTTSuccess ) + { + LogInfo( ( "Disconnected from the broker." ) ); + pContext->connectStatus = MQTTNotConnected; + + /* Reset the index and clean the buffer on a successful disconnect. */ + pContext->index = 0; + ( void ) memset( pContext->networkBuffer.pBuffer, 0, pContext->networkBuffer.size ); + } + + return status; + } +#endif /* if ( MQTT_VERSION_5_ENABLED ) */ + /*-----------------------------------------------------------*/ diff --git a/source/core_mqtt_serializer.c b/source/core_mqtt_serializer.c index 97022034c..055168ac8 100644 --- a/source/core_mqtt_serializer.c +++ b/source/core_mqtt_serializer.c @@ -31,8 +31,6 @@ #include "core_mqtt_serializer.h" -/* Include config defaults header to get default values of configs. */ -#include "core_mqtt_config_defaults.h" /** * @brief MQTT protocol version 3.1.1. @@ -96,559 +94,2880 @@ */ #define MQTT_MAX_REMAINING_LENGTH ( 268435455UL ) +#if ( MQTT_VERSION_5_ENABLED ) + /** - * @brief Set a bit in an 8-bit unsigned integer. + * @brief Per the MQTT spec, the max packet size can be of max remaining length + 5 bytes */ -#define UINT8_SET_BIT( x, position ) ( ( x ) = ( uint8_t ) ( ( x ) | ( 0x01U << ( position ) ) ) ) + #define MQTT_MAX_PACKET_SIZE ( 268435460U ) /** - * @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. + * @brief Version 5 has the value 5. */ -#define UINT8_CHECK_BIT( x, position ) ( ( ( x ) & ( 0x01U << ( position ) ) ) == ( 0x01U << ( position ) ) ) + #define MQTT_VERSION_5 ( 5U ) /** - * @brief Get the high byte of a 16-bit unsigned integer. + * @brief Per the MQTT 5 spec, the session expiry is of 5 bytes. */ -#define UINT16_HIGH_BYTE( x ) ( ( uint8_t ) ( ( x ) >> 8 ) ) + #define MQTT_SESSION_EXPIRY_SIZE ( 5U ) /** - * @brief Get the low byte of a 16-bit unsigned integer. + * @brief Per the MQTT 5 spec, the receive maximum is of 3 bytes. */ -#define UINT16_LOW_BYTE( x ) ( ( uint8_t ) ( ( x ) & 0x00ffU ) ) + #define MQTT_RECEIVE_MAX_SIZE ( 3U ) /** - * @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. + * @brief Per the MQTT 5 spec, the max packet size is of 5 bytes. */ -#define UINT16_DECODE( ptr ) \ - ( uint16_t ) ( ( ( ( uint16_t ) ptr[ 0 ] ) << 8 ) | \ - ( ( uint16_t ) ptr[ 1 ] ) ) + #define MQTT_MAX_PACKET_PROPERTY_SIZE ( 5U ) /** - * @brief A value that represents an invalid remaining length. - * - * This value is greater than what is allowed by the MQTT specification. + * @brief Per the MQTT 5 spec, the topic alias is of 3 bytes. */ -#define MQTT_REMAINING_LENGTH_INVALID ( ( size_t ) 268435456 ) + #define MQTT_TOPIC_ALIAS_SIZE ( 3U ) /** - * @brief The minimum remaining length for a QoS 0 PUBLISH. - * - * Includes two bytes for topic name length and one byte for topic name. + * @brief Per the MQTT 5 spec, the request response is of 2 bytes. */ -#define MQTT_MIN_PUBLISH_REMAINING_LENGTH_QOS0 ( 3U ) + #define MQTT_REQUEST_RESPONSE_SIZE ( 2U ) -/*-----------------------------------------------------------*/ +/** + * @brief Per the MQTT 5 spec, the request problem is of 2 bytes. + */ + #define MQTT_REQUEST_PROBLEM_SIZE ( 2U ) + +/** + * @brief Utf 8 encoded string has 2 byte length field and 1 byte property id. + */ + #define MQTT_UTF8_LENGTH_SIZE ( 3U ) +/*Publish Properties*/ /** - * @brief MQTT Subscription packet types. + * @brief Per the MQTT 5 spec, the will delay is of 2 bytes. */ -typedef enum MQTTSubscriptionType -{ - MQTT_SUBSCRIBE, /**< @brief The type is a SUBSCRIBE packet. */ - MQTT_UNSUBSCRIBE /**< @brief The type is a UNSUBSCRIBE packet. */ -} MQTTSubscriptionType_t; + #define MQTT_WILL_DELAY_SIZE ( 5U ) -/*-----------------------------------------------------------*/ +/** + * @brief Per the MQTT 5 spec, the payload format indicator is of 2 bytes. + */ + #define MQTT_PAYLOAD_FORMAT_SIZE ( 2U ) /** - * @brief Serializes MQTT PUBLISH packet into the buffer provided. - * - * This function serializes MQTT PUBLISH packet into #MQTTFixedBuffer_t.pBuffer. - * Copy of the payload into the buffer is done as part of the serialization - * only if @p serializePayload is true. - * - * @brief param[in] pPublishInfo Publish information. - * @brief param[in] remainingLength Remaining length of the PUBLISH packet. - * @brief param[in] packetIdentifier Packet identifier of PUBLISH packet. - * @brief param[in, out] pFixedBuffer Buffer to which PUBLISH packet will be - * serialized. - * @brief param[in] serializePayload Copy payload to the serialized buffer - * only if true. Only PUBLISH header will be serialized if false. + * @brief Per the MQTT 5 spec, the request problem is of 2 bytes. */ -static void serializePublishCommon( const MQTTPublishInfo_t * pPublishInfo, - size_t remainingLength, - uint16_t packetIdentifier, - const MQTTFixedBuffer_t * pFixedBuffer, - bool serializePayload ); + #define MQTT_MSG_EXPIRY_SIZE ( 5U ) + +/*CONNECT PROPERTIES*/ /** - * @brief Calculates the packet size and remaining length of an MQTT - * PUBLISH packet. - * - * @param[in] pPublishInfo MQTT PUBLISH packet parameters. - * @param[out] pRemainingLength The Remaining Length of the MQTT PUBLISH packet. - * @param[out] pPacketSize The total size of the MQTT PUBLISH packet. - * - * @return false if the packet would exceed the size allowed by the - * MQTT spec; true otherwise. + * @brief Session expiry id. */ -static bool calculatePublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, - size_t * pRemainingLength, - size_t * pPacketSize ); + #define MQTT_SESSION_EXPIRY_ID ( 0x11 ) /** - * @brief Calculates the packet size and remaining length of an MQTT - * SUBSCRIBE or UNSUBSCRIBE packet. - * - * @param[in] pSubscriptionList List of MQTT subscription info. - * @param[in] subscriptionCount The number of elements in pSubscriptionList. - * @param[out] pRemainingLength The Remaining Length of the MQTT SUBSCRIBE or - * UNSUBSCRIBE packet. - * @param[out] pPacketSize The total size of the MQTT MQTT SUBSCRIBE or - * UNSUBSCRIBE packet. - * @param[in] subscriptionType #MQTT_SUBSCRIBE or #MQTT_UNSUBSCRIBE. - * - * #MQTTBadParameter if the packet would exceed the size allowed by the - * MQTT spec or a subscription is empty; #MQTTSuccess otherwise. + * @brief Receive maximum id. */ -static MQTTStatus_t calculateSubscriptionPacketSize( const MQTTSubscribeInfo_t * pSubscriptionList, - size_t subscriptionCount, - size_t * pRemainingLength, - size_t * pPacketSize, - MQTTSubscriptionType_t subscriptionType ); + #define MQTT_RECEIVE_MAX_ID ( 0x21 ) /** - * @brief Validates parameters of #MQTT_SerializeSubscribe or - * #MQTT_SerializeUnsubscribe. - * - * @param[in] pSubscriptionList List of MQTT subscription info. - * @param[in] subscriptionCount The number of elements in pSubscriptionList. - * @param[in] packetId Packet identifier. - * @param[in] remainingLength Remaining length of the packet. - * @param[in] pFixedBuffer Buffer for packet serialization. - * - * @return #MQTTNoMemory if pBuffer is too small to hold the MQTT packet; - * #MQTTBadParameter if invalid parameters are passed; - * #MQTTSuccess otherwise. + * @brief Maximum packet size id. */ -static MQTTStatus_t validateSubscriptionSerializeParams( const MQTTSubscribeInfo_t * pSubscriptionList, - size_t subscriptionCount, - uint16_t packetId, - size_t remainingLength, - const MQTTFixedBuffer_t * pFixedBuffer ); + #define MQTT_MAX_PACKET_SIZE_ID ( 0x27 ) /** - * @brief Serialize an MQTT CONNECT packet in the given buffer. - * - * @param[in] pConnectInfo MQTT CONNECT packet parameters. - * @param[in] pWillInfo Last Will and Testament. Pass NULL if not used. - * @param[in] remainingLength Remaining Length of MQTT CONNECT packet. - * @param[out] pFixedBuffer Buffer for packet serialization. + * @brief Topic alias size id. */ -static void serializeConnectPacket( const MQTTConnectInfo_t * pConnectInfo, - const MQTTPublishInfo_t * pWillInfo, - size_t remainingLength, - const MQTTFixedBuffer_t * pFixedBuffer ); + #define MQTT_TOPIC_ALIAS_MAX_ID ( 0x22 ) /** - * @brief Prints the appropriate message for the CONNACK response code if logs - * are enabled. - * - * @param[in] responseCode MQTT standard CONNACK response code. + * @brief Request response id. */ -static void logConnackResponse( uint8_t responseCode ); + #define MQTT_REQUEST_RESPONSE_ID ( 0x19 ) /** - * @brief Encodes the remaining length of the packet using the variable length - * encoding scheme provided in the MQTT v3.1.1 specification. - * - * @param[out] pDestination The destination buffer to store the encoded remaining - * length. - * @param[in] length The remaining length to encode. - * - * @return The location of the byte following the encoded value. + * @brief Request problem id. */ -static uint8_t * encodeRemainingLength( uint8_t * pDestination, - size_t length ); + #define MQTT_REQUEST_PROBLEM_ID ( 0x17 ) /** - * @brief Retrieve the size of the remaining length if it were to be encoded. - * - * @param[in] length The remaining length to be encoded. - * - * @return The size of the remaining length if it were to be encoded. + * @brief User property id. */ -static size_t remainingLengthEncodedSize( size_t length ); + #define MQTT_USER_PROPERTY_ID ( 0x26 ) /** - * @brief Encode a string whose size is at maximum 16 bits in length. - * - * @param[out] pDestination Destination buffer for the encoding. - * @param[in] pSource The source string to encode. - * @param[in] sourceLength The length of the source string to encode. - * - * @return A pointer to the end of the encoded string. + * @brief Authentication method id. */ -static uint8_t * encodeString( uint8_t * pDestination, - const char * pSource, - uint16_t sourceLength ); + #define MQTT_AUTH_METHOD_ID ( 0x15 ) /** - * @brief Retrieves and decodes the Remaining Length from the network interface - * by reading a single byte at a time. - * - * @param[in] recvFunc Network interface receive function. - * @param[in] pNetworkContext Network interface context to the receive function. - * - * @return The Remaining Length of the incoming packet. + * @brief Authentication data id. */ -static size_t getRemainingLength( TransportRecv_t recvFunc, - NetworkContext_t * pNetworkContext ); + #define MQTT_AUTH_DATA_ID ( 0x16 ) + +/*Publish PROPERTIES*/ /** - * @brief Retrieves, decodes and stores the Remaining Length from the network - * interface by reading a single byte at a time. - * - * @param[in] pBuffer The buffer holding the raw data to be processed - * @param[in] pIndex Pointer to the index within the buffer to marking the end of raw data - * available. - * @param[in] pIncomingPacket Structure used to hold the fields of the - * incoming packet. - * - * @return MQTTNeedMoreBytes is returned to show that the incoming - * packet is not yet fully received and decoded. Otherwise, MQTTSuccess - * shows that processing of the packet was successful. + * @brief Will delay id. */ -static MQTTStatus_t processRemainingLength( const uint8_t * pBuffer, - const size_t * pIndex, - MQTTPacketInfo_t * pIncomingPacket ); + #define MQTT_WILL_DELAY_ID ( 0x18 ) /** - * @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. + * @brief Payload format id. */ -static bool incomingPacketValid( uint8_t packetType ); + #define MQTT_PAYLOAD_FORMAT_ID ( 0x01 ) /** - * @brief Check the remaining length of an incoming PUBLISH packet against some - * value for QoS 0, or for QoS 1 and 2. - * - * The remaining length for a QoS 1 and 2 packet will always be two greater than - * for a QoS 0. - * - * @param[in] remainingLength Remaining length of the PUBLISH packet. - * @param[in] qos The QoS of the PUBLISH. - * @param[in] qos0Minimum Minimum possible remaining length for a QoS 0 PUBLISH. - * - * @return #MQTTSuccess or #MQTTBadResponse. + * @brief Message Expiry id. */ -static MQTTStatus_t checkPublishRemainingLength( size_t remainingLength, - MQTTQoS_t qos, - size_t qos0Minimum ); + #define MQTT_MSG_EXPIRY_ID ( 0x02 ) /** - * @brief Process the flags of an incoming PUBLISH packet. - * - * @param[in] publishFlags Flags of an incoming PUBLISH. - * @param[in, out] pPublishInfo Pointer to #MQTTPublishInfo_t struct where - * output will be written. - * - * @return #MQTTSuccess or #MQTTBadResponse. + * @brief Content type id. */ -static MQTTStatus_t processPublishFlags( uint8_t publishFlags, - MQTTPublishInfo_t * pPublishInfo ); + #define MQTT_CONTENT_TYPE_ID ( 0x03 ) /** - * @brief Deserialize a CONNACK packet. - * - * Converts the packet from a stream of bytes to an #MQTTStatus_t. - * - * @param[in] pConnack Pointer to an MQTT packet struct representing a - * CONNACK. - * @param[out] pSessionPresent Whether a previous session was present. - * - * @return #MQTTSuccess if CONNACK specifies that CONNECT was accepted; - * #MQTTServerRefused if CONNACK specifies that CONNECT was rejected; - * #MQTTBadResponse if the CONNACK packet doesn't follow MQTT spec. + * @brief Response topic id. */ -static MQTTStatus_t deserializeConnack( const MQTTPacketInfo_t * pConnack, - bool * pSessionPresent ); + #define MQTT_RESPONSE_TOPIC_ID ( 0x08 ) /** - * @brief Decode the status bytes of a SUBACK packet to a #MQTTStatus_t. - * - * @param[in] statusCount Number of status bytes in the SUBACK. - * @param[in] pStatusStart The first status byte in the SUBACK. - * - * @return #MQTTSuccess, #MQTTServerRefused, or #MQTTBadResponse. + * @brief Correlation data id. */ -static MQTTStatus_t readSubackStatus( size_t statusCount, - const uint8_t * pStatusStart ); + #define MQTT_CORRELATION_DATA_ID ( 0x09 ) /** - * @brief Deserialize a SUBACK packet. - * - * Converts the packet from a stream of bytes to an #MQTTStatus_t and extracts - * the packet identifier. - * - * @param[in] pSuback Pointer to an MQTT packet struct representing a SUBACK. - * @param[out] pPacketIdentifier Packet ID of the SUBACK. - * - * @return #MQTTSuccess if SUBACK is valid; #MQTTBadResponse if SUBACK packet - * doesn't follow the MQTT spec. + * @brief Topic alias id. */ -static MQTTStatus_t deserializeSuback( const MQTTPacketInfo_t * pSuback, - uint16_t * pPacketIdentifier ); + #define MQTT_TOPIC_ALIAS_ID ( 0x23 ) + +/*CONNACK PROPERTIES*/ /** - * @brief Deserialize a PUBLISH packet received from the server. - * - * Converts the packet from a stream of bytes to an #MQTTPublishInfo_t and - * extracts the packet identifier. Also prints out debug log messages about the - * packet. - * - * @param[in] pIncomingPacket Pointer to an MQTT packet struct representing a - * PUBLISH. - * @param[out] pPacketId Packet identifier of the PUBLISH. - * @param[out] pPublishInfo Pointer to #MQTTPublishInfo_t where output is - * written. - * - * @return #MQTTSuccess if PUBLISH is valid; #MQTTBadResponse - * if the PUBLISH packet doesn't follow MQTT spec. + * @brief Max qos id. */ -static MQTTStatus_t deserializePublish( const MQTTPacketInfo_t * pIncomingPacket, - uint16_t * pPacketId, - MQTTPublishInfo_t * pPublishInfo ); + #define MQTT_MAX_QOS_ID ( 0x24 ) /** - * @brief Deserialize an UNSUBACK, PUBACK, PUBREC, PUBREL, or PUBCOMP packet. - * - * Converts the packet from a stream of bytes to an #MQTTStatus_t and extracts - * the packet identifier. - * - * @param[in] pAck Pointer to the MQTT packet structure representing the packet. - * @param[out] pPacketIdentifier Packet ID of the ack type packet. - * - * @return #MQTTSuccess if UNSUBACK, PUBACK, PUBREC, PUBREL, or PUBCOMP is valid; - * #MQTTBadResponse if the packet doesn't follow the MQTT spec. + * @brief Retain available id. */ -static MQTTStatus_t deserializeSimpleAck( const MQTTPacketInfo_t * pAck, - uint16_t * pPacketIdentifier ); + #define MQTT_RETAIN_AVAILABLE_ID ( 0x25 ) /** - * @brief Deserialize a PINGRESP packet. - * - * Converts the packet from a stream of bytes to an #MQTTStatus_t. - * - * @param[in] pPingresp Pointer to an MQTT packet struct representing a PINGRESP. - * - * @return #MQTTSuccess if PINGRESP is valid; #MQTTBadResponse if the PINGRESP - * packet doesn't follow MQTT spec. + * @brief Assigned client identifier id. */ -static MQTTStatus_t deserializePingresp( const MQTTPacketInfo_t * pPingresp ); + #define MQTT_ASSIGNED_CLIENT_ID ( 0x12 ) -/*-----------------------------------------------------------*/ +/** + * @brief Reason string id. + */ + #define MQTT_REASON_STRING_ID ( 0x1F ) -static size_t remainingLengthEncodedSize( size_t length ) -{ - size_t encodedSize; +/** + * @brief Wildcard available id. + */ + #define MQTT_WILDCARD_ID ( 0x28 ) - /* Determine how many bytes are needed to encode length. - * The values below are taken from the MQTT 3.1.1 spec. */ +/** + * @brief Subscription available id. + */ + #define MQTT_SUB_AVAILABLE_ID ( 0x29 ) - /* 1 byte is needed to encode lengths between 0 and 127. */ - if( length < 128U ) - { - encodedSize = 1U; - } - /* 2 bytes are needed to encode lengths between 128 and 16,383. */ - else if( length < 16384U ) - { - encodedSize = 2U; - } - /* 3 bytes are needed to encode lengths between 16,384 and 2,097,151. */ - else if( length < 2097152U ) - { - encodedSize = 3U; - } - /* 4 bytes are needed to encode lengths between 2,097,152 and 268,435,455. */ - else - { - encodedSize = 4U; - } +/** + * @brief Shared subscription id. + */ + #define MQTT_SHARED_SUB_ID ( 0x2A ) - LogDebug( ( "Encoded size for length %lu is %lu bytes.", - ( unsigned long ) length, - ( unsigned long ) encodedSize ) ); +/** + * @brief Server keep alive id. + */ + #define MQTT_SERVER_KEEP_ALIVE_ID ( 0x13 ) - return encodedSize; -} +/** + * @brief Response information id. + */ -/*-----------------------------------------------------------*/ + #define MQTT_RESPONSE_INFO_ID ( 0x1A ) -static uint8_t * encodeRemainingLength( uint8_t * pDestination, - size_t length ) -{ - uint8_t lengthByte; - uint8_t * pLengthEnd = NULL; - size_t remainingLength = length; +/** + * @brief Server reference id. + */ + #define MQTT_SERVER_REF_ID ( 0x1C ) - assert( pDestination != NULL ); +/** + * @brief Size of the property id. + */ - pLengthEnd = pDestination; + #define CORE_MQTT_ID_SIZE ( 1U ) - /* This algorithm is copied from the MQTT v3.1.1 spec. */ - do - { - lengthByte = ( uint8_t ) ( remainingLength % 128U ); - remainingLength = remainingLength / 128U; +/** + * @ingroup mqtt_constants + * @brief The size of MQTT PUBACK, PUBREC, PUBREL, and PUBCOMP packets with reason code, packet id. + */ + #define MQTT_PUBLISH_ACK_PACKET_SIZE_WITH_REASON ( 3UL ) - /* Set the high bit of this byte, indicating that there's more data. */ - if( remainingLength > 0U ) - { - UINT8_SET_BIT( lengthByte, 7 ); - } +/** + * @brief The Connection is accepted. + */ + #define MQTT_REASON_SUCCESS ( 0x00 ) - /* Output a single encoded byte. */ - *pLengthEnd = lengthByte; - pLengthEnd++; - } while( remainingLength > 0U ); +/** + * @brief The Client wishes to disconnect but requires + * that the Server also publishes its Will Message. + */ + #define MQTT_REASON_SEND_WILL ( 0x04 ) - return pLengthEnd; -} +/** + * @brief The message is accepted but there are no + * subscribers. + */ + #define MQTT_REASON_NO_MATCHING_SUBSCRIBERS ( 0x10 ) -/*-----------------------------------------------------------*/ +/** + * @brief The Server does not wish to reveal the reason for the + * failure, or none of the other Reason Codes apply. + */ + #define MQTT_REASON_UNSPECIFIED_ERR ( 0x80 ) -static uint8_t * encodeString( uint8_t * pDestination, - const char * pSource, - uint16_t sourceLength ) -{ - uint8_t * pBuffer = NULL; +/** + * @brief Data within the CONNECT packet could not be + * correctly parsed. + */ + #define MQTT_REASON_MALFORMED_PACKET ( 0x81 ) - /* Typecast const char * typed source buffer to const uint8_t *. - * This is to use same type buffers in memcpy. */ - const uint8_t * pSourceBuffer = ( const uint8_t * ) pSource; +/** + * @brief Data in the CONNECT packet does not conform to this + * specification. + */ + #define MQTT_REASON_PROTOCOL_ERR ( 0x82 ) - assert( pDestination != NULL ); +/** + * @brief The CONNECT is valid but is not accepted by this + * Server. + */ + #define MQTT_REASON_IMPL_SPECIFIC_ERR ( 0x83 ) - pBuffer = pDestination; +/** + * @brief The Server does not support the version of the MQTT + * protocol requested by the Client. + */ + #define MQTT_REASON_UNSUPPORTED_PROTO_VER ( 0x84 ) - /* The first byte of a UTF-8 string is the high byte of the string length. */ - *pBuffer = UINT16_HIGH_BYTE( sourceLength ); - pBuffer++; +/** + * @brief The Client Identifier is a valid string but is not allowed + * by the Server. + */ + #define MQTT_REASON_CLIENT_ID_NOT_VALID ( 0x85 ) - /* The second byte of a UTF-8 string is the low byte of the string length. */ - *pBuffer = UINT16_LOW_BYTE( sourceLength ); - pBuffer++; +/** + * @brief The Server does not accept the User Name or + * Password specified by the Client. + */ + #define MQTT_REASON_BAD_USER_OR_PASS ( 0x86 ) - /* Copy the string into pBuffer. */ - if( pSourceBuffer != NULL ) - { - ( void ) memcpy( pBuffer, pSourceBuffer, sourceLength ); - } +/** + * @brief The Client is not authorized to connect. + */ + #define MQTT_REASON_NOT_AUTHORIZED ( 0x87 ) - /* Return the pointer to the end of the encoded string. */ - pBuffer = &pBuffer[ sourceLength ]; +/** + * @brief The MQTT Server is not available. + */ + #define MQTT_REASON_SERVER_UNAVAILABLE ( 0x88 ) - return pBuffer; -} +/** + * @brief The Server is busy, try again later. + */ + #define MQTT_REASON_SERVER_BUSY ( 0x89 ) -/*-----------------------------------------------------------*/ +/** + * @brief This Client has been banned by administrative action. + */ + #define MQTT_REASON_BANNED ( 0x8A ) -static bool calculatePublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, - size_t * pRemainingLength, - size_t * pPacketSize ) -{ - bool status = true; - size_t packetSize = 0, payloadLimit = 0; +/** + * @brief The Server is shutting down. + */ + #define MQTT_REASON_SERVER_SHUTTING_DOWN ( 0x8B ) - assert( pPublishInfo != NULL ); - assert( pRemainingLength != NULL ); - assert( pPacketSize != NULL ); +/** + * @brief The authentication method is not supported or does not + * match the authentication method currently in use. + */ + #define MQTT_REASON_BAD_AUTH_METHOD ( 0x8C ) - /* The variable header of a PUBLISH packet always contains the topic name. - * The first 2 bytes of UTF-8 string contains length of the string. - */ - packetSize += pPublishInfo->topicNameLength + sizeof( uint16_t ); +/** + * @brief The Connection is closed because no packet + * has been received for 1.5 times the Keepalive + * time. + */ + #define MQTT_REASON_KEEP_ALIVE_TIMEOUT ( 0x8D ) - /* The variable header of a QoS 1 or 2 PUBLISH packet contains a 2-byte - * packet identifier. */ - if( pPublishInfo->qos > MQTTQoS0 ) - { - packetSize += sizeof( uint16_t ); - } +/** + * @brief Another Connection using the same ClientID + * has connected causing this Connection to be + * closed. + */ + #define MQTT_REASON_SESSION_TAKEN_OVER ( 0x8E ) - /* 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 - packetSize - 1U; +/** + * @brief The Topic Filter is correctly formed, but is not + * accepted by this Server. + */ + #define MQTT_REASON_TOPIC_FILTER_INVALID ( 0x8F ) - /* Ensure that the given payload fits within the calculated limit. */ - if( pPublishInfo->payloadLength > payloadLimit ) - { - LogError( ( "PUBLISH payload length of %lu cannot exceed " - "%lu so as not to exceed the maximum " - "remaining length of MQTT 3.1.1 packet( %lu ).", - ( unsigned long ) pPublishInfo->payloadLength, - ( unsigned long ) payloadLimit, - MQTT_MAX_REMAINING_LENGTH ) ); - status = false; - } - else - { - /* Add the length of the PUBLISH payload. At this point, the "Remaining length" - * has been calculated. */ - packetSize += pPublishInfo->payloadLength; +/** + * @ingroup mqtt_constants + * @brief The Topic Name is not malformed, but is not + * accepted by this Server. + */ + #define MQTT_REASON_TOPIC_NAME_INVALID ( 0x90 ) - /* Now that the "Remaining length" is known, recalculate the payload limit - * based on the size of its encoding. */ - payloadLimit -= remainingLengthEncodedSize( packetSize ); +/** + * @brief The Packet Identifier is already in use. + */ + #define MQTT_REASON_PACKET_ID_IN_USE ( 0x91 ) - /* Check that the given payload fits within the size allowed by MQTT spec. */ - if( pPublishInfo->payloadLength > payloadLimit ) - { - LogError( ( "PUBLISH payload length of %lu cannot exceed " - "%lu so as not to exceed the maximum " - "remaining length of MQTT 3.1.1 packet( %lu ).", - ( unsigned long ) pPublishInfo->payloadLength, - ( unsigned long ) payloadLimit, - MQTT_MAX_REMAINING_LENGTH ) ); - status = false; - } - else - { - /* Set the "Remaining length" output parameter and calculate the full - * size of the PUBLISH packet. */ - *pRemainingLength = packetSize; +/** + * @brief The Packet Identifier is not known. + */ + #define MQTT_REASON_PACKET_ID_NOT_FOUND ( 0x92 ) - packetSize += 1U + remainingLengthEncodedSize( packetSize ); - *pPacketSize = packetSize; - } - } +/** + * @brief The Client or Server has received more than + * Receive Maximum publication for which it has + * not sent PUBACK or PUBCOMP. + */ + #define MQTT_REASON_RX_MAX_EXCEEDED ( 0x93 ) - LogDebug( ( "PUBLISH packet remaining length=%lu and packet size=%lu.", - ( unsigned long ) *pRemainingLength, - ( unsigned long ) *pPacketSize ) ); - return status; -} +/** + * @brief The Connection is accepted. + */ + #define MQTT_REASON_TOPIC_ALIAS_INVALID ( 0x94 ) -/*-----------------------------------------------------------*/ +/** + * @brief The packet exceeded the maximum + * permissible size. + */ + #define MQTT_REASON_PACKET_TOO_LARGE ( 0x95 ) -MQTTStatus_t MQTT_SerializePublishHeaderWithoutTopic( const MQTTPublishInfo_t * pPublishInfo, - size_t remainingLength, - uint8_t * pBuffer, - size_t * headerSize ) -{ - size_t headerLength; - uint8_t * pIndex; - MQTTStatus_t status = MQTTSuccess; +/** + * @brief The Connection is accepted. + */ + #define MQTT_REASON_MSG_RATE_TOO_HIGH ( 0x96 ) - /* The first byte of a PUBLISH packet contains the packet type and flags. */ - uint8_t publishFlags = MQTT_PACKET_TYPE_PUBLISH; +/** + * @brief An implementation or administrative imposed limit has + * been exceeded. + */ + #define MQTT_REASON_QUOTA_EXCEEDED ( 0x97 ) - /* Get the start address of the buffer. */ - pIndex = pBuffer; +/** + * @brief The Connection is closed due to an + * administrative action. + */ + #define MQTT_REASON_ADMIN_ACTION ( 0x98 ) - /* Length of serialized packet = First byte +/** + * @brief The Payload does not match the specified Payload + * Format Indicator. + */ + #define MQTT_REASON_PAYLOAD_FORMAT_INVALID ( 0x99 ) + +/** + * @brief The Server does not support retained messages, and + * Will Retain was set to 1. + */ + #define MQTT_REASON_RETAIN_NOT_SUPPORTED ( 0x9A ) + +/** + * @brief The Server does not support the QoS. + */ + #define MQTT_REASON_QOS_NOT_SUPPORTED ( 0x9B ) + +/** + * @brief The Client should temporarily use another server. + */ + #define MQTT_REASON_USE_ANOTHER_SERVER ( 0x9C ) + +/** + * @brief The Client should permanently use another server. + */ + #define MQTT_REASON_SERVER_MOVED ( 0x9D ) + +/** + * @brief The Server does not support Shared + * Subscriptions. + */ + #define MQTT_REASON_SS_NOT_SUPPORTED ( 0x9E ) + +/** + * @brief The connection rate limit has been exceeded. + */ + #define MQTT_REASON_CON_RATE_EXCEED ( 0x9F ) + +/** + * @brief The maximum connection time authorized for + * this connection has been exceeded. + */ + #define MQTT_REASON_MAX_CON_TIME ( 0xA0 ) + +/** + * @brief The Server does not support Subscription + * Identifiers; the subscription is not accepted. + */ + #define MQTT_REASON_SUB_ID_NOT_SUP ( 0xA1 ) + +/** + * @brief The Server does not support Wildcard + * Subscriptions; the subscription is not accepted. + */ + #define MQTT_REASON_WILDCARD_SUB_NOT_SUP ( 0xA2 ) + +#endif /* if ( MQTT_VERSION_5_ENABLED ) */ + +/** + * @brief Set a bit in an 8-bit unsigned integer. + */ +#define UINT8_SET_BIT( x, position ) ( ( x ) = ( uint8_t ) ( ( x ) | ( 0x01U << ( 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 ) & ( 0x01U << ( position ) ) ) == ( 0x01U << ( position ) ) ) + +/** + * @brief Get the high byte of a 16-bit unsigned integer. + */ +#define UINT16_HIGH_BYTE( x ) ( ( uint8_t ) ( ( x ) >> 8 ) ) + +/** + * @brief Get the low byte of a 16-bit unsigned integer. + */ +#define UINT16_LOW_BYTE( x ) ( ( uint8_t ) ( ( x ) & 0x00ffU ) ) + +/** + * @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[ 0 ] ) << 8 ) | \ + ( ( uint16_t ) ptr[ 1 ] ) ) + +#if ( MQTT_VERSION_5_ENABLED ) + +/** + * @brief Get the 4th byte of a 32-bit unsigned integer. + */ + #define UINT32_BYTE3( x ) ( ( uint8_t ) ( ( x ) >> 24 ) ) + +/** + * @brief Get the 3rd byte of a 32-bit unsigned integer. + */ + #define UINT32_BYTE2( x ) ( ( uint8_t ) ( ( x ) >> 16 ) ) + +/** + * @brief Get the 2nd byte of a 32-bit unsigned integer. + */ + #define UINT32_BYTE1( x ) ( ( uint8_t ) ( ( x ) >> 8 ) ) + +/** + * @brief Get the 1st byte of a 32-bit unsigned integer. + */ + #define UINT32_BYTE0( x ) ( ( uint8_t ) ( ( x ) & 0x000000FFU ) ) + +/** + * @brief Macro for decoding a 4-byte unsigned int from a sequence of bytes. + * + * @param[in] ptr A uint8_t* that points to the high byte. + */ + #define UINT32_DECODE( ptr ) \ + ( uint32_t ) ( ( ( ( uint32_t ) ptr[ 0 ] ) << 24 ) | \ + ( ( ( uint32_t ) ptr[ 1 ] ) << 16 ) | \ + ( ( ( uint32_t ) ptr[ 2 ] ) << 8 ) | \ + ( ( uint32_t ) ptr[ 3 ] ) ) + +#endif /* if ( MQTT_VERSION_5_ENABLED ) */ + +/** + * @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 ) + +/** + * @brief The minimum remaining length for a QoS 0 PUBLISH. + * + * Includes two bytes for topic name length and one byte for topic name. + */ +#define MQTT_MIN_PUBLISH_REMAINING_LENGTH_QOS0 ( 3U ) + +/*-----------------------------------------------------------*/ + +/** + * @brief MQTT Subscription packet types. + */ +typedef enum MQTTSubscriptionType +{ + MQTT_SUBSCRIBE, /**< @brief The type is a SUBSCRIBE packet. */ + MQTT_UNSUBSCRIBE /**< @brief The type is a UNSUBSCRIBE packet. */ +} MQTTSubscriptionType_t; + +/*-----------------------------------------------------------*/ + +/** + * @brief Serializes MQTT PUBLISH packet into the buffer provided. + * + * This function serializes MQTT PUBLISH packet into #MQTTFixedBuffer_t.pBuffer. + * Copy of the payload into the buffer is done as part of the serialization + * only if @p serializePayload is true. + * + * @brief param[in] pPublishInfo Publish information. + * @brief param[in] remainingLength Remaining length of the PUBLISH packet. + * @brief param[in] packetIdentifier Packet identifier of PUBLISH packet. + * @brief param[in, out] pFixedBuffer Buffer to which PUBLISH packet will be + * serialized. + * @brief param[in] serializePayload Copy payload to the serialized buffer + * only if true. Only PUBLISH header will be serialized if false. + */ +static void serializePublishCommon( const MQTTPublishInfo_t * pPublishInfo, + size_t remainingLength, + uint16_t packetIdentifier, + const MQTTFixedBuffer_t * pFixedBuffer, + bool serializePayload ); + +/** + * @brief Calculates the packet size and remaining length of an MQTT + * PUBLISH packet. + * + * @param[in] pPublishInfo MQTT PUBLISH packet parameters. + * @param[out] pRemainingLength The Remaining Length of the MQTT PUBLISH packet. + * @param[out] pPacketSize The total size of the MQTT PUBLISH packet. + * + * @return false if the packet would exceed the size allowed by the + * MQTT spec; true otherwise. + */ +static bool calculatePublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, + size_t * pRemainingLength, + size_t * pPacketSize ); + +/** + * @brief Calculates the packet size and remaining length of an MQTT + * SUBSCRIBE or UNSUBSCRIBE packet. + * + * @param[in] pSubscriptionList List of MQTT subscription info. + * @param[in] subscriptionCount The number of elements in pSubscriptionList. + * @param[out] pRemainingLength The Remaining Length of the MQTT SUBSCRIBE or + * UNSUBSCRIBE packet. + * @param[out] pPacketSize The total size of the MQTT MQTT SUBSCRIBE or + * UNSUBSCRIBE packet. + * @param[in] subscriptionType #MQTT_SUBSCRIBE or #MQTT_UNSUBSCRIBE. + * + * #MQTTBadParameter if the packet would exceed the size allowed by the + * MQTT spec or a subscription is empty; #MQTTSuccess otherwise. + */ +static MQTTStatus_t calculateSubscriptionPacketSize( const MQTTSubscribeInfo_t * pSubscriptionList, + size_t subscriptionCount, + size_t * pRemainingLength, + size_t * pPacketSize, + MQTTSubscriptionType_t subscriptionType ); + +/** + * @brief Validates parameters of #MQTT_SerializeSubscribe or + * #MQTT_SerializeUnsubscribe. + * + * @param[in] pSubscriptionList List of MQTT subscription info. + * @param[in] subscriptionCount The number of elements in pSubscriptionList. + * @param[in] packetId Packet identifier. + * @param[in] remainingLength Remaining length of the packet. + * @param[in] pFixedBuffer Buffer for packet serialization. + * + * @return #MQTTNoMemory if pBuffer is too small to hold the MQTT packet; + * #MQTTBadParameter if invalid parameters are passed; + * #MQTTSuccess otherwise. + */ +static MQTTStatus_t validateSubscriptionSerializeParams( const MQTTSubscribeInfo_t * pSubscriptionList, + size_t subscriptionCount, + uint16_t packetId, + size_t remainingLength, + const MQTTFixedBuffer_t * pFixedBuffer ); + +/** + * @brief Serialize an MQTT CONNECT packet in the given buffer. + * + * @param[in] pConnectInfo MQTT CONNECT packet parameters. + * @param[in] pWillInfo Last Will and Testament. Pass NULL if not used. + * @param[in] remainingLength Remaining Length of MQTT CONNECT packet. + * @param[out] pFixedBuffer Buffer for packet serialization. + */ +static void serializeConnectPacket( const MQTTConnectInfo_t * pConnectInfo, + const MQTTPublishInfo_t * pWillInfo, + size_t remainingLength, + const MQTTFixedBuffer_t * pFixedBuffer ); + +/** + * @brief Prints the appropriate message for the CONNACK response code if logs + * are enabled. + * + * @param[in] responseCode MQTT standard CONNACK response code. + */ +static void logConnackResponse( uint8_t responseCode ); + +/** + * @brief Encodes the remaining length of the packet using the variable length + * encoding scheme provided in the MQTT v3.1.1 specification. + * + * @param[out] pDestination The destination buffer to store the encoded remaining + * length. + * @param[in] length The remaining length to encode. + * + * @return The location of the byte following the encoded value. + */ +static uint8_t * encodeRemainingLength( uint8_t * pDestination, + size_t length ); + +/** + * @brief Retrieve the size of the remaining length if it were to be encoded. + * + * @param[in] length The remaining length to be encoded. + * + * @return The size of the remaining length if it were to be encoded. + */ +static size_t remainingLengthEncodedSize( size_t length ); + +/** + * @brief Encode a string whose size is at maximum 16 bits in length. + * + * @param[out] pDestination Destination buffer for the encoding. + * @param[in] pSource The source string to encode. + * @param[in] sourceLength The length of the source string to encode. + * + * @return A pointer to the end of the encoded string. + */ +static uint8_t * encodeString( uint8_t * pDestination, + const char * pSource, + uint16_t sourceLength ); + +/** + * @brief Retrieves and decodes the Remaining Length from the network interface + * by reading a single byte at a time. + * + * @param[in] recvFunc Network interface receive function. + * @param[in] pNetworkContext Network interface context to the receive function. + * + * @return The Remaining Length of the incoming packet. + */ +static size_t getRemainingLength( TransportRecv_t recvFunc, + NetworkContext_t * pNetworkContext ); + +/** + * @brief Retrieves, decodes and stores the Remaining Length from the network + * interface by reading a single byte at a time. + * + * @param[in] pBuffer The buffer holding the raw data to be processed + * @param[in] pIndex Pointer to the index within the buffer to marking the end of raw data + * available. + * @param[in] pIncomingPacket Structure used to hold the fields of the + * incoming packet. + * + * @return MQTTNeedMoreBytes is returned to show that the incoming + * packet is not yet fully received and decoded. Otherwise, MQTTSuccess + * shows that processing of the packet was successful. + */ +static MQTTStatus_t processRemainingLength( const uint8_t * pBuffer, + const size_t * pIndex, + MQTTPacketInfo_t * pIncomingPacket ); + +/** + * @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 Check the remaining length of an incoming PUBLISH packet against some + * value for QoS 0, or for QoS 1 and 2. + * + * The remaining length for a QoS 1 and 2 packet will always be two greater than + * for a QoS 0. + * + * @param[in] remainingLength Remaining length of the PUBLISH packet. + * @param[in] qos The QoS of the PUBLISH. + * @param[in] qos0Minimum Minimum possible remaining length for a QoS 0 PUBLISH. + * + * @return #MQTTSuccess or #MQTTBadResponse. + */ +static MQTTStatus_t checkPublishRemainingLength( size_t remainingLength, + MQTTQoS_t qos, + size_t qos0Minimum ); + +/** + * @brief Process the flags of an incoming PUBLISH packet. + * + * @param[in] publishFlags Flags of an incoming PUBLISH. + * @param[in, out] pPublishInfo Pointer to #MQTTPublishInfo_t struct where + * output will be written. + * + * @return #MQTTSuccess or #MQTTBadResponse. + */ +static MQTTStatus_t processPublishFlags( uint8_t publishFlags, + MQTTPublishInfo_t * pPublishInfo ); + +/** + * @brief Deserialize a CONNACK packet. + * + * Converts the packet from a stream of bytes to an #MQTTStatus_t. + * + * @param[in] pConnack Pointer to an MQTT packet struct representing a + * CONNACK. + * @param[out] pSessionPresent Whether a previous session was present. + * + * @return #MQTTSuccess if CONNACK specifies that CONNECT was accepted; + * #MQTTServerRefused if CONNACK specifies that CONNECT was rejected; + * #MQTTBadResponse if the CONNACK packet doesn't follow MQTT spec. + */ +static MQTTStatus_t deserializeConnack( const MQTTPacketInfo_t * pConnack, + bool * pSessionPresent ); + +/** + * @brief Decode the status bytes of a SUBACK packet to a #MQTTStatus_t. + * + * @param[in] statusCount Number of status bytes in the SUBACK. + * @param[in] pStatusStart The first status byte in the SUBACK. + * + * @return #MQTTSuccess, #MQTTServerRefused, or #MQTTBadResponse. + */ +static MQTTStatus_t readSubackStatus( size_t statusCount, + const uint8_t * pStatusStart ); + +/** + * @brief Deserialize a SUBACK packet. + * + * Converts the packet from a stream of bytes to an #MQTTStatus_t and extracts + * the packet identifier. + * + * @param[in] pSuback Pointer to an MQTT packet struct representing a SUBACK. + * @param[out] pPacketIdentifier Packet ID of the SUBACK. + * + * @return #MQTTSuccess if SUBACK is valid; #MQTTBadResponse if SUBACK packet + * doesn't follow the MQTT spec. + */ +static MQTTStatus_t deserializeSuback( const MQTTPacketInfo_t * pSuback, + uint16_t * pPacketIdentifier ); + +/** + * @brief Deserialize a PUBLISH packet received from the server. + * + * Converts the packet from a stream of bytes to an #MQTTPublishInfo_t and + * extracts the packet identifier. Also prints out debug log messages about the + * packet. + * + * @param[in] pIncomingPacket Pointer to an MQTT packet struct representing a + * PUBLISH. + * @param[out] pPacketId Packet identifier of the PUBLISH. + * @param[out] pPublishInfo Pointer to #MQTTPublishInfo_t where output is + * written. + * + * @return #MQTTSuccess if PUBLISH is valid; #MQTTBadResponse + * if the PUBLISH packet doesn't follow MQTT spec. + */ +static MQTTStatus_t deserializePublish( const MQTTPacketInfo_t * pIncomingPacket, + uint16_t * pPacketId, + MQTTPublishInfo_t * pPublishInfo ); + +/** + * @brief Deserialize an UNSUBACK, PUBACK, PUBREC, PUBREL, or PUBCOMP packet. + * + * Converts the packet from a stream of bytes to an #MQTTStatus_t and extracts + * the packet identifier. + * + * @param[in] pAck Pointer to the MQTT packet structure representing the packet. + * @param[out] pPacketIdentifier Packet ID of the ack type packet. + * + * @return #MQTTSuccess if UNSUBACK, PUBACK, PUBREC, PUBREL, or PUBCOMP is valid; + * #MQTTBadResponse if the packet doesn't follow the MQTT spec. + */ +static MQTTStatus_t deserializeSimpleAck( const MQTTPacketInfo_t * pAck, + uint16_t * pPacketIdentifier ); + +/** + * @brief Deserialize a PINGRESP packet. + * + * Converts the packet from a stream of bytes to an #MQTTStatus_t. + * + * @param[in] pPingresp Pointer to an MQTT packet struct representing a PINGRESP. + * + * @return #MQTTSuccess if PINGRESP is valid; #MQTTBadResponse if the PINGRESP + * packet doesn't follow MQTT spec. + */ +static MQTTStatus_t deserializePingresp( const MQTTPacketInfo_t * pPingresp ); + +#if ( MQTT_VERSION_5_ENABLED ) + + #if ( MQTT_USER_PROPERTY_ENABLED ) + +/** + * @brief Get the size of user properties. + * + * Validates the user properties and calculates the total size of user property. + * + * @param[in] pUserProperty Pointer to an MQTT packet struct representing user properties. + * @param[in] number Total number of user properties + * @param[out] pSize Size of the user properties + * + * @return #MQTTSuccess if user properties are valid and #MQTTBadParameter if the user properties are not valid + */ + static MQTTStatus_t MQTT_GetUserPropertySize( const MQTTUserProperty_t * pUserProperty, + uint32_t number, + size_t * pSize ); + +/** + * @brief Validate the length and decode a user property. + * + * @param[out] pUserProperties To store the decoded property. + * @param[out] count Number of user properties decoded. + * @param[out] pPropertyLength Size of the length. + * @param[out] pIndex Pointer to the current index of the buffer. + * + * @return #MQTTSuccess, #MQTTProtocolError and #MQTTMalformedPacket + **/ + static MQTTStatus_t decodeutf_8pair( MQTTUserProperties_t * pUserProperties, + uint32_t * count, + size_t * pPropertyLength, + const uint8_t ** pIndex ); + + #else /* if ( MQTT_USER_PROPERTY_ENABLED ) */ + +/** + * @brief Validate the length and decode a user property. + * + * @param[out] pPropertyLength Size of the length. + * @param[out] pIndex Pointer to the current index of the buffer. + * + * @return #MQTTSuccess, #MQTTProtocolError and #MQTTMalformedPacket + **/ + static MQTTStatus_t decodeAndDiscard( size_t * pPropertyLength, + const uint8_t ** pIndex ); + #endif /* if ( MQTT_USER_PROPERTY_ENABLED ) */ + +/** + * @brief Get the size of authentication information. + * + * Validates the authentication method, data and calculates the total size of authentication information. + * + * @param[in] pAuthInfo Pointer to an MQTT packet struct representing authentication information. + * @param[out] pPropertyLength Size of the authentication information. + * + * @return #MQTTSuccess if user properties are valid and #MQTTBadParameter if the user properties are not valid + */ + + static MQTTStatus_t MQTT_GetAuthInfoSize( const MQTTAuthInfo_t * pAuthInfo, + size_t * pPropertyLength ); + +/** + * @brief Get the size of connect properties. + * + * Validates the connect properties, calculates the total size of connect properties and stores it int MQTTConnectProperties_t. + * + * @param[out] pConnectProperties Pointer to an MQTT packet struct representing connect properties. + * + * @return #MQTTSuccess if connect properties are valid and #MQTTBadParameter if the connect properties are not valid. + */ + + static MQTTStatus_t MQTT_GetConnectPropertiesSize( MQTTConnectProperties_t * pConnectProperties ); + +/** + * @brief Get the size of will properties. + * + * Validates the publish properties,calculates the total size of publish properties and stores it in MQTTPublishInfo_t. + * + * @param[out] pPublishProperties Pointer to an MQTT packet struct representing publish properties. + * + * @return #MQTTSuccess if publish properties are valid and #MQTTBadParameter if they are not valid. + */ + + static MQTTStatus_t MQTT_GetPublishPropertiesSize( MQTTPublishInfo_t * pPublishProperties ); + +/** + * @brief Decodes the variable length by reading a single byte at a time. + * + * Uses the algorithm provided in the spec. + * + * @param[in] pBuffer Pointer to the buffer. + * @param[out] pLength Decoded variable length + * + * @return #MQTTSuccess if variable length and paramters are valid else #MQTTBadParameter. + */ + static MQTTStatus_t decodeVariableLength( const uint8_t * pBuffer, + size_t * pLength ); + +/** + * @brief Validate the connack parameters. + * + * Converts the packet from a stream of bytes to an #MQTTStatus_t and extracts + * the variable header without connack properties. + * + * @param[in] pIncomingPacket Pointer to an MQTT packet struct representing a incoming packet. + * @param[out] pSessionPresent Whether a session is present or not. + * + * + * @return #MQTTSuccess if connack without connack properties is valid; #MQTTBadResponse if the Connack + * packet doesn't follow MQTT spec. + */ + static MQTTStatus_t validateConnackParams( const MQTTPacketInfo_t * pIncomingPacket, + bool * pSessionPresent ); + +/** + * @brief Prints the appropriate message for the CONNACK response code if logs + * are enabled. + * + * @param[in] responseCode MQTT Verion 5 standard CONNACK response code. + * + * @return MQTTServerRefused if response code is valid and MQTTProtocolError if responseCode is invalid. + */ + static MQTTStatus_t logConnackResponseV5( uint8_t responseCode ); + +/** + * @brief Serialize a MQTT CONNECT packet in the given buffer. + * + * @param[in] pConnectInfo MQTT CONNECT packet parameters. + * @param[in] pWillInfo Last Will and Testament. Pass NULL if not used. + * @param[in] pConnectProperties MQTT CONNECT Properties parameters. + * @param[in] remainingLength Remaining Length of MQTT CONNECT packet. + * @param[in] pFixedBuffer Buffer for packet serialization. + * + */ + + static void serializeConnectPacketV5( const MQTTConnectInfo_t * pConnectInfo, + const MQTTPublishInfo_t * pWillInfo, + const MQTTConnectProperties_t * pConnectProperties, + size_t remainingLength, + const MQTTFixedBuffer_t * pFixedBuffer ); + +/** + * @brief Validate the length and decode a 4 byte value. + * + * @param[out] pProperty To store the decoded property. + * @param[out] pPropertyLength Size of the length. + * @param[out] pUsed Whether the property is decoded befire. + * @param[out] pIndex Pointer to the current index of the buffer. + * + * @return #MQTTSuccess, #MQTTProtocolError and #MQTTMalformedPacket + **/ + + static MQTTStatus_t decodeuint32_t( uint32_t * pProperty, + size_t * pPropertyLength, + bool * pUsed, + const uint8_t ** pIndex ); + +/** + * @brief Validate the length and decode a 2 byte value. + * + * @param[out] pProperty To store the decoded property. + * @param[out] pPropertyLength Size of the length. + * @param[out] pUsed Whether the property is decoded befire. + * @param[out] pIndex Pointer to the current index of the buffer. + * + * @return #MQTTSuccess, #MQTTProtocolError and #MQTTMalformedPacket + **/ + + static MQTTStatus_t decodeuint16_t( uint16_t * pProperty, + size_t * pPropertyLength, + bool * pUsed, + const uint8_t ** pIndex ); + +/** + * @brief Validate the length and decode a 1 byte value. + * + * @param[out] pProperty To store the decoded property. + * @param[out] pPropertyLength Size of the length. + * @param[out] pUsed Whether the property is decoded befire. + * @param[out] pIndex Pointer to the current index of the buffer. + * + * @return #MQTTSuccess, #MQTTProtocolError and #MQTTMalformedPacket + **/ + static MQTTStatus_t decodeuint8_t( uint8_t * pProperty, + size_t * pPropertyLength, + bool * pUsed, + const uint8_t ** pIndex ); + +/** + * @brief Validate the length and decode a utf 8 string. + * + * @param[out] pProperty To store the decoded string. + * @param[out] pLength Size of the decoded utf-8 string. + * @param[out] pPropertyLength Size of the length. + * @param[out] pUsed Whether the property is decoded before. + * @param[out] pIndex Pointer to the current index of the buffer. + * + * @return #MQTTSuccess, #MQTTProtocolError and #MQTTMalformedPacket + **/ + static MQTTStatus_t decodeutf_8( const char ** pProperty, + uint16_t * pLength, + size_t * pPropertyLength, + bool * pUsed, + const uint8_t ** pIndex ); + + +/** + * @brief Validate the length and decode authentication information. + * + * @param[out] pConnackProperties To store the decoded property. + * @param[out] pAuthMethod Whether the authentication method is decoded before. + * @param[out] pAuthData Whether the authentication data is decoded before. + * @param[out] pPropertyLength Size of the length. + * @param[out] pIndex Pointer to the current index of the buffer. + * @param[in] id To differentiate between authentication method and authentication data. + * + * @return #MQTTSuccess, #MQTTProtocolError and #MQTTMalformedPacket + **/ + static MQTTStatus_t decodeAuthInfo( const MQTTConnectProperties_t * pConnackProperties, + bool * pAuthMethod, + bool * pAuthData, + size_t * pPropertyLength, + const uint8_t ** pIndex, + const uint8_t id ); + +/** + * @brief Validate the length and decode the connack properties. + * + * @param[out] pConnackProperties To store the decoded property. + * @param[out] length Length of the property. + * @param[out] pIndex Pointer to the current index of the buffer. + * + * @return #MQTTSuccess, #MQTTProtocolError and #MQTTMalformedPacket + **/ + static MQTTStatus_t deserializeConnackV5( MQTTConnectProperties_t * pConnackProperties, + size_t length, + const uint8_t * const * pIndex ); + +/** + * @brief Calculates the packet size and remaining length of an MQTT + * PUBLISH packet. + * + * @param[in] pPublishInfo MQTT PUBLISH packet parameters. + * @param[out] pRemainingLength The Remaining Length of the MQTT PUBLISH packet. + * @param[out] pPacketSize The total size of the MQTT PUBLISH packet. + * @param[in] maxPacketSize Max packet size allowed by the server. + * + * + * @return MQTTBadParameter if the packet would exceed the size allowed by the + * MQTT spec; true otherwise. + */ + static MQTTStatus_t calculatePublishPacketSizeV5( MQTTPublishInfo_t * pPublishInfo, + size_t * pRemainingLength, + size_t * pPacketSize, + uint32_t maxPacketSize ); + +/** + * @brief Serialize a MQTT Publish Ack packet in the given buffer. + * + * @param[in] pAckInfo MQTT PUB ACK packet parameters. + * @param[in] packetType Type of pub ack. + * @param[in] packetId Packet identifier. + * @param[in] remainingLength Remaining Length of PUB ACK packet. + * @param[in] pFixedBuffer Buffer for packet serialization. + * + */ + static void serializePubAckPacketV5( const MQTTAckInfo_t * pAckInfo, + uint8_t packetType, + uint16_t packetId, + size_t remainingLength, + const MQTTFixedBuffer_t * pFixedBuffer ); + +/** + * @brief Prints the appropriate message for the PUBREL, PUBACK response code if logs + * are enabled. + * + * @param[in] reasonCode MQTT Verion 5 standard PUBREL, PUBACK response code. + * @param[in] packetIdentifier Packet id of the ack packet. + * + * @return #MQTTSuccess, #MQTTServerRefused and #MQTTProtocolError. + */ + static MQTTStatus_t logAckResponseV5( uint8_t reasonCode, + uint16_t packetIdentifier ); + +/** + * @brief Prints the appropriate message for the CONNACK response code if logs + * are enabled. + * + * @param[in] reasonCode MQTT Verion 5 standard CONNACK response code. + * @param[in] packetIdentifier Packet id of the ack packet. + * + * @return #MQTTSuccess, #MQTTServerRefused and #MQTTProtocolError. + */ + static MQTTStatus_t logSimpleAckResponseV5( uint8_t reasonCode, + uint16_t packetIdentifier ); + +/** + * @brief Validate the length and decode the publish ack properties. + * + * @param[out] pAckInfo To store the decoded property. + * @param[out] pIndex Pointer to the current index of the buffer. + * @param[out] remainingLength Remaining length of the incoming packet. + * + * + * @return #MQTTSuccess, #MQTTProtocolError and #MQTTMalformedPacket + **/ + static MQTTStatus_t decodeAckProperties( MQTTAckInfo_t * pAckInfo, + const uint8_t * pIndex, + size_t remainingLength ); + +/** + * @brief Deserialize an PUBACK, PUBREC, PUBREL, or PUBCOMP packet. + * + * Converts the packet from a stream of bytes to an #MQTTStatus_t and extracts + * the packet identifier, reason code, properties. + * + * @param[in] pAck Pointer to the MQTT packet structure representing the packet. + * @param[out] pPacketIdentifier Packet ID of the ack type packet. + * @param[out] pAckInfo Structure to store the ack properties. + * @param[in] requestProblem To validate the packet. + * + * @return #MQTTSuccess, #MQTTBadResponse, #MQTTProtocolError and #MQTTMalformedPacket. + */ + static MQTTStatus_t deserializeSimpleAckV5( const MQTTPacketInfo_t * pAck, + uint16_t * pPacketIdentifier, + MQTTAckInfo_t * pAckInfo, + bool requestProblem ); + +/** + * @brief Serialize the publish properties in the given buffer. + * + * @param[in] pPublishInfo MQTT PUBLISH packet parameters. + * @param[in] pIndex Pointer to the current index of the buffer. + * + * @return Pointer to the new index of the buffer. + */ + static uint8_t * serializePublishProperties( const MQTTPublishInfo_t * pPublishInfo, + uint8_t * pIndex ); + +/** + * @brief Serialize an MQTT CONNECT packet in the given buffer. + * + * @param[in] pDisconnectInfo MQTT DISCONNECT packet parameters. + * @param[out] pFixedBuffer Buffer for packet serialization. + * @param[in] remainingLength Remaining Length of MQTT DISCONNECT packet. + * @param[in] sessionExpiry Session Expiry Interval. + * + */ + static void serializeDisconnectPacketV5( const MQTTAckInfo_t * pDisconnectInfo, + const MQTTFixedBuffer_t * pFixedBuffer, + size_t remainingLength, + uint32_t sessionExpiry ); + +/** + * @brief Prints and validates the appropriate message for the Disconnect response code if logs + * are enabled. + * + * @param[in] reasonCode MQTT Verion 5 standard DISCONNECT response code. + * @param[in] incoming To differentiate between outgoing and incoming disconnect. + * + * @return #MQTTSuccess,#MQTTBadParameter and #MQTTProtocolError. + */ + static MQTTStatus_t validateDisconnectResponseV5( uint8_t reasonCode, + bool incoming ); +/*-----------------------------------------------------------*/ + + #if ( MQTT_USER_PROPERTY_ENABLED ) + static MQTTStatus_t MQTT_GetUserPropertySize( const MQTTUserProperty_t * pUserProperty, + uint32_t number, + size_t * pSize ) + { + MQTTStatus_t status = MQTTSuccess; + uint32_t i = 0; + + /*Number of user properties can't be more than the max user properties specified*/ + if( number > ( uint32_t ) MAX_USER_PROPERTY ) + { + status = MQTTBadParameter; + } + else + { + for( ; ( i < number ); i++ ) + { + /*Validate the key and value*/ + if( ( pUserProperty[ i ].keyLength == 0U ) || ( pUserProperty[ i ].valueLength == 0U ) || ( pUserProperty[ i ].pKey == NULL ) || ( pUserProperty[ i ].pValue == NULL ) ) + { + status = MQTTBadParameter; + break; + } + else + { + *pSize += ( pUserProperty[ i ].keyLength ); + *pSize += MQTT_UTF8_LENGTH_SIZE; + *pSize += ( pUserProperty[ i ].valueLength ); + *pSize += 2U; + } + } + } + + return status; + } + + static MQTTStatus_t decodeutf_8pair( MQTTUserProperties_t * pUserProperties, + uint32_t * count, + size_t * pPropertyLength, + const uint8_t ** pIndex ) + { + const uint8_t * pVariableHeader = *pIndex; + MQTTStatus_t status = MQTTSuccess; + MQTTUserProperty_t discardUserProperty; + MQTTUserProperty_t * pUserProperty; + + if( *count == ( uint32_t ) MAX_USER_PROPERTY ) + { + pUserProperty = &discardUserProperty; + } + else + { + pUserProperty = pUserProperties->userProperty; + pUserProperty = &pUserProperty[ *count ]; + } + + /*Validate the property length and decode the user property received.*/ + if( *pPropertyLength < sizeof( uint16_t ) ) + { + status = MQTTMalformedPacket; + } + else + { + pUserProperty->keyLength = UINT16_DECODE( pVariableHeader ); + *pPropertyLength -= sizeof( uint16_t ); + + if( *pPropertyLength < pUserProperty->keyLength ) + { + status = MQTTMalformedPacket; + } + else + { + pVariableHeader = &pVariableHeader[ sizeof( uint16_t ) ]; + pUserProperty->pKey = ( const char * ) pVariableHeader; + *pPropertyLength -= pUserProperty->keyLength; + pVariableHeader = &pVariableHeader[ pUserProperty->keyLength ]; + + if( *pPropertyLength < sizeof( uint16_t ) ) + { + status = MQTTMalformedPacket; + } + else + { + pUserProperty->valueLength = UINT16_DECODE( pVariableHeader ); + *pPropertyLength -= sizeof( uint16_t ); + pVariableHeader = &pVariableHeader[ sizeof( uint16_t ) ]; + + if( *pPropertyLength < ( size_t ) ( pUserProperty->valueLength ) ) + { + status = MQTTMalformedPacket; + } + else + { + pUserProperty->pValue = ( const char * ) pVariableHeader; + pVariableHeader = &pVariableHeader[ pUserProperty->valueLength ]; + *pPropertyLength -= pUserProperty->valueLength; + } + } + } + } + + *pIndex = pVariableHeader; + + if( ( *count == ( uint32_t ) MAX_USER_PROPERTY ) && ( status == MQTTSuccess ) ) + { + LogDebug( ( "Discarded additional user property with key %s and value %s. ", + discardUserProperty.pKey, + discardUserProperty.pValue ) ); + } + else + { + *count += 1U; + } + + return status; + } + #else /* if ( MQTT_USER_PROPERTY_ENABLED ) */ + static MQTTStatus_t decodeAndDiscard( size_t * pPropertyLength, + const uint8_t ** pIndex ) + { + const uint8_t * pVariableHeader = *pIndex; + MQTTStatus_t status = MQTTSuccess; + uint16_t keyLength; + uint16_t valueLength; + const char * pKey; + const char * pValue; + + /*Validate the property length and decode the user property received.*/ + if( *pPropertyLength < sizeof( uint16_t ) ) + { + status = MQTTMalformedPacket; + } + else + { + keyLength = UINT16_DECODE( pVariableHeader ); + *pPropertyLength -= sizeof( uint16_t ); + + if( *pPropertyLength < keyLength ) + { + status = MQTTMalformedPacket; + } + else + { + pVariableHeader = &pVariableHeader[ sizeof( uint16_t ) ]; + pKey = ( const char * ) pVariableHeader; + *pPropertyLength -= keyLength; + pVariableHeader = &pVariableHeader[ keyLength ]; + + if( *pPropertyLength < sizeof( uint16_t ) ) + { + status = MQTTMalformedPacket; + } + else + { + valueLength = UINT16_DECODE( pVariableHeader ); + *pPropertyLength -= sizeof( uint16_t ); + pVariableHeader = &pVariableHeader[ sizeof( uint16_t ) ]; + + if( *pPropertyLength < ( size_t ) ( valueLength ) ) + { + status = MQTTMalformedPacket; + } + else + { + pValue = ( const char * ) pVariableHeader; + pVariableHeader = &pVariableHeader[ valueLength ]; + *pPropertyLength -= valueLength; + } + } + } + } + + *pIndex = pVariableHeader; + + if( status == MQTTSuccess ) + { + LogDebug( ( "Discarded additional user property with key %s and value %s. ", + pKey, + pValue ) ); + } + + return status; + } + + #endif /* if ( MQTT_USER_PROPERTY_ENABLED ) */ + + static MQTTStatus_t MQTT_GetAuthInfoSize( const MQTTAuthInfo_t * pAuthInfo, + size_t * pPropertyLength ) + { + MQTTStatus_t status = MQTTSuccess; + + /*Validate the parameters*/ + if( ( pAuthInfo->authMethodLength == 0U ) && ( pAuthInfo->authDataLength != 0U ) ) + { + status = MQTTBadParameter; + } + else if( ( pAuthInfo->authMethodLength != 0U ) && ( pAuthInfo->pAuthMethod == NULL ) ) + { + status = MQTTBadParameter; + } + else if( ( pAuthInfo->authDataLength != 0U ) && ( pAuthInfo->pAuthData == NULL ) ) + { + status = MQTTBadParameter; + } + else + { + /*Add authentication method and data if provided*/ + if( pAuthInfo->authMethodLength != 0U ) + { + *pPropertyLength += pAuthInfo->authMethodLength; + *pPropertyLength += MQTT_UTF8_LENGTH_SIZE; + + if( pAuthInfo->authDataLength != 0U ) + { + *pPropertyLength += pAuthInfo->authDataLength; + *pPropertyLength += MQTT_UTF8_LENGTH_SIZE; + } + } + } + + return status; + } + + static MQTTStatus_t MQTT_GetConnectPropertiesSize( MQTTConnectProperties_t * pConnectProperties ) + { + size_t propertyLength = 0; + MQTTStatus_t status = MQTTSuccess; + + /*Validate the arguments*/ + if( pConnectProperties->maxPacketSize == 0U ) + { + status = MQTTBadParameter; + } + else if( pConnectProperties->receiveMax == 0U ) + { + status = MQTTBadParameter; + } + else + { + /*Add the lengths of the parameters if applicable*/ + if( pConnectProperties->sessionExpiry != 0U ) + { + propertyLength += MQTT_SESSION_EXPIRY_SIZE; + } + + if( pConnectProperties->receiveMax != ( uint16_t ) UINT16_MAX ) + { + propertyLength += MQTT_RECEIVE_MAX_SIZE; + } + + if( pConnectProperties->maxPacketSize != MQTT_MAX_PACKET_SIZE ) + { + propertyLength += MQTT_MAX_PACKET_PROPERTY_SIZE; + } + + if( pConnectProperties->topicAliasMax != 0U ) + { + propertyLength += MQTT_TOPIC_ALIAS_SIZE; + } + + if( pConnectProperties->requestResponseInfo != false ) + { + propertyLength += MQTT_REQUEST_RESPONSE_SIZE; + } + + if( pConnectProperties->requestProblemInfo != true ) + { + propertyLength += MQTT_REQUEST_PROBLEM_SIZE; + } + + if( pConnectProperties->pOutgoingAuth != NULL ) + { + /*Valid authentication parameters*/ + status = MQTT_GetAuthInfoSize( pConnectProperties->pOutgoingAuth, &propertyLength ); + } + } + + #if ( MQTT_USER_PROPERTY_ENABLED ) + /*Get the length of the user properties*/ + if( ( status == MQTTSuccess ) && ( pConnectProperties->pOutgoingUserProperty != NULL ) ) + { + status = MQTT_GetUserPropertySize( pConnectProperties->pOutgoingUserProperty->userProperty, pConnectProperties->pOutgoingUserProperty->count, &propertyLength ); + } + #endif + + /*Variable length encoded values cannot have a value of more than 268435455U*/ + if( ( status == MQTTSuccess ) && ( pConnectProperties->propertyLength > MQTT_MAX_REMAINING_LENGTH ) ) + { + status = MQTTBadParameter; + } + + if( status == MQTTSuccess ) + { + pConnectProperties->propertyLength = propertyLength; + } + + return status; + } + + static MQTTStatus_t MQTT_GetPublishPropertiesSize( MQTTPublishInfo_t * pPublishProperties ) + { + size_t propertyLength = 0U; + MQTTStatus_t status = MQTTSuccess; + + /*Add the length of all the parameters which are provided*/ + /*Only applicable for will*/ + if( pPublishProperties->willDelay != 0U ) + { + propertyLength += MQTT_WILL_DELAY_SIZE; + } + + /*Only applicable for publish packet.*/ + if( pPublishProperties->topicAlias != 0U ) + { + propertyLength += 3U; + } + + if( pPublishProperties->payloadFormat != 0U ) + { + propertyLength += MQTT_PAYLOAD_FORMAT_SIZE; + } + + if( pPublishProperties->msgExpiryPresent == true ) + { + propertyLength += MQTT_MSG_EXPIRY_SIZE; + } + + if( pPublishProperties->contentTypeLength != 0U ) + { + if( pPublishProperties->pContentType == NULL ) + { + status = MQTTBadParameter; + } + else + { + propertyLength += ( pPublishProperties->contentTypeLength ); + propertyLength += MQTT_UTF8_LENGTH_SIZE; + } + } + + /*Validate if length and pointers are valid*/ + if( ( status == MQTTSuccess ) && ( pPublishProperties->responseTopicLength != 0U ) ) + { + if( pPublishProperties->pResponseTopic == NULL ) + { + status = MQTTBadParameter; + } + else + { + propertyLength += ( pPublishProperties->responseTopicLength ); + propertyLength += MQTT_UTF8_LENGTH_SIZE; + } + } + + if( ( status == MQTTSuccess ) && ( pPublishProperties->correlationLength != 0U ) ) + { + if( pPublishProperties->pCorrelationData == NULL ) + { + status = MQTTBadParameter; + } + else + { + propertyLength += ( pPublishProperties->correlationLength ); + propertyLength += MQTT_UTF8_LENGTH_SIZE; + } + } + + #if ( MQTT_USER_PROPERTY_ENABLED ) + /*Get the length of user properties*/ + if( ( status == MQTTSuccess ) && ( pPublishProperties->pUserProperty != NULL ) ) + { + status = MQTT_GetUserPropertySize( pPublishProperties->pUserProperty->userProperty, pPublishProperties->pUserProperty->count, &propertyLength ); + } + #endif + + /*Variable encoded can't have a value more than 268435455UL*/ + if( propertyLength > MQTT_MAX_REMAINING_LENGTH ) + { + status = MQTTBadParameter; + } + + if( status == MQTTSuccess ) + { + pPublishProperties->propertyLength = propertyLength; + } + + return status; + } + + + static void serializeConnectPacketV5( const MQTTConnectInfo_t * pConnectInfo, + const MQTTPublishInfo_t * pWillInfo, + const MQTTConnectProperties_t * pConnectProperties, + size_t remainingLength, + const MQTTFixedBuffer_t * pFixedBuffer ) + { + uint8_t * pIndex = NULL; + + assert( pConnectInfo != NULL ); + assert( pFixedBuffer != NULL ); + assert( pFixedBuffer->pBuffer != NULL ); + assert( pConnectProperties != NULL ); + pIndex = pFixedBuffer->pBuffer; + /* Serialize the header. */ + pIndex = MQTT_SerializeConnectFixedHeader( pIndex, + pConnectInfo, + pWillInfo, + remainingLength ); + + /* Serialize the connect Properties. */ + pIndex = MQTTV5_SerializeConnectProperties( pIndex, pConnectProperties ); + #if ( MQTT_USER_PROPERTY_ENABLED ) + if( pConnectProperties->pOutgoingUserProperty != NULL ) + { + uint32_t i = 0; + uint32_t size = pConnectProperties->pOutgoingUserProperty->count; + const MQTTUserProperty_t * pUserProperty = pConnectProperties->pOutgoingUserProperty->userProperty; + + for( ; i < size; i++ ) + { + *pIndex = MQTT_USER_PROPERTY_ID; + pIndex++; + pIndex = encodeString( pIndex, pUserProperty[ i ].pKey, pUserProperty[ i ].keyLength ); + pIndex = encodeString( pIndex, pUserProperty[ i ].pValue, pUserProperty[ i ].valueLength ); + } + } + #endif /* if ( MQTT_USER_PROPERTY_ENABLED ) */ + + if( pConnectProperties->pOutgoingAuth != NULL ) + { + /* Serialize the authentication method string. */ + *pIndex = MQTT_AUTH_METHOD_ID; + pIndex++; + pIndex = encodeString( pIndex, pConnectProperties->pOutgoingAuth->pAuthMethod, pConnectProperties->pOutgoingAuth->authMethodLength ); + + if( pConnectProperties->pOutgoingAuth->authDataLength != 0U ) + { + *pIndex = MQTT_AUTH_DATA_ID; + pIndex++; + pIndex = encodeString( pIndex, pConnectProperties->pOutgoingAuth->pAuthData, pConnectProperties->pOutgoingAuth->authDataLength ); + } + } + + /* Write the client identifier into the CONNECT packet. */ + pIndex = encodeString( pIndex, + pConnectInfo->pClientIdentifier, + pConnectInfo->clientIdentifierLength ); + + /* Serialize the will properties,topic name and message into the CONNECT packet if provided. */ + if( pWillInfo != NULL ) + { + pIndex = serializePublishProperties( pWillInfo, pIndex ); + pIndex = encodeString( pIndex, pWillInfo->pTopicName, pWillInfo->topicNameLength ); + pIndex = encodeString( pIndex, pWillInfo->pPayload, ( uint16_t ) pWillInfo->payloadLength ); + } + + /* Encode the user name if provided. */ + if( pConnectInfo->pUserName != NULL ) + { + pIndex = encodeString( pIndex, pConnectInfo->pUserName, pConnectInfo->userNameLength ); + } + + /* Encode the password if provided. */ + if( pConnectInfo->pPassword != NULL ) + { + pIndex = encodeString( pIndex, pConnectInfo->pPassword, pConnectInfo->passwordLength ); + } + + LogDebug( ( "Length of serialized CONNECT packet is %lu.", + ( ( unsigned long ) ( pIndex - pFixedBuffer->pBuffer ) ) ) ); + + /* Ensure that the difference between the end and beginning of the buffer + * is less than the buffer size. */ + assert( ( ( size_t ) ( pIndex - pFixedBuffer->pBuffer ) ) <= pFixedBuffer->size ); + } + + + static MQTTStatus_t logConnackResponseV5( uint8_t responseCode ) + { + MQTTStatus_t status = MQTTServerRefused; + + /* Log an error based on the CONNACK response code. */ + switch( responseCode ) + { + case ( uint8_t ) MQTT_REASON_UNSPECIFIED_ERR: + LogError( ( "Connection refused: Unspecified error" ) ); + break; + + case ( uint8_t ) MQTT_REASON_MALFORMED_PACKET: + LogError( ( "Connection refused: Malformed Packet." ) ); + break; + + case ( uint8_t ) MQTT_REASON_PROTOCOL_ERR: + LogError( ( "Connection refused: Protocol Error." ) ); + break; + + case ( uint8_t ) MQTT_REASON_IMPL_SPECIFIC_ERR: + LogError( ( "Connection refused: Implementation specific error." ) ); + break; + + case ( uint8_t ) MQTT_REASON_UNSUPPORTED_PROTO_VER: + LogError( ( "Connection refused: Unsupported Protocol Version." ) ); + break; + + case ( uint8_t ) MQTT_REASON_CLIENT_ID_NOT_VALID: + LogError( ( "Connection refused: Client Identifier not valid." ) ); + break; + + case ( uint8_t ) MQTT_REASON_BAD_USER_OR_PASS: + LogError( ( "Connection refused: Bad User Name or Password." ) ); + break; + + case ( uint8_t ) MQTT_REASON_NOT_AUTHORIZED: + LogError( ( "Connection refused: Not authorized." ) ); + break; + + case ( uint8_t ) MQTT_REASON_SERVER_UNAVAILABLE: + LogError( ( "Connection refused: Server unavailable." ) ); + break; + + case ( uint8_t ) MQTT_REASON_SERVER_BUSY: + LogError( ( "Connection refused: Server busy." ) ); + break; + + case ( uint8_t ) MQTT_REASON_BANNED: + LogError( ( "Connection refused: Banned." ) ); + break; + + case ( uint8_t ) MQTT_REASON_BAD_AUTH_METHOD: + LogError( ( "Connection refused: Bad authentication method." ) ); + break; + + case ( uint8_t ) MQTT_REASON_TOPIC_NAME_INVALID: + LogError( ( "Connection refused: Topic Name invalid." ) ); + break; + + case ( uint8_t ) MQTT_REASON_PACKET_TOO_LARGE: + LogError( ( "Connection refused: Packet too large ." ) ); + break; + + case ( uint8_t ) MQTT_REASON_QUOTA_EXCEEDED: + LogError( ( "Connection refused: Quota exceeded." ) ); + break; + + case ( uint8_t ) MQTT_REASON_PAYLOAD_FORMAT_INVALID: + LogError( ( "Connection refused: Payload format invalid." ) ); + break; + + case ( uint8_t ) MQTT_REASON_RETAIN_NOT_SUPPORTED: + LogError( ( "Connection refused: Retain not supported." ) ); + break; + + case ( uint8_t ) MQTT_REASON_QOS_NOT_SUPPORTED: + LogError( ( "Connection refused: QoS not supported." ) ); + break; + + case ( uint8_t ) MQTT_REASON_USE_ANOTHER_SERVER: + LogError( ( "Connection refused: Use another server." ) ); + break; + + case ( uint8_t ) MQTT_REASON_SERVER_MOVED: + LogError( ( "Connection refused: Server moved." ) ); + break; + + case ( uint8_t ) MQTT_REASON_CON_RATE_EXCEED: + LogError( ( "Connection refused: Connection rate exceeded." ) ); + break; + + default: + status = MQTTProtocolError; + break; + } + + return status; + } + + static MQTTStatus_t validateConnackParams( const MQTTPacketInfo_t * pIncomingPacket, + bool * pSessionPresent ) + { + MQTTStatus_t status = MQTTSuccess; + + /*Validate the arguments*/ + if( pIncomingPacket == NULL ) + { + LogError( ( "pIncomingPacket cannot be NULL." ) ); + status = MQTTBadParameter; + } + else if( pSessionPresent == NULL ) + { + LogError( ( "pSessionPresent cannot be NULL for CONNACK packet." ) ); + status = MQTTBadParameter; + } + else if( pIncomingPacket->pRemainingData == NULL ) + { + LogError( ( "Remaining data of incoming packet is NULL." ) ); + status = MQTTBadParameter; + } + else if( pIncomingPacket->type != MQTT_PACKET_TYPE_CONNACK ) + { + LogError( ( "Packet type is invalid." ) ); + status = MQTTBadParameter; + } + else + { + const uint8_t * pRemainingData = NULL; + pRemainingData = pIncomingPacket->pRemainingData; + + if( ( pRemainingData[ 0 ] | 0x01U ) != 0x01U ) + { + LogError( ( "Reserved bits in CONNACK incorrect." ) ); + + status = MQTTBadResponse; + } + else + { + /* Determine if the "Session Present" bit is set. This is the lowest bit of + * the third byte in CONNACK. */ + if( ( pRemainingData[ 0 ] & MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) == MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) + { + LogDebug( ( "CONNACK session present bit set." ) ); + *pSessionPresent = true; + + /* MQTT 5 specifies that the fourth byte in CONNACK must be 0 if the + * "Session Present" bit is set. */ + if( pRemainingData[ 1 ] != 0U ) + { + LogError( ( "Session Present bit is set, but connect return code in CONNACK is %u (nonzero).", + ( unsigned int ) pRemainingData[ 1 ] ) ); + status = MQTTBadResponse; + } + } + else + { + LogDebug( ( "CONNACK session present bit not set." ) ); + *pSessionPresent = false; + } + } + + if( status == MQTTSuccess ) + { + if( pRemainingData[ 1 ] != 0u ) + { + status = logConnackResponseV5( pRemainingData[ 1 ] ); + } + } + } + + return status; + } + + static MQTTStatus_t decodeVariableLength( const uint8_t * pBuffer, + size_t * pLength ) + { + size_t remainingLength = 0; + size_t multiplier = 1; + size_t bytesDecoded = 0; + size_t expectedSize = 0; + uint8_t encodedByte = 0; + MQTTStatus_t status = MQTTSuccess; + + /* This algorithm is copied from the MQTT v3.1.1 spec. */ + do + { + if( multiplier > 2097152U ) /* 128 ^ 3 */ + { + remainingLength = MQTT_REMAINING_LENGTH_INVALID; + + LogError( ( "Invalid remaining length in the packet.\n" ) ); + + status = MQTTBadResponse; + } + else + { + /* Get the next byte. It is at the next position after the bytes + * decoded till now since the header of one byte was read before. */ + encodedByte = pBuffer[ bytesDecoded ]; + remainingLength += ( ( size_t ) encodedByte & 0x7FU ) * multiplier; + multiplier *= 128U; + bytesDecoded++; + } + + /* If the response is incorrect then + * break out of the loop. */ + if( remainingLength == MQTT_REMAINING_LENGTH_INVALID ) + { + break; + } + } while( ( encodedByte & 0x80U ) != 0U ); + + if( status == MQTTSuccess ) + { + /* Check that the decoded remaining length conforms to the MQTT specification. */ + expectedSize = remainingLengthEncodedSize( remainingLength ); + + if( bytesDecoded != expectedSize ) + { + LogError( ( "Expected and actual length of decoded bytes do not match.\n" ) ); + status = MQTTBadResponse; + } + else + { + *pLength = remainingLength; + } + } + + return status; + } + + static MQTTStatus_t decodeuint32_t( uint32_t * pProperty, + size_t * pPropertyLength, + bool * pUsed, + const uint8_t ** pIndex ) + { + const uint8_t * pVariableHeader = *pIndex; + MQTTStatus_t status = MQTTSuccess; + + /*Protocol error to include the same property twice.*/ + if( *pUsed == true ) + { + status = MQTTProtocolError; + } + /*Validate the length and decode.*/ + else if( *pPropertyLength < sizeof( uint32_t ) ) + { + status = MQTTMalformedPacket; + } + else + { + *pProperty = UINT32_DECODE( pVariableHeader ); + pVariableHeader = &pVariableHeader[ sizeof( uint32_t ) ]; + *pUsed = true; + *pPropertyLength -= sizeof( uint32_t ); + } + + *pIndex = pVariableHeader; + return status; + } + + static MQTTStatus_t decodeuint16_t( uint16_t * pProperty, + size_t * pPropertyLength, + bool * pUsed, + const uint8_t ** pIndex ) + { + const uint8_t * pVariableHeader = *pIndex; + MQTTStatus_t status = MQTTSuccess; + + /*Protocol error to include the same property twice.*/ + + if( *pUsed == true ) + { + status = MQTTProtocolError; + } + /*Validate the length and decode.*/ + + else if( *pPropertyLength < sizeof( uint16_t ) ) + { + status = MQTTMalformedPacket; + } + else + { + *pProperty = UINT16_DECODE( pVariableHeader ); + pVariableHeader = &pVariableHeader[ sizeof( uint16_t ) ]; + *pUsed = true; + *pPropertyLength -= sizeof( uint16_t ); + } + + *pIndex = pVariableHeader; + return status; + } + + static MQTTStatus_t decodeuint8_t( uint8_t * pProperty, + size_t * pPropertyLength, + bool * pUsed, + const uint8_t ** pIndex ) + { + const uint8_t * pVariableHeader = *pIndex; + MQTTStatus_t status = MQTTSuccess; + + /*Protocol error to include the same property twice.*/ + + if( *pUsed == true ) + { + status = MQTTProtocolError; + } + /*Validate the length and decode.*/ + + else if( *pPropertyLength < sizeof( uint8_t ) ) + { + status = MQTTMalformedPacket; + } + else + { + *pProperty = *pVariableHeader; + pVariableHeader = &pVariableHeader[ sizeof( uint8_t ) ]; + *pUsed = true; + *pPropertyLength -= sizeof( uint8_t ); + + if( *pProperty > 1U ) + { + status = MQTTProtocolError; + } + } + + *pIndex = pVariableHeader; + return status; + } + + static MQTTStatus_t decodeutf_8( const char ** pProperty, + uint16_t * pLength, + size_t * pPropertyLength, + bool * pUsed, + const uint8_t ** pIndex ) + { + const uint8_t * pVariableHeader = *pIndex; + MQTTStatus_t status = MQTTSuccess; + + /*Protocol error to include the same property twice.*/ + + if( *pUsed == true ) + { + status = MQTTProtocolError; + } + /*Validate the length and decode.*/ + + else if( *pPropertyLength < sizeof( uint16_t ) ) + { + status = MQTTMalformedPacket; + } + else + { + *pLength = UINT16_DECODE( pVariableHeader ); + pVariableHeader = &pVariableHeader[ sizeof( uint16_t ) ]; + *pPropertyLength -= sizeof( uint16_t ); + + if( *pPropertyLength < *pLength ) + { + status = MQTTMalformedPacket; + } + else + { + *pProperty = ( const char * ) pVariableHeader; + pVariableHeader = &pVariableHeader[ *pLength ]; + *pPropertyLength -= *pLength; + *pUsed = true; + } + } + + *pIndex = pVariableHeader; + return status; + } + + static MQTTStatus_t decodeAuthInfo( const MQTTConnectProperties_t * pConnackProperties, + bool * pAuthMethod, + bool * pAuthData, + size_t * pPropertyLength, + const uint8_t ** pIndex, + const uint8_t id ) + { + MQTTStatus_t status = MQTTSuccess; + + if( pConnackProperties->pOutgoingAuth == NULL ) + { + status = MQTTProtocolError; + } + else if( id == ( uint8_t ) MQTT_AUTH_METHOD_ID ) + { + /*Decode the authenticaton method */ + status = decodeutf_8( &pConnackProperties->pIncomingAuth->pAuthMethod, &pConnackProperties->pIncomingAuth->authMethodLength, pPropertyLength, pAuthMethod, pIndex ); + } + else + { + /*Decode the authentication data */ + status = decodeutf_8( &pConnackProperties->pIncomingAuth->pAuthData, &pConnackProperties->pIncomingAuth->authDataLength, pPropertyLength, pAuthData, pIndex ); + } + + return status; + } + + static MQTTStatus_t deserializeConnackV5( MQTTConnectProperties_t * pConnackProperties, + size_t length, + const uint8_t * const * pIndex ) + { + MQTTStatus_t status = MQTTSuccess; + const uint8_t * pVariableHeader = *pIndex; + size_t propertyLength = length; + bool sessionExpiry = false; + bool serverReceiveMax = false; + bool maxQos = false; + bool retain = false; + bool maxPacket = false; + bool clientId = false; + bool topicAlias = false; + bool reasonString = false; + bool wildcard = false; + bool subId = false; + bool sharedsub = false; + bool keepAlive = false; + bool responseInfo = false; + bool serverRef = false; + bool authMethod = false; + bool authData = false; + + pVariableHeader = &pVariableHeader[ remainingLengthEncodedSize( propertyLength ) ]; + + /*Decode all the properties received, validate and store them in pConnackProperties.*/ + while( ( propertyLength > 0U ) && ( status == MQTTSuccess ) ) + { + uint8_t packetId = *pVariableHeader; + pVariableHeader = &pVariableHeader[ 1 ]; + propertyLength -= sizeof( uint8_t ); + + switch( packetId ) + { + case MQTT_SESSION_EXPIRY_ID: + status = decodeuint32_t( &pConnackProperties->sessionExpiry, &propertyLength, &sessionExpiry, &pVariableHeader ); + break; + + case MQTT_RECEIVE_MAX_ID: + status = decodeuint16_t( &pConnackProperties->serverReceiveMax, &propertyLength, &serverReceiveMax, &pVariableHeader ); + break; + + case MQTT_MAX_QOS_ID: + status = decodeuint8_t( &pConnackProperties->serverMaxQos, &propertyLength, &maxQos, &pVariableHeader ); + break; + + case MQTT_RETAIN_AVAILABLE_ID: + status = decodeuint8_t( &pConnackProperties->retainAvailable, &propertyLength, &retain, &pVariableHeader ); + break; + + case MQTT_MAX_PACKET_SIZE_ID: + status = decodeuint32_t( &pConnackProperties->serverMaxPacketSize, &propertyLength, &maxPacket, &pVariableHeader ); + break; + + case MQTT_ASSIGNED_CLIENT_ID: + status = decodeutf_8( &pConnackProperties->pClientIdentifier, &pConnackProperties->clientIdLength, &propertyLength, &clientId, &pVariableHeader ); + break; + + case MQTT_TOPIC_ALIAS_MAX_ID: + status = decodeuint16_t( &pConnackProperties->serverTopicAliasMax, &propertyLength, &topicAlias, &pVariableHeader ); + break; + + case MQTT_REASON_STRING_ID: + status = decodeutf_8( &pConnackProperties->pReasonString, &pConnackProperties->reasonStringLength, &propertyLength, &reasonString, &pVariableHeader ); + break; + + case MQTT_USER_PROPERTY_ID: + #if ( MQTT_USER_PROPERTY_ENABLED ) + status = decodeutf_8pair( pConnackProperties->pIncomingUserProperty, &pConnackProperties->pIncomingUserProperty->count, &propertyLength, &pVariableHeader ); + #else + status = decodeAndDiscard( &propertyLength, &pVariableHeader ); + #endif + break; + + case MQTT_WILDCARD_ID: + status = decodeuint8_t( &pConnackProperties->isWildcardAvaiable, &propertyLength, &wildcard, &pVariableHeader ); + break; + + case MQTT_SUB_AVAILABLE_ID: + status = decodeuint8_t( &pConnackProperties->subscriptionId, &propertyLength, &subId, &pVariableHeader ); + break; + + case MQTT_SHARED_SUB_ID: + status = decodeuint8_t( &pConnackProperties->isSharedAvailable, &propertyLength, &sharedsub, &pVariableHeader ); + break; + + case MQTT_SERVER_KEEP_ALIVE_ID: + status = decodeuint16_t( &pConnackProperties->serverKeepAlive, &propertyLength, &keepAlive, &pVariableHeader ); + break; + + case MQTT_RESPONSE_INFO_ID: + status = decodeutf_8( &pConnackProperties->pResponseInfo, &pConnackProperties->responseInfoLength, &propertyLength, &responseInfo, &pVariableHeader ); + break; + + case MQTT_SERVER_REF_ID: + status = decodeutf_8( &pConnackProperties->pServerRef, &pConnackProperties->serverRefLength, &propertyLength, &serverRef, &pVariableHeader ); + break; + + case MQTT_AUTH_METHOD_ID: + case MQTT_AUTH_DATA_ID: + + status = decodeAuthInfo( pConnackProperties, &authMethod, &authData, &propertyLength, &pVariableHeader, packetId ); + break; + + /*Protocol error to include any other property id.*/ + default: + status = MQTTProtocolError; + break; + } + } + + if( status == MQTTSuccess ) + { + /*Receive max cannot be 0.*/ + if( ( serverReceiveMax == true ) && ( pConnackProperties->serverReceiveMax == 0U ) ) + { + status = MQTTProtocolError; + } + + /*Maximum packet size cannot be 0.*/ + else if( ( maxPacket == true ) && ( pConnackProperties->serverMaxPacketSize == 0U ) ) + { + status = MQTTProtocolError; + } + /*Protocol error to send response information if the client has not requested it.*/ + else if( ( responseInfo == true ) && ( pConnackProperties->requestResponseInfo == false ) ) + { + status = MQTTProtocolError; + } + + else + { + /* MISRA Empty body */ + } + } + + return status; + } + + static MQTTStatus_t calculatePublishPacketSizeV5( MQTTPublishInfo_t * pPublishInfo, + size_t * pRemainingLength, + size_t * pPacketSize, + uint32_t maxPacketSize ) + { + MQTTStatus_t status = MQTTSuccess; + size_t packetSize = 0, payloadLimit = 0; + + assert( pPublishInfo != NULL ); + assert( pRemainingLength != NULL ); + assert( pPacketSize != NULL ); + + /* The variable header of a PUBLISH packet always contains the topic name. + * The first 2 bytes of UTF-8 string contains length of the string. + */ + packetSize += 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 > MQTTQoS0 ) + { + packetSize += sizeof( uint16_t ); + } + + /*Calculate the size of the publish properties*/ + status = MQTT_GetPublishPropertiesSize( pPublishInfo ); + + if( status == MQTTSuccess ) + { + packetSize += pPublishInfo->propertyLength; + packetSize += remainingLengthEncodedSize( pPublishInfo->propertyLength ); + + /* 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 - packetSize - 1U; + + /* Ensure that the given payload fits within the calculated limit. */ + if( pPublishInfo->payloadLength > payloadLimit ) + { + LogError( ( "PUBLISH payload length of %lu cannot exceed " + "%lu so as not to exceed the maximum " + "remaining length of MQTT 3.1.1 packet( %lu ).", + ( unsigned long ) pPublishInfo->payloadLength, + ( unsigned long ) payloadLimit, + MQTT_MAX_REMAINING_LENGTH ) ); + status = MQTTBadParameter; + } + } + + if( status == MQTTSuccess ) + { + /* Add the length of the PUBLISH payload. At this point, the "Remaining length" + * has been calculated. */ + packetSize += pPublishInfo->payloadLength; + + /* Now that the "Remaining length" is known, recalculate the payload limit + * based on the size of its encoding. */ + payloadLimit -= remainingLengthEncodedSize( packetSize ); + + /* Check that the given payload fits within the size allowed by MQTT spec. */ + if( pPublishInfo->payloadLength > payloadLimit ) + { + LogError( ( "PUBLISH payload length of %lu cannot exceed " + "%lu so as not to exceed the maximum " + "remaining length of MQTT 3.1.1 packet( %lu ).", + ( unsigned long ) pPublishInfo->payloadLength, + ( unsigned long ) payloadLimit, + MQTT_MAX_REMAINING_LENGTH ) ); + status = MQTTBadParameter; + } + else + { + /* Set the "Remaining length" output parameter and calculate the full + * size of the PUBLISH packet. */ + *pRemainingLength = packetSize; + + packetSize += 1U + remainingLengthEncodedSize( packetSize ); + + if( packetSize > maxPacketSize ) + { + LogError( ( "Packet size is greater than the allowed maximum packet size." ) ); + status = MQTTBadParameter; + } + + *pPacketSize = packetSize; + } + } + + LogDebug( ( "PUBLISH packet remaining length=%lu and packet size=%lu.", + ( unsigned long ) *pRemainingLength, + ( unsigned long ) *pPacketSize ) ); + + return status; + } + + static void serializePubAckPacketV5( const MQTTAckInfo_t * pAckInfo, + uint8_t packetType, + uint16_t packetId, + size_t remainingLength, + const MQTTFixedBuffer_t * pFixedBuffer ) + { + uint8_t * pIndex = NULL; + + assert( pAckInfo != NULL ); + assert( pFixedBuffer != NULL ); + assert( pFixedBuffer->pBuffer != NULL ); + pIndex = pFixedBuffer->pBuffer; + /* Serialize the header including reason code and property length */ + pIndex = MQTTV5_SerializeAckFixed( pIndex, + packetType, + packetId, + remainingLength, + pAckInfo->propertyLength ); + + /*Serialize the reason string if provided.*/ + if( pAckInfo->reasonStringLength != 0U ) + { + *pIndex = MQTT_REASON_STRING_ID; + pIndex++; + pIndex = encodeString( pIndex, pAckInfo->pReasonString, pAckInfo->reasonStringLength ); + } + + #if ( MQTT_USER_PROPERTY_ENABLED ) + /*Serialize the user properties if provided.*/ + if( pAckInfo->pUserProperty != NULL ) + { + uint32_t i = 0; + uint32_t size = pAckInfo->pUserProperty->count; + const MQTTUserProperty_t * pUserProperty = pAckInfo->pUserProperty->userProperty; + + for( ; i < size; i++ ) + { + *pIndex = MQTT_USER_PROPERTY_ID; + pIndex++; + pIndex = encodeString( pIndex, pUserProperty[ i ].pKey, pUserProperty[ i ].keyLength ); + pIndex = encodeString( pIndex, pUserProperty[ i ].pValue, pUserProperty[ i ].valueLength ); + } + } + #endif /* if ( MQTT_USER_PROPERTY_ENABLED ) */ + + /* Ensure that the difference between the end and beginning of the buffer + * is less than the buffer size. */ + assert( ( ( size_t ) ( pIndex - pFixedBuffer->pBuffer ) ) <= pFixedBuffer->size ); + } + +/* coverity[misra_c_2012_rule_2_7_violation] */ + static MQTTStatus_t logAckResponseV5( uint8_t reasonCode, + uint16_t packetIdentifier ) + { + MQTTStatus_t status = MQTTServerRefused; + + switch( reasonCode ) + { + case MQTT_REASON_SUCCESS: + status = MQTTSuccess; + break; + + case MQTT_REASON_NO_MATCHING_SUBSCRIBERS: + LogDebug( ( "Publish accepted with packet id %hu: No matching subscribers.", + ( unsigned short ) packetIdentifier ) ); + status = MQTTSuccess; + break; + + case MQTT_REASON_UNSPECIFIED_ERR: + LogError( ( "Publish refused with packet id %hu: Connection rate exceeded.", + ( unsigned short ) packetIdentifier ) ); + break; + + case MQTT_REASON_IMPL_SPECIFIC_ERR: + LogError( ( "Publish refused with packet id %hu: The PUBLISH is valid but the receiver is not willing to accept it.", + ( unsigned short ) packetIdentifier ) ); + break; + + case MQTT_REASON_NOT_AUTHORIZED: + LogError( ( "Publish refused with packet id %hu: The PUBLISH is not authorized.", + ( unsigned short ) packetIdentifier ) ); + break; + + case MQTT_REASON_TOPIC_NAME_INVALID: + LogError( ( "Publish refused with packet id %hu: Topic Name not accepted.", + ( unsigned short ) packetIdentifier ) ); + break; + + case MQTT_REASON_PACKET_ID_IN_USE: + LogError( ( "Publish refused with packet id %hu: The Packet Identifier is already in use. ", + ( unsigned short ) packetIdentifier ) ); + break; + + case MQTT_REASON_QUOTA_EXCEEDED: + LogError( ( "Publish refused with packet id %hu: Quota exceeded.", + ( unsigned short ) packetIdentifier ) ); + break; + + case MQTT_REASON_PAYLOAD_FORMAT_INVALID: + LogError( ( "Publish refused with packet id %hu: Payload format indicator is invalid.", + ( unsigned short ) packetIdentifier ) ); + break; + + default: + status = MQTTProtocolError; + break; + } + + return status; + } + +/* coverity[misra_c_2012_rule_2_7_violation] */ + static MQTTStatus_t logSimpleAckResponseV5( uint8_t reasonCode, + uint16_t packetIdentifier ) + { + MQTTStatus_t status = MQTTServerRefused; + + switch( reasonCode ) + { + case MQTT_REASON_SUCCESS: + status = MQTTSuccess; + break; + + case MQTT_REASON_PACKET_ID_NOT_FOUND: + LogError( ( "Publish refused with packet id %hu: Packet identifier invalid.", + ( unsigned short ) packetIdentifier ) ); + status = MQTTServerRefused; + break; + + default: + status = MQTTProtocolError; + break; + } + + return status; + } + + + static MQTTStatus_t decodeAckProperties( MQTTAckInfo_t * pAckInfo, + const uint8_t * pIndex, + size_t remainingLength ) + { + size_t propertyLength = 0U; + MQTTStatus_t status = MQTTSuccess; + const uint8_t * pLocalIndex = pIndex; + + /*Decode the property length*/ + status = decodeVariableLength( pLocalIndex, &propertyLength ); + + if( status == MQTTSuccess ) + { + pLocalIndex = &pLocalIndex[ remainingLengthEncodedSize( propertyLength ) ]; + + /*Validate the remaining length.*/ + if( remainingLength != ( propertyLength + remainingLengthEncodedSize( propertyLength ) + 3U ) ) + { + status = MQTTMalformedPacket; + } + } + + if( status == MQTTSuccess ) + { + while( ( propertyLength > 0U ) && ( status == MQTTSuccess ) ) + { + /*Decode the property id.*/ + uint8_t packetId = *pLocalIndex; + bool reasonString = false; + pLocalIndex = &pLocalIndex[ 1 ]; + propertyLength -= sizeof( uint8_t ); + + switch( packetId ) + { + case MQTT_REASON_STRING_ID: + status = decodeutf_8( &pAckInfo->pReasonString, &pAckInfo->reasonStringLength, &propertyLength, &reasonString, &pLocalIndex ); + break; + + case MQTT_USER_PROPERTY_ID: + #if ( MQTT_USER_PROPERTY_ENABLED ) + status = decodeutf_8pair( pAckInfo->pUserProperty, &pAckInfo->pUserProperty->count, &propertyLength, &pLocalIndex ); + #else + status = decodeAndDiscard( &propertyLength, &pLocalIndex ); + #endif + break; + + default: + status = MQTTProtocolError; + break; + } + } + } + + return status; + } + + static MQTTStatus_t deserializeSimpleAckV5( const MQTTPacketInfo_t * pAck, + uint16_t * pPacketIdentifier, + MQTTAckInfo_t * pAckInfo, + bool requestProblem ) + { + MQTTStatus_t status = MQTTSuccess; + const uint8_t * pIndex = pAck->pRemainingData; + + if( pAck->remainingLength < 2U ) + { + status = MQTTMalformedPacket; + } + + if( status == MQTTSuccess ) + { + /* Extract the packet identifier (third and fourth bytes) from ACK. */ + *pPacketIdentifier = UINT16_DECODE( pIndex ); + pIndex = &pIndex[ 2 ]; + + LogDebug( ( "Packet identifier %hu.", + ( unsigned short ) *pPacketIdentifier ) ); + + /* Packet identifier cannot be 0. */ + if( *pPacketIdentifier == 0U ) + { + LogError( ( "Packet identifier cannot be 0." ) ); + status = MQTTBadResponse; + } + } + + /* If reason code is success, server can choose to not send the reason code.*/ + if( ( status == MQTTSuccess ) && ( pAck->remainingLength > 2U ) ) + { + pAckInfo->reasonCode = *pIndex; + pIndex++; + } + + if( ( pAck->remainingLength > 4U ) ) + { + /*Protocol error to send user property and reason string if client has set request problem to false.*/ + if( requestProblem == false ) + { + status = MQTTProtocolError; + } + + #if ( MQTT_USER_PROPERTY_ENABLED ) + else if( pAckInfo->pUserProperty == NULL ) + { + status = MQTTBadParameter; + } + #endif + else + { + status = decodeAckProperties( pAckInfo, pIndex, pAck->remainingLength ); + } + } + + return status; + } + + static uint8_t * serializePublishProperties( const MQTTPublishInfo_t * pPublishInfo, + uint8_t * pIndex ) + { + uint8_t * pIndexLocal = pIndex; + + pIndexLocal = MQTT_SerializePublishProperties( pPublishInfo, pIndexLocal ); + + /*Serialize the provided will properties which has variable length.*/ + if( pPublishInfo->contentTypeLength != 0U ) + { + *pIndexLocal = MQTT_CONTENT_TYPE_ID; + pIndexLocal++; + pIndexLocal = encodeString( pIndexLocal, pPublishInfo->pContentType, pPublishInfo->contentTypeLength ); + } + + if( pPublishInfo->responseTopicLength != 0U ) + { + *pIndexLocal = MQTT_RESPONSE_TOPIC_ID; + pIndexLocal++; + pIndexLocal = encodeString( pIndexLocal, pPublishInfo->pResponseTopic, pPublishInfo->responseTopicLength ); + } + + if( pPublishInfo->correlationLength != 0U ) + { + *pIndexLocal = MQTT_CORRELATION_DATA_ID; + pIndexLocal++; + pIndexLocal = encodeString( pIndexLocal, pPublishInfo->pCorrelationData, pPublishInfo->correlationLength ); + } + + #if ( MQTT_USER_PROPERTY_ENABLED ) + if( pPublishInfo->pUserProperty != NULL ) + { + uint32_t i = 0; + uint32_t size = pPublishInfo->pUserProperty->count; + const MQTTUserProperty_t * pUserProperty = pPublishInfo->pUserProperty->userProperty; + + for( ; i < size; i++ ) + { + *pIndexLocal = MQTT_USER_PROPERTY_ID; + pIndexLocal++; + pIndexLocal = encodeString( pIndexLocal, pUserProperty[ i ].pKey, pUserProperty[ i ].keyLength ); + pIndexLocal = encodeString( pIndexLocal, pUserProperty[ i ].pValue, pUserProperty[ i ].valueLength ); + } + } + #endif /* if ( MQTT_USER_PROPERTY_ENABLED ) */ + return pIndexLocal; + } + + static MQTTStatus_t validateDisconnectResponseV5( uint8_t reasonCode, + bool incoming ) + { + MQTTStatus_t status; + + /*Validate the reason code.*/ + switch( reasonCode ) + { + case MQTT_REASON_SEND_WILL: + + if( incoming == true ) + { + status = MQTTProtocolError; + } + else + { + status = MQTTSuccess; + } + + break; + + case MQTT_REASON_SUCCESS: + case MQTT_REASON_UNSPECIFIED_ERR: + case MQTT_REASON_MALFORMED_PACKET: + case MQTT_REASON_PROTOCOL_ERR: + case MQTT_REASON_IMPL_SPECIFIC_ERR: + case MQTT_REASON_TOPIC_NAME_INVALID: + case MQTT_REASON_RX_MAX_EXCEEDED: + case MQTT_REASON_TOPIC_ALIAS_INVALID: + case MQTT_REASON_PACKET_TOO_LARGE: + case MQTT_REASON_MSG_RATE_TOO_HIGH: + case MQTT_REASON_QUOTA_EXCEEDED: + case MQTT_REASON_ADMIN_ACTION: + case MQTT_REASON_PAYLOAD_FORMAT_INVALID: + status = MQTTSuccess; + break; + + case MQTT_REASON_NOT_AUTHORIZED: + case MQTT_REASON_SERVER_BUSY: + case MQTT_REASON_SERVER_SHUTTING_DOWN: + case MQTT_REASON_KEEP_ALIVE_TIMEOUT: + case MQTT_REASON_SESSION_TAKEN_OVER: + case MQTT_REASON_TOPIC_FILTER_INVALID: + case MQTT_REASON_RETAIN_NOT_SUPPORTED: + case MQTT_REASON_QOS_NOT_SUPPORTED: + case MQTT_REASON_USE_ANOTHER_SERVER: + case MQTT_REASON_SERVER_MOVED: + case MQTT_REASON_MAX_CON_TIME: + case MQTT_REASON_SS_NOT_SUPPORTED: + case MQTT_REASON_WILDCARD_SUB_NOT_SUP: + + if( incoming == true ) + { + status = MQTTSuccess; + } + else + { + status = MQTTBadParameter; + } + + break; + + default: + status = MQTTProtocolError; + break; + } + + return status; + } + +#endif /* if ( MQTT_VERSION_5_ENABLED ) */ +/*-----------------------------------------------------------*/ + +static size_t remainingLengthEncodedSize( size_t length ) +{ + size_t encodedSize; + + /* 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 < 128U ) + { + encodedSize = 1U; + } + /* 2 bytes are needed to encode lengths between 128 and 16,383. */ + else if( length < 16384U ) + { + encodedSize = 2U; + } + /* 3 bytes are needed to encode lengths between 16,384 and 2,097,151. */ + else if( length < 2097152U ) + { + encodedSize = 3U; + } + /* 4 bytes are needed to encode lengths between 2,097,152 and 268,435,455. */ + else + { + encodedSize = 4U; + } + + LogDebug( ( "Encoded size for length %lu is %lu bytes.", + ( unsigned long ) length, + ( unsigned long ) encodedSize ) ); + + return encodedSize; +} + +/*-----------------------------------------------------------*/ + +static uint8_t * encodeRemainingLength( uint8_t * pDestination, + size_t length ) +{ + uint8_t lengthByte; + uint8_t * pLengthEnd = NULL; + size_t remainingLength = length; + + assert( pDestination != NULL ); + + pLengthEnd = pDestination; + + /* This algorithm is copied from the MQTT v3.1.1 spec. */ + do + { + lengthByte = ( uint8_t ) ( remainingLength % 128U ); + remainingLength = remainingLength / 128U; + + /* Set the high bit of this byte, indicating that there's more data. */ + if( remainingLength > 0U ) + { + UINT8_SET_BIT( lengthByte, 7 ); + } + + /* Output a single encoded byte. */ + *pLengthEnd = lengthByte; + pLengthEnd++; + } while( remainingLength > 0U ); + + return pLengthEnd; +} + +/*-----------------------------------------------------------*/ + +static uint8_t * encodeString( uint8_t * pDestination, + const char * pSource, + uint16_t sourceLength ) +{ + uint8_t * pBuffer = NULL; + + /* Typecast const char * typed source buffer to const uint8_t *. + * This is to use same type buffers in memcpy. */ + const uint8_t * pSourceBuffer = ( const uint8_t * ) pSource; + + assert( pDestination != NULL ); + + pBuffer = pDestination; + + /* The first byte of a UTF-8 string is the high byte of the string length. */ + *pBuffer = UINT16_HIGH_BYTE( sourceLength ); + pBuffer++; + + /* The second byte of a UTF-8 string is the low byte of the string length. */ + *pBuffer = UINT16_LOW_BYTE( sourceLength ); + pBuffer++; + + /* Copy the string into pBuffer. */ + if( pSourceBuffer != NULL ) + { + ( void ) memcpy( pBuffer, pSourceBuffer, sourceLength ); + } + + /* Return the pointer to the end of the encoded string. */ + pBuffer = &pBuffer[ sourceLength ]; + + return pBuffer; +} + +/*-----------------------------------------------------------*/ + +static bool calculatePublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, + size_t * pRemainingLength, + size_t * pPacketSize ) +{ + bool status = true; + size_t packetSize = 0, payloadLimit = 0; + + assert( pPublishInfo != NULL ); + assert( pRemainingLength != NULL ); + assert( pPacketSize != NULL ); + + /* The variable header of a PUBLISH packet always contains the topic name. + * The first 2 bytes of UTF-8 string contains length of the string. + */ + packetSize += 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 > MQTTQoS0 ) + { + packetSize += sizeof( uint16_t ); + } + + /* 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 - packetSize - 1U; + + /* Ensure that the given payload fits within the calculated limit. */ + if( pPublishInfo->payloadLength > payloadLimit ) + { + LogError( ( "PUBLISH payload length of %lu cannot exceed " + "%lu so as not to exceed the maximum " + "remaining length of MQTT 3.1.1 packet( %lu ).", + ( unsigned long ) pPublishInfo->payloadLength, + ( unsigned long ) payloadLimit, + MQTT_MAX_REMAINING_LENGTH ) ); + status = false; + } + else + { + /* Add the length of the PUBLISH payload. At this point, the "Remaining length" + * has been calculated. */ + packetSize += pPublishInfo->payloadLength; + + /* Now that the "Remaining length" is known, recalculate the payload limit + * based on the size of its encoding. */ + payloadLimit -= remainingLengthEncodedSize( packetSize ); + + /* Check that the given payload fits within the size allowed by MQTT spec. */ + if( pPublishInfo->payloadLength > payloadLimit ) + { + LogError( ( "PUBLISH payload length of %lu cannot exceed " + "%lu so as not to exceed the maximum " + "remaining length of MQTT 3.1.1 packet( %lu ).", + ( unsigned long ) pPublishInfo->payloadLength, + ( unsigned long ) payloadLimit, + MQTT_MAX_REMAINING_LENGTH ) ); + status = false; + } + else + { + /* Set the "Remaining length" output parameter and calculate the full + * size of the PUBLISH packet. */ + *pRemainingLength = packetSize; + + packetSize += 1U + remainingLengthEncodedSize( packetSize ); + *pPacketSize = packetSize; + } + } + + LogDebug( ( "PUBLISH packet remaining length=%lu and packet size=%lu.", + ( unsigned long ) *pRemainingLength, + ( unsigned long ) *pPacketSize ) ); + return status; +} + +/*-----------------------------------------------------------*/ + +MQTTStatus_t MQTT_SerializePublishHeaderWithoutTopic( const MQTTPublishInfo_t * pPublishInfo, + size_t remainingLength, + uint8_t * pBuffer, + size_t * headerSize ) +{ + size_t headerLength; + uint8_t * pIndex; + MQTTStatus_t status = MQTTSuccess; + + /* The first byte of a PUBLISH packet contains the packet type and flags. */ + uint8_t publishFlags = MQTT_PACKET_TYPE_PUBLISH; + + /* Get the start address of the buffer. */ + pIndex = pBuffer; + + /* Length of serialized packet = First byte * + Length of encoded remaining length * + Encoded topic length. */ headerLength = 1U + remainingLengthEncodedSize( remainingLength ) + 2U; @@ -772,6 +3091,10 @@ static void serializePublishCommon( const MQTTPublishInfo_t * pPublishInfo, pIndex = &pIndex[ 2U ]; } + #if ( MQTT_VERSION_5_ENABLED ) + pIndex = serializePublishProperties( pPublishInfo, pIndex ); + #endif + /* The payload is placed after the packet identifier. * Payload is copied over only if required by the flag serializePayload. * This will help reduce an unnecessary copy of the payload into the buffer. @@ -948,6 +3271,14 @@ static bool incomingPacketValid( uint8_t packetType ) break; + case MQTT_PACKET_TYPE_DISCONNECT: + #if ( MQTT_VERSION_5_ENABLED ) + status = true; + #else + status = false; + #endif + break; + /* Any other packet type is invalid. */ default: LogWarn( ( "Incoming packet invalid: Packet type=%u.", @@ -1114,8 +3445,7 @@ static MQTTStatus_t deserializeConnack( const MQTTPacketInfo_t * pConnack, { /* Determine if the "Session Present" bit is set. This is the lowest bit of * the third byte in CONNACK. */ - if( ( pRemainingData[ 0 ] & MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) - == MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) + if( ( pRemainingData[ 0 ] & MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) == MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) { LogDebug( ( "CONNACK session present bit set." ) ); *pSessionPresent = true; @@ -1383,8 +3713,7 @@ static MQTTStatus_t validateSubscriptionSerializeParams( const MQTTSubscribeInfo /* The serialized packet size = First byte * + length of encoded size of remaining length * + remaining length. */ - packetSize = 1U + remainingLengthEncodedSize( remainingLength ) - + remainingLength; + packetSize = 1U + remainingLengthEncodedSize( remainingLength ) + remainingLength; if( packetSize > pFixedBuffer->size ) { @@ -1572,7 +3901,11 @@ uint8_t * MQTT_SerializeConnectFixedHeader( uint8_t * pIndex, pIndexLocal = encodeString( pIndexLocal, "MQTT", 4 ); /* The MQTT protocol version is the second field of the variable header. */ - *pIndexLocal = MQTT_VERSION_3_1_1; + #if ( MQTT_VERSION_5_ENABLED ) + *pIndexLocal = MQTT_VERSION_5; + #else + *pIndexLocal = MQTT_VERSION_3_1_1; + #endif pIndexLocal++; /* Set the clean session flag if needed. */ @@ -2128,14 +4461,17 @@ MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * pPublishInfo, pPublishInfo->pPayload ) ); status = MQTTBadParameter; } - else if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0U ) ) - { - LogError( ( "Invalid topic name for PUBLISH: pTopicName=%p, " - "topicNameLength=%hu.", - ( void * ) pPublishInfo->pTopicName, - ( unsigned short ) pPublishInfo->topicNameLength ) ); - status = MQTTBadParameter; - } + + #if ( !MQTT_VERSION_5_ENABLED ) + else if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0U ) ) + { + LogError( ( "Invalid topic name for PUBLISH: pTopicName=%p, " + "topicNameLength=%hu.", + ( void * ) pPublishInfo->pTopicName, + ( unsigned short ) pPublishInfo->topicNameLength ) ); + status = MQTTBadParameter; + } + #endif else if( ( pPublishInfo->qos != MQTTQoS0 ) && ( packetId == 0U ) ) { LogError( ( "Packet ID is 0 for PUBLISH with QoS=%u.", @@ -2152,8 +4488,7 @@ MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * pPublishInfo, /* Length of serialized packet = First byte * + Length of encoded remaining length * + Remaining length. */ - packetSize = 1U + remainingLengthEncodedSize( remainingLength ) - + remainingLength; + packetSize = 1U + remainingLengthEncodedSize( remainingLength ) + remainingLength; } if( ( status == MQTTSuccess ) && ( packetSize > pFixedBuffer->size ) ) @@ -2231,31 +4566,247 @@ MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * pPublishInfo * + Remaining length * - Payload Length. */ - packetSize = 1U + remainingLengthEncodedSize( remainingLength ) - + remainingLength - - pPublishInfo->payloadLength; + packetSize = 1U + remainingLengthEncodedSize( remainingLength ) + remainingLength - pPublishInfo->payloadLength; } if( ( status == MQTTSuccess ) && ( packetSize > pFixedBuffer->size ) ) { - LogError( ( "Buffer size of %lu is not sufficient to hold " - "serialized PUBLISH header packet of size of %lu.", - ( unsigned long ) pFixedBuffer->size, - ( unsigned long ) ( packetSize - pPublishInfo->payloadLength ) ) ); - status = MQTTNoMemory; + LogError( ( "Buffer size of %lu is not sufficient to hold " + "serialized PUBLISH header packet of size of %lu.", + ( unsigned long ) pFixedBuffer->size, + ( unsigned long ) ( packetSize - pPublishInfo->payloadLength ) ) ); + status = MQTTNoMemory; + } + + if( status == MQTTSuccess ) + { + /* Serialize publish without copying the payload. */ + serializePublishCommon( pPublishInfo, + remainingLength, + packetId, + pFixedBuffer, + false ); + + /* Header size is the same as calculated packet size. */ + *pHeaderSize = packetSize; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +MQTTStatus_t MQTT_SerializeAck( const MQTTFixedBuffer_t * pFixedBuffer, + uint8_t packetType, + uint16_t packetId ) +{ + MQTTStatus_t status = MQTTSuccess; + + if( pFixedBuffer == NULL ) + { + LogError( ( "Provided buffer is NULL." ) ); + status = MQTTBadParameter; + } + else if( pFixedBuffer->pBuffer == NULL ) + { + LogError( ( "pFixedBuffer->pBuffer cannot be NULL." ) ); + status = MQTTBadParameter; + } + /* The buffer must be able to fit 4 bytes for the packet. */ + else if( pFixedBuffer->size < MQTT_PUBLISH_ACK_PACKET_SIZE ) + { + LogError( ( "Insufficient memory for packet." ) ); + status = MQTTNoMemory; + } + else if( packetId == 0U ) + { + LogError( ( "Packet ID cannot be 0." ) ); + status = MQTTBadParameter; + } + else + { + switch( packetType ) + { + /* Only publish acks are serialized by the client. */ + case MQTT_PACKET_TYPE_PUBACK: + case MQTT_PACKET_TYPE_PUBREC: + case MQTT_PACKET_TYPE_PUBREL: + case MQTT_PACKET_TYPE_PUBCOMP: + pFixedBuffer->pBuffer[ 0 ] = packetType; + pFixedBuffer->pBuffer[ 1 ] = MQTT_PACKET_SIMPLE_ACK_REMAINING_LENGTH; + pFixedBuffer->pBuffer[ 2 ] = UINT16_HIGH_BYTE( packetId ); + pFixedBuffer->pBuffer[ 3 ] = UINT16_LOW_BYTE( packetId ); + break; + + default: + LogError( ( "Packet type is not a publish ACK: Packet type=%02x", + ( unsigned int ) packetType ) ); + status = MQTTBadParameter; + break; + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +MQTTStatus_t MQTT_GetDisconnectPacketSize( size_t * pPacketSize ) +{ + MQTTStatus_t status = MQTTSuccess; + + if( pPacketSize == NULL ) + { + LogError( ( "pPacketSize is NULL." ) ); + status = MQTTBadParameter; + } + else + { + /* MQTT DISCONNECT packets always have the same size. */ + *pPacketSize = MQTT_DISCONNECT_PACKET_SIZE; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * pFixedBuffer ) +{ + MQTTStatus_t status = MQTTSuccess; + + /* Validate arguments. */ + if( pFixedBuffer == NULL ) + { + LogError( ( "pFixedBuffer cannot be NULL." ) ); + status = MQTTBadParameter; + } + else if( pFixedBuffer->pBuffer == NULL ) + { + LogError( ( "pFixedBuffer->pBuffer cannot be NULL." ) ); + status = MQTTBadParameter; + } + else + { + /* Empty else MISRA 15.7 */ + } + + if( status == MQTTSuccess ) + { + if( pFixedBuffer->size < MQTT_DISCONNECT_PACKET_SIZE ) + { + LogError( ( "Buffer size of %lu is not sufficient to hold " + "serialized DISCONNECT packet of size of %lu.", + ( unsigned long ) pFixedBuffer->size, + MQTT_DISCONNECT_PACKET_SIZE ) ); + status = MQTTNoMemory; + } + } + + if( status == MQTTSuccess ) + { + pFixedBuffer->pBuffer[ 0 ] = MQTT_PACKET_TYPE_DISCONNECT; + pFixedBuffer->pBuffer[ 1 ] = MQTT_DISCONNECT_REMAINING_LENGTH; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +MQTTStatus_t MQTT_GetPingreqPacketSize( size_t * pPacketSize ) +{ + MQTTStatus_t status = MQTTSuccess; + + if( pPacketSize == NULL ) + { + LogError( ( "pPacketSize is NULL." ) ); + status = MQTTBadParameter; + } + else + { + /* MQTT PINGREQ packets always have the same size. */ + *pPacketSize = MQTT_PACKET_PINGREQ_SIZE; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * pFixedBuffer ) +{ + MQTTStatus_t status = MQTTSuccess; + + if( pFixedBuffer == NULL ) + { + LogError( ( "pFixedBuffer is NULL." ) ); + status = MQTTBadParameter; + } + else if( pFixedBuffer->pBuffer == NULL ) + { + LogError( ( "pFixedBuffer->pBuffer cannot be NULL." ) ); + status = MQTTBadParameter; + } + else + { + /* Empty else MISRA 15.7 */ + } + + if( status == MQTTSuccess ) + { + if( pFixedBuffer->size < MQTT_PACKET_PINGREQ_SIZE ) + { + LogError( ( "Buffer size of %lu is not sufficient to hold " + "serialized PINGREQ packet of size of %lu.", + ( unsigned long ) pFixedBuffer->size, + MQTT_PACKET_PINGREQ_SIZE ) ); + status = MQTTNoMemory; + } + } + + if( status == MQTTSuccess ) + { + /* Ping request packets are always the same. */ + pFixedBuffer->pBuffer[ 0 ] = MQTT_PACKET_TYPE_PINGREQ; + pFixedBuffer->pBuffer[ 1 ] = 0x00; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +MQTTStatus_t MQTT_DeserializePublish( const MQTTPacketInfo_t * pIncomingPacket, + uint16_t * pPacketId, + MQTTPublishInfo_t * pPublishInfo ) +{ + MQTTStatus_t status = MQTTSuccess; + + if( ( pIncomingPacket == NULL ) || ( pPacketId == NULL ) || ( pPublishInfo == NULL ) ) + { + LogError( ( "Argument cannot be NULL: pIncomingPacket=%p, " + "pPacketId=%p, pPublishInfo=%p", + ( void * ) pIncomingPacket, + ( void * ) pPacketId, + ( void * ) pPublishInfo ) ); + status = MQTTBadParameter; + } + else if( ( pIncomingPacket->type & 0xF0U ) != MQTT_PACKET_TYPE_PUBLISH ) + { + LogError( ( "Packet is not publish. Packet type: %02x.", + ( unsigned int ) pIncomingPacket->type ) ); + status = MQTTBadParameter; + } + else if( pIncomingPacket->pRemainingData == NULL ) + { + LogError( ( "Argument cannot be NULL: " + "pIncomingPacket->pRemainingData is NULL." ) ); + status = MQTTBadParameter; } - - if( status == MQTTSuccess ) + else { - /* Serialize publish without copying the payload. */ - serializePublishCommon( pPublishInfo, - remainingLength, - packetId, - pFixedBuffer, - false ); - - /* Header size is the same as calculated packet size. */ - *pHeaderSize = packetSize; + status = deserializePublish( pIncomingPacket, pPacketId, pPublishInfo ); } return status; @@ -2263,52 +4814,74 @@ MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * pPublishInfo /*-----------------------------------------------------------*/ -MQTTStatus_t MQTT_SerializeAck( const MQTTFixedBuffer_t * pFixedBuffer, - uint8_t packetType, - uint16_t packetId ) +MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * pIncomingPacket, + uint16_t * pPacketId, + bool * pSessionPresent ) { MQTTStatus_t status = MQTTSuccess; - if( pFixedBuffer == NULL ) + if( pIncomingPacket == NULL ) { - LogError( ( "Provided buffer is NULL." ) ); + LogError( ( "pIncomingPacket cannot be NULL." ) ); status = MQTTBadParameter; } - else if( pFixedBuffer->pBuffer == NULL ) + + /* Pointer for packet identifier cannot be NULL for packets other than + * CONNACK and PINGRESP. */ + else if( ( pPacketId == NULL ) && + ( ( pIncomingPacket->type != MQTT_PACKET_TYPE_CONNACK ) && + ( pIncomingPacket->type != MQTT_PACKET_TYPE_PINGRESP ) ) ) { - LogError( ( "pFixedBuffer->pBuffer cannot be NULL." ) ); + LogError( ( "pPacketId cannot be NULL for packet type %02x.", + ( unsigned int ) pIncomingPacket->type ) ); status = MQTTBadParameter; } - /* The buffer must be able to fit 4 bytes for the packet. */ - else if( pFixedBuffer->size < MQTT_PUBLISH_ACK_PACKET_SIZE ) + /* Pointer for session present cannot be NULL for CONNACK. */ + else if( ( pSessionPresent == NULL ) && + ( pIncomingPacket->type == MQTT_PACKET_TYPE_CONNACK ) ) { - LogError( ( "Insufficient memory for packet." ) ); - status = MQTTNoMemory; + LogError( ( "pSessionPresent cannot be NULL for CONNACK packet." ) ); + status = MQTTBadParameter; } - else if( packetId == 0U ) + + /* Pointer for remaining data cannot be NULL for packets other + * than PINGRESP. */ + else if( ( pIncomingPacket->pRemainingData == NULL ) && + ( pIncomingPacket->type != MQTT_PACKET_TYPE_PINGRESP ) ) { - LogError( ( "Packet ID cannot be 0." ) ); + LogError( ( "Remaining data of incoming packet is NULL." ) ); status = MQTTBadParameter; } else { - switch( packetType ) + /* Make sure response packet is a valid ack. */ + switch( pIncomingPacket->type ) { - /* Only publish acks are serialized by the client. */ + case MQTT_PACKET_TYPE_CONNACK: + status = deserializeConnack( pIncomingPacket, pSessionPresent ); + break; + + case MQTT_PACKET_TYPE_SUBACK: + status = deserializeSuback( pIncomingPacket, pPacketId ); + break; + + case MQTT_PACKET_TYPE_PINGRESP: + status = deserializePingresp( pIncomingPacket ); + break; + + case MQTT_PACKET_TYPE_UNSUBACK: case MQTT_PACKET_TYPE_PUBACK: case MQTT_PACKET_TYPE_PUBREC: case MQTT_PACKET_TYPE_PUBREL: case MQTT_PACKET_TYPE_PUBCOMP: - pFixedBuffer->pBuffer[ 0 ] = packetType; - pFixedBuffer->pBuffer[ 1 ] = MQTT_PACKET_SIMPLE_ACK_REMAINING_LENGTH; - pFixedBuffer->pBuffer[ 2 ] = UINT16_HIGH_BYTE( packetId ); - pFixedBuffer->pBuffer[ 3 ] = UINT16_LOW_BYTE( packetId ); + status = deserializeSimpleAck( pIncomingPacket, pPacketId ); break; + /* Any other packet type is invalid. */ default: - LogError( ( "Packet type is not a publish ACK: Packet type=%02x", - ( unsigned int ) packetType ) ); - status = MQTTBadParameter; + LogError( ( "IotMqtt_DeserializeResponse() called with unknown packet type:(%02x).", + ( unsigned int ) pIncomingPacket->type ) ); + status = MQTTBadResponse; break; } } @@ -2318,19 +4891,64 @@ MQTTStatus_t MQTT_SerializeAck( const MQTTFixedBuffer_t * pFixedBuffer, /*-----------------------------------------------------------*/ -MQTTStatus_t MQTT_GetDisconnectPacketSize( size_t * pPacketSize ) +MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( TransportRecv_t readFunc, + NetworkContext_t * pNetworkContext, + MQTTPacketInfo_t * pIncomingPacket ) { MQTTStatus_t status = MQTTSuccess; + int32_t bytesReceived = 0; - if( pPacketSize == NULL ) + if( pIncomingPacket == NULL ) { - LogError( ( "pPacketSize is NULL." ) ); + LogError( ( "Invalid parameter: pIncomingPacket is NULL." ) ); status = MQTTBadParameter; } else { - /* MQTT DISCONNECT packets always have the same size. */ - *pPacketSize = MQTT_DISCONNECT_PACKET_SIZE; + /* Read a single byte. */ + bytesReceived = readFunc( pNetworkContext, + &( pIncomingPacket->type ), + 1U ); + } + + if( bytesReceived == 1 ) + { + /* Check validity. */ + if( incomingPacketValid( pIncomingPacket->type ) == true ) + { + pIncomingPacket->remainingLength = getRemainingLength( readFunc, + pNetworkContext ); + + if( pIncomingPacket->remainingLength == MQTT_REMAINING_LENGTH_INVALID ) + { + LogError( ( "Incoming packet remaining length invalid." ) ); + status = MQTTBadResponse; + } + } + else + { + LogError( ( "Incoming packet invalid: Packet type=%u.", + ( unsigned int ) pIncomingPacket->type ) ); + status = MQTTBadResponse; + } + } + else if( ( status != MQTTBadParameter ) && ( bytesReceived == 0 ) ) + { + status = MQTTNoDataAvailable; + } + + /* If the input packet was valid, then any other number of bytes received is + * a failure. */ + else if( status != MQTTBadParameter ) + { + LogError( ( "A single byte was not read from the transport: " + "transportStatus=%ld.", + ( long int ) bytesReceived ) ); + status = MQTTRecvFailed; + } + else + { + /* Empty else MISRA 15.7 */ } return status; @@ -2338,343 +4956,1118 @@ MQTTStatus_t MQTT_GetDisconnectPacketSize( size_t * pPacketSize ) /*-----------------------------------------------------------*/ -MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * pFixedBuffer ) +MQTTStatus_t MQTT_ProcessIncomingPacketTypeAndLength( const uint8_t * pBuffer, + const size_t * pIndex, + MQTTPacketInfo_t * pIncomingPacket ) { MQTTStatus_t status = MQTTSuccess; - /* Validate arguments. */ - if( pFixedBuffer == NULL ) + if( pIncomingPacket == NULL ) { - LogError( ( "pFixedBuffer cannot be NULL." ) ); + LogError( ( "Invalid parameter: pIncomingPacket is NULL." ) ); status = MQTTBadParameter; } - else if( pFixedBuffer->pBuffer == NULL ) + else if( pIndex == NULL ) { - LogError( ( "pFixedBuffer->pBuffer cannot be NULL." ) ); + LogError( ( "Invalid parameter: pIndex is NULL." ) ); + status = MQTTBadParameter; + } + else if( pBuffer == NULL ) + { + LogError( ( "Invalid parameter: pBuffer is NULL." ) ); status = MQTTBadParameter; } + /* There should be at least one byte in the buffer */ + else if( *pIndex < 1U ) + { + /* No data is available. There are 0 bytes received from the network + * receive function. */ + status = MQTTNoDataAvailable; + } else { - /* Empty else MISRA 15.7 */ + /* At least one byte is present which should be deciphered. */ + pIncomingPacket->type = pBuffer[ 0 ]; } if( status == MQTTSuccess ) { - if( pFixedBuffer->size < MQTT_DISCONNECT_PACKET_SIZE ) + /* Check validity. */ + if( incomingPacketValid( pIncomingPacket->type ) == true ) { - LogError( ( "Buffer size of %lu is not sufficient to hold " - "serialized DISCONNECT packet of size of %lu.", - ( unsigned long ) pFixedBuffer->size, - MQTT_DISCONNECT_PACKET_SIZE ) ); - status = MQTTNoMemory; + status = processRemainingLength( pBuffer, + pIndex, + pIncomingPacket ); + } + else + { + LogError( ( "Incoming packet invalid: Packet type=%u.", + ( unsigned int ) pIncomingPacket->type ) ); + status = MQTTBadResponse; + } + } + + return status; +} + +#if ( MQTT_VERSION_5_ENABLED ) + + MQTTStatus_t MQTTV5_InitConnect( MQTTConnectProperties_t * pConnectProperties ) + { + MQTTStatus_t status = MQTTSuccess; + + if( pConnectProperties == NULL ) + { + status = MQTTBadParameter; + } + else + { + pConnectProperties->receiveMax = UINT16_MAX; + pConnectProperties->maxPacketSize = MQTT_MAX_PACKET_SIZE; + pConnectProperties->requestProblemInfo = true; + pConnectProperties->serverReceiveMax = UINT16_MAX; + pConnectProperties->serverMaxQos = 1U; + pConnectProperties->serverMaxPacketSize = MQTT_MAX_PACKET_SIZE; + pConnectProperties->isWildcardAvaiable = 1U; + pConnectProperties->subscriptionId = 1U; + pConnectProperties->isSharedAvailable = 1U; + } + + return status; + } + + MQTTStatus_t MQTTV5_GetConnectPacketSize( const MQTTConnectInfo_t * pConnectInfo, + MQTTPublishInfo_t * pWillInfo, + MQTTConnectProperties_t * pConnectProperties, + size_t * pRemainingLength, + size_t * pPacketSize ) + { + MQTTStatus_t status = MQTTSuccess; + size_t remainingLength; + + /* The CONNECT packet will always include a 10-byte variable header without connect properties. */ + size_t connectPacketSize = MQTT_PACKET_CONNECT_HEADER_SIZE; + + /* Validate arguments. */ + if( ( pConnectInfo == NULL ) || ( pRemainingLength == NULL ) || + ( pPacketSize == NULL ) ) + { + LogError( ( "Argument cannot be NULL: pConnectInfo=%p, " + "pRemainingLength=%p, pPacketSize=%p.", + ( void * ) pConnectInfo, + ( void * ) pRemainingLength, + ( void * ) pPacketSize ) ); + status = MQTTBadParameter; + } + else if( ( pConnectInfo->clientIdentifierLength == 0U ) || ( pConnectInfo->pClientIdentifier == NULL ) ) + { + LogError( ( "Mqtt_GetConnectPacketSize() client identifier must be set." ) ); + status = MQTTBadParameter; + } + else if( ( pWillInfo != NULL ) && ( pWillInfo->payloadLength > ( size_t ) UINT16_MAX ) ) + { + /* The MQTTPublishInfo_t is reused for the will message. The payload + * length for any other message could be larger than 65,535, but + * the will message length is required to be represented in 2 bytes.*/ + LogError( ( "The Will Message length must not exceed %d. " + "pWillInfo->payloadLength=%lu.", + UINT16_MAX, + ( unsigned long ) pWillInfo->payloadLength ) ); + status = MQTTBadParameter; + } + else if( pConnectProperties == NULL ) + { + LogError( ( "Argument cannot be NULL: connectProperties" ) ); + status = MQTTBadParameter; + } + else if( ( pConnectProperties->pOutgoingAuth != NULL ) && ( pConnectProperties->pIncomingAuth == NULL ) ) + { + LogError( ( "Incoming Auth cannot be NULL" ) ); + status = MQTTBadParameter; + } + + #if ( MQTT_USER_PROPERTY_ENABLED ) + else if( pConnectProperties->pIncomingUserProperty == NULL ) + { + LogError( ( "Incoming user property cannot be NULL" ) ); + status = MQTTBadParameter; + } + #endif + else + { + /* Add the connect properties size. */ + status = MQTT_GetConnectPropertiesSize( pConnectProperties ); + } + + if( status == MQTTSuccess ) + { + connectPacketSize += pConnectProperties->propertyLength; + connectPacketSize += remainingLengthEncodedSize( pConnectProperties->propertyLength ); + /* Add the length of the client identifier. */ + connectPacketSize += pConnectInfo->clientIdentifierLength + sizeof( uint16_t ); + + /* Add the lengths of the will properties if provided. */ + if( pWillInfo != NULL ) + { + status = MQTT_GetPublishPropertiesSize( pWillInfo ); + } + } + + if( status == MQTTSuccess ) + { + /* Add the lengths of the will message and topic name if provided. */ + if( pWillInfo != NULL ) + { + connectPacketSize += pWillInfo->propertyLength; + connectPacketSize += remainingLengthEncodedSize( pWillInfo->propertyLength ); + connectPacketSize += pWillInfo->topicNameLength + sizeof( uint16_t ) + + pWillInfo->payloadLength + sizeof( uint16_t ); + } + + /* Add the lengths of the user name and password if provided. */ + if( pConnectInfo->pUserName != NULL ) + { + connectPacketSize += pConnectInfo->userNameLength + sizeof( uint16_t ); + } + + if( pConnectInfo->pPassword != NULL ) + { + connectPacketSize += pConnectInfo->passwordLength + sizeof( uint16_t ); + } + + /* 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 += 1U + remainingLengthEncodedSize( connectPacketSize ); + + /** + * 268,435,455 - max remaining length according to spec MQTT-v5; + * + * */ + if( remainingLength > MQTT_MAX_REMAINING_LENGTH ) + { + status = MQTTBadParameter; + } + } + + if( status == MQTTSuccess ) + { + *pRemainingLength = remainingLength; + *pPacketSize = connectPacketSize; + + LogDebug( ( "CONNECT packet remaining length=%lu and packet size=%lu.", + ( unsigned long ) *pRemainingLength, + ( unsigned long ) *pPacketSize ) ); } + + return status; } - if( status == MQTTSuccess ) + uint8_t * MQTT_SerializePublishProperties( const MQTTPublishInfo_t * pPublishInfo, + uint8_t * pIndex ) { - pFixedBuffer->pBuffer[ 0 ] = MQTT_PACKET_TYPE_DISCONNECT; - pFixedBuffer->pBuffer[ 1 ] = MQTT_DISCONNECT_REMAINING_LENGTH; - } + uint8_t * pIndexLocal = pIndex; - return status; -} + pIndexLocal = encodeRemainingLength( pIndexLocal, pPublishInfo->propertyLength ); -/*-----------------------------------------------------------*/ + /*Serialize the will delay if provided.*/ -MQTTStatus_t MQTT_GetPingreqPacketSize( size_t * pPacketSize ) -{ - MQTTStatus_t status = MQTTSuccess; + if( pPublishInfo->willDelay != 0U ) + { + *pIndexLocal = MQTT_WILL_DELAY_ID; + pIndexLocal++; + pIndexLocal[ 0 ] = UINT32_BYTE3( pPublishInfo->willDelay ); + pIndexLocal[ 1 ] = UINT32_BYTE2( pPublishInfo->willDelay ); + pIndexLocal[ 2 ] = UINT32_BYTE1( pPublishInfo->willDelay ); + pIndexLocal[ 3 ] = UINT32_BYTE0( pPublishInfo->willDelay ); + pIndexLocal = &pIndexLocal[ 4 ]; + } - if( pPacketSize == NULL ) - { - LogError( ( "pPacketSize is NULL." ) ); - status = MQTTBadParameter; - } - else - { - /* MQTT PINGREQ packets always have the same size. */ - *pPacketSize = MQTT_PACKET_PINGREQ_SIZE; - } + /*Serialize the topic alias if provided*/ - return status; -} + if( pPublishInfo->topicAlias != 0U ) + { + *pIndexLocal = MQTT_TOPIC_ALIAS_ID; + pIndexLocal++; + pIndexLocal[ 0 ] = UINT16_HIGH_BYTE( pPublishInfo->topicAlias ); + pIndexLocal[ 1 ] = UINT16_LOW_BYTE( pPublishInfo->topicAlias ); + pIndexLocal = &pIndexLocal[ 2 ]; + } -/*-----------------------------------------------------------*/ + /*Serialize the payload format if provided.*/ -MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * pFixedBuffer ) -{ - MQTTStatus_t status = MQTTSuccess; + if( pPublishInfo->payloadFormat != 0U ) + { + *pIndexLocal = MQTT_PAYLOAD_FORMAT_ID; + pIndexLocal++; + *pIndexLocal = pPublishInfo->payloadFormat; + pIndexLocal++; + } - if( pFixedBuffer == NULL ) - { - LogError( ( "pFixedBuffer is NULL." ) ); - status = MQTTBadParameter; - } - else if( pFixedBuffer->pBuffer == NULL ) - { - LogError( ( "pFixedBuffer->pBuffer cannot be NULL." ) ); - status = MQTTBadParameter; - } - else - { - /* Empty else MISRA 15.7 */ - } + /*Serialize the message expiry if provided.*/ - if( status == MQTTSuccess ) - { - if( pFixedBuffer->size < MQTT_PACKET_PINGREQ_SIZE ) + if( pPublishInfo->msgExpiryPresent != false ) { - LogError( ( "Buffer size of %lu is not sufficient to hold " - "serialized PINGREQ packet of size of %lu.", - ( unsigned long ) pFixedBuffer->size, - MQTT_PACKET_PINGREQ_SIZE ) ); - status = MQTTNoMemory; + *pIndexLocal = MQTT_MSG_EXPIRY_ID; + pIndexLocal++; + pIndexLocal[ 0 ] = UINT32_BYTE3( pPublishInfo->msgExpiryInterval ); + pIndexLocal[ 1 ] = UINT32_BYTE2( pPublishInfo->msgExpiryInterval ); + pIndexLocal[ 2 ] = UINT32_BYTE1( pPublishInfo->msgExpiryInterval ); + pIndexLocal[ 3 ] = UINT32_BYTE0( pPublishInfo->msgExpiryInterval ); + pIndexLocal = &pIndexLocal[ 4 ]; } + + return pIndexLocal; } - if( status == MQTTSuccess ) + uint8_t * MQTTV5_SerializeConnectProperties( uint8_t * pIndex, + const MQTTConnectProperties_t * pConnectProperties ) { - /* Ping request packets are always the same. */ - pFixedBuffer->pBuffer[ 0 ] = MQTT_PACKET_TYPE_PINGREQ; - pFixedBuffer->pBuffer[ 1 ] = 0x00; - } + uint8_t * pIndexLocal = pIndex; - return status; -} + pIndexLocal = encodeRemainingLength( pIndexLocal, pConnectProperties->propertyLength ); -/*-----------------------------------------------------------*/ + /*Serialize session expiry if provided.*/ + if( pConnectProperties->sessionExpiry != 0U ) + { + *pIndexLocal = MQTT_SESSION_EXPIRY_ID; + pIndexLocal++; + pIndexLocal[ 0 ] = UINT32_BYTE3( pConnectProperties->sessionExpiry ); + pIndexLocal[ 1 ] = UINT32_BYTE2( pConnectProperties->sessionExpiry ); + pIndexLocal[ 2 ] = UINT32_BYTE1( pConnectProperties->sessionExpiry ); + pIndexLocal[ 3 ] = UINT32_BYTE0( pConnectProperties->sessionExpiry ); + pIndexLocal = &pIndexLocal[ 4 ]; + } -MQTTStatus_t MQTT_DeserializePublish( const MQTTPacketInfo_t * pIncomingPacket, - uint16_t * pPacketId, - MQTTPublishInfo_t * pPublishInfo ) -{ - MQTTStatus_t status = MQTTSuccess; + /*Serialize receive max if provided.*/ - if( ( pIncomingPacket == NULL ) || ( pPacketId == NULL ) || ( pPublishInfo == NULL ) ) - { - LogError( ( "Argument cannot be NULL: pIncomingPacket=%p, " - "pPacketId=%p, pPublishInfo=%p", - ( void * ) pIncomingPacket, - ( void * ) pPacketId, - ( void * ) pPublishInfo ) ); - status = MQTTBadParameter; - } - else if( ( pIncomingPacket->type & 0xF0U ) != MQTT_PACKET_TYPE_PUBLISH ) - { - LogError( ( "Packet is not publish. Packet type: %02x.", - ( unsigned int ) pIncomingPacket->type ) ); - status = MQTTBadParameter; + if( pConnectProperties->receiveMax != ( uint16_t ) UINT16_MAX ) + { + *pIndexLocal = MQTT_RECEIVE_MAX_ID; + pIndexLocal++; + pIndexLocal[ 0 ] = UINT16_HIGH_BYTE( pConnectProperties->receiveMax ); + pIndexLocal[ 1 ] = UINT16_LOW_BYTE( pConnectProperties->receiveMax ); + pIndexLocal = &pIndexLocal[ 2 ]; + } + + /*Serialize the max packet size if provided.*/ + + if( pConnectProperties->maxPacketSize != MQTT_MAX_PACKET_SIZE ) + { + *pIndexLocal = MQTT_MAX_PACKET_SIZE_ID; + pIndexLocal++; + pIndexLocal[ 0 ] = UINT32_BYTE3( pConnectProperties->maxPacketSize ); + pIndexLocal[ 1 ] = UINT32_BYTE2( pConnectProperties->maxPacketSize ); + pIndexLocal[ 2 ] = UINT32_BYTE1( pConnectProperties->maxPacketSize ); + pIndexLocal[ 3 ] = UINT32_BYTE0( pConnectProperties->maxPacketSize ); + pIndexLocal = &pIndexLocal[ 4 ]; + } + + /*Serialize the topic alias if provided.*/ + + if( pConnectProperties->topicAliasMax != 0U ) + { + *pIndexLocal = MQTT_TOPIC_ALIAS_MAX_ID; + pIndexLocal++; + pIndexLocal[ 0 ] = UINT16_HIGH_BYTE( pConnectProperties->topicAliasMax ); + pIndexLocal[ 1 ] = UINT16_LOW_BYTE( pConnectProperties->topicAliasMax ); + pIndexLocal = &pIndexLocal[ 2 ]; + } + + /*Serialize the request response information if provided.*/ + + if( pConnectProperties->requestResponseInfo != false ) + { + *pIndexLocal = MQTT_REQUEST_RESPONSE_ID; + pIndexLocal++; + *pIndexLocal = 1U; + pIndexLocal++; + } + + /*Serialize request problem information if provided.*/ + + if( pConnectProperties->requestProblemInfo != true ) + { + *pIndexLocal = MQTT_REQUEST_PROBLEM_ID; + pIndexLocal++; + *pIndexLocal = 0U; + pIndexLocal++; + } + + return pIndexLocal; } - else if( pIncomingPacket->pRemainingData == NULL ) + + MQTTStatus_t MQTTV5_SerializeConnect( const MQTTConnectInfo_t * pConnectInfo, + const MQTTPublishInfo_t * pWillInfo, + const MQTTConnectProperties_t * pConnectProperties, + size_t remainingLength, + const MQTTFixedBuffer_t * pFixedBuffer ) { - LogError( ( "Argument cannot be NULL: " - "pIncomingPacket->pRemainingData is NULL." ) ); - status = MQTTBadParameter; + MQTTStatus_t status = MQTTSuccess; + size_t connectPacketSize = 0; + + /* Validate arguments. */ + if( ( pConnectInfo == NULL ) || ( pFixedBuffer == NULL ) ) + { + LogError( ( "Argument cannot be NULL: pConnectInfo=%p, " + "pFixedBuffer=%p.", + ( void * ) pConnectInfo, + ( void * ) pFixedBuffer ) ); + status = MQTTBadParameter; + } + /* A buffer must be configured for serialization. */ + else if( pFixedBuffer->pBuffer == NULL ) + { + LogError( ( "Argument cannot be NULL: pFixedBuffer->pBuffer is NULL." ) ); + status = MQTTBadParameter; + } + else if( ( pWillInfo != NULL ) && ( pWillInfo->pTopicName == NULL ) ) + { + LogError( ( "pWillInfo->pTopicName cannot be NULL if Will is present." ) ); + status = MQTTBadParameter; + } + + else if( pConnectProperties == NULL ) + { + LogError( ( "Argument cannot be NULL: connectProperties" ) ); + status = MQTTBadParameter; + } + else + { + /* Calculate CONNECT packet size. Overflow in in this addition is not checked + * because it is part of the API contract to call Mqtt_GetConnectPacketSize() + * before this function. */ + connectPacketSize = remainingLength + remainingLengthEncodedSize( remainingLength ) + 1U; + + /* Check that the full packet size fits within the given buffer. */ + if( connectPacketSize > pFixedBuffer->size ) + { + LogError( ( "Buffer size of %lu is not sufficient to hold " + "serialized CONNECT packet of size of %lu.", + ( unsigned long ) pFixedBuffer->size, + ( unsigned long ) connectPacketSize ) ); + status = MQTTNoMemory; + } + else + { + serializeConnectPacketV5( pConnectInfo, + pWillInfo, + pConnectProperties, + remainingLength, + pFixedBuffer ); + } + } + + return status; } - else + + MQTTStatus_t MQTTV5_DeserializeConnack( MQTTConnectProperties_t * pConnackProperties, + const MQTTPacketInfo_t * pIncomingPacket, + bool * pSessionPresent ) { - status = deserializePublish( pIncomingPacket, pPacketId, pPublishInfo ); - } + MQTTStatus_t status = MQTTSuccess; + size_t propertyLength; + size_t remainingLengthSize; + const uint8_t * pVariableHeader = NULL; - return status; -} + /*Validate the arguments*/ + if( pConnackProperties == NULL ) + { + status = MQTTBadParameter; + } -/*-----------------------------------------------------------*/ + #if ( MQTT_USER_PROPERTY_ENABLED ) + else if( pConnackProperties->pIncomingUserProperty == NULL ) + { + status = MQTTBadParameter; + } + #endif + else + { + status = validateConnackParams( pIncomingPacket, pSessionPresent ); + } -MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * pIncomingPacket, - uint16_t * pPacketId, - bool * pSessionPresent ) -{ - MQTTStatus_t status = MQTTSuccess; + if( status == MQTTSuccess ) + { + pVariableHeader = pIncomingPacket->pRemainingData; + pVariableHeader = &pVariableHeader[ 2 ]; + remainingLengthSize = remainingLengthEncodedSize( pIncomingPacket->remainingLength ); + status = decodeVariableLength( pVariableHeader, &propertyLength ); + } - if( pIncomingPacket == NULL ) - { - LogError( ( "pIncomingPacket cannot be NULL." ) ); - status = MQTTBadParameter; + /*Validate the packet size if max packet size is set*/ + if( status == MQTTSuccess ) + { + if( ( pIncomingPacket->remainingLength + remainingLengthSize + 1U ) > ( pConnackProperties->maxPacketSize ) ) + { + status = MQTTProtocolError; + } + /*Validate the remaining length*/ + else if( ( pIncomingPacket->remainingLength ) != ( 2U + propertyLength + remainingLengthEncodedSize( propertyLength ) ) ) + { + status = MQTTMalformedPacket; + } + /*Deserialize the connack properties.*/ + else + { + status = deserializeConnackV5( pConnackProperties, propertyLength, &pVariableHeader ); + } + } + + return status; } - /* Pointer for packet identifier cannot be NULL for packets other than - * CONNACK and PINGRESP. */ - else if( ( pPacketId == NULL ) && - ( ( pIncomingPacket->type != MQTT_PACKET_TYPE_CONNACK ) && - ( pIncomingPacket->type != MQTT_PACKET_TYPE_PINGRESP ) ) ) + MQTTStatus_t MQTTV5_ValidatePublishParams( const MQTTPublishInfo_t * pPublishInfo, + uint16_t topicAliasMax, + uint8_t retainAvailable, + uint8_t maxQos ) { - LogError( ( "pPacketId cannot be NULL for packet type %02x.", - ( unsigned int ) pIncomingPacket->type ) ); - status = MQTTBadParameter; + MQTTStatus_t status; + + if( pPublishInfo == NULL ) + { + LogError( ( "Argument cannot be NULL: pPublishInfo=%p ", + ( void * ) pPublishInfo + ) ); + status = MQTTBadParameter; + } + else if( pPublishInfo->topicAlias > topicAliasMax ) + { + LogError( ( "Invalid topic alias." ) ); + status = MQTTBadParameter; + } + else if( ( pPublishInfo->retain == true ) && ( retainAvailable == 0U ) ) + { + LogError( ( "Retain is not available." ) ); + status = MQTTBadParameter; + } + else if( ( pPublishInfo->qos != MQTTQoS0 ) && ( maxQos == 0U ) ) + { + LogError( ( "Qos value = %hu is not allowed by the server ", + ( unsigned short ) pPublishInfo->topicNameLength ) ); + status = MQTTBadParameter; + } + else + { + status = MQTTSuccess; + } + + return status; } - /* Pointer for session present cannot be NULL for CONNACK. */ - else if( ( pSessionPresent == NULL ) && - ( pIncomingPacket->type == MQTT_PACKET_TYPE_CONNACK ) ) + + MQTTStatus_t MQTTV5_GetPublishPacketSize( MQTTPublishInfo_t * pPublishInfo, + size_t * pRemainingLength, + size_t * pPacketSize, + uint32_t maxPacketSize ) { - LogError( ( "pSessionPresent cannot be NULL for CONNACK packet." ) ); - status = MQTTBadParameter; + MQTTStatus_t status = MQTTSuccess; + + if( ( pPublishInfo == NULL ) || ( pRemainingLength == NULL ) || ( pPacketSize == NULL ) ) + { + LogError( ( "Argument cannot be NULL: pPublishInfo=%p, " + "pRemainingLength=%p, pPacketSize=%p.", + ( void * ) pPublishInfo, + ( void * ) pRemainingLength, + ( void * ) pPacketSize ) ); + status = MQTTBadParameter; + } + else if( ( pPublishInfo->pTopicName == NULL ) && ( pPublishInfo->topicNameLength != 0U ) ) + { + LogError( ( "Invalid topic name for PUBLISH: pTopicName=%p, " + "topicNameLength=%hu.", + ( void * ) pPublishInfo->pTopicName, + ( unsigned short ) pPublishInfo->topicNameLength ) ); + status = MQTTBadParameter; + } + else if( ( pPublishInfo->topicAlias == 0U ) && ( pPublishInfo->topicNameLength == 0U ) ) + { + LogError( ( "Invalid topic name for PUBLISH: pTopicName=%p, " + "topicNameLength=%hu.", + ( void * ) pPublishInfo->pTopicName, + ( unsigned short ) pPublishInfo->topicNameLength ) ); + status = MQTTBadParameter; + } + else if( maxPacketSize == 0U ) + { + status = MQTTBadParameter; + } + else + { + status = calculatePublishPacketSizeV5( pPublishInfo, pRemainingLength, pPacketSize, maxPacketSize ); + } + + return status; } - /* Pointer for remaining data cannot be NULL for packets other - * than PINGRESP. */ - else if( ( pIncomingPacket->pRemainingData == NULL ) && - ( pIncomingPacket->type != MQTT_PACKET_TYPE_PINGRESP ) ) + MQTTStatus_t MQTTV5_DeserializeAck( const MQTTPacketInfo_t * pIncomingPacket, + uint16_t * pPacketId, + MQTTAckInfo_t * pAckInfo, + bool requestProblem, + uint32_t maxPacketSize ) { - LogError( ( "Remaining data of incoming packet is NULL." ) ); - status = MQTTBadParameter; + MQTTStatus_t status = MQTTSuccess; + + if( pIncomingPacket == NULL ) + { + LogError( ( "pIncomingPacket cannot be NULL." ) ); + status = MQTTBadParameter; + } + + /* Pointer for packet identifier cannot be NULL for packets other than + * CONNACK and PINGRESP. */ + else if( pPacketId == NULL ) + { + LogError( ( "pPacketId cannot be NULL for packet type %02x.", + ( unsigned int ) pIncomingPacket->type ) ); + status = MQTTBadParameter; + } + + /* Pointer for remaining data cannot be NULL for packets other + * than PINGRESP. */ + else if( pIncomingPacket->pRemainingData == NULL ) + { + LogError( ( "Remaining data of incoming packet is NULL." ) ); + status = MQTTBadParameter; + } + /*Max packet size cannot be 0.*/ + else if( maxPacketSize == 0U ) + { + status = MQTTBadParameter; + } + else if( ( pIncomingPacket->remainingLength + remainingLengthEncodedSize( pIncomingPacket->remainingLength ) + 1U ) > maxPacketSize ) + { + status = MQTTProtocolError; + } + else + { + /* Make sure response packet is a valid ack. */ + switch( pIncomingPacket->type ) + { + case MQTT_PACKET_TYPE_PUBACK: + case MQTT_PACKET_TYPE_PUBREC: + status = deserializeSimpleAckV5( pIncomingPacket, pPacketId, pAckInfo, requestProblem ); + + if( status == MQTTSuccess ) + { + status = logAckResponseV5( pAckInfo->reasonCode, *pPacketId ); + } + + break; + + case MQTT_PACKET_TYPE_PUBREL: + case MQTT_PACKET_TYPE_PUBCOMP: + status = deserializeSimpleAckV5( pIncomingPacket, pPacketId, pAckInfo, requestProblem ); + + if( status == MQTTSuccess ) + { + status = logSimpleAckResponseV5( pAckInfo->reasonCode, *pPacketId ); + } + + break; + + /* Any other packet type is invalid. */ + default: + LogError( ( "IotMqttv5_DeserializeResponse() called with unknown packet type:(%02x).", + ( unsigned int ) pIncomingPacket->type ) ); + status = MQTTBadResponse; + break; + } + } + + return status; } - else + + MQTTStatus_t MQTTV5_GetAckPacketSize( MQTTAckInfo_t * pAckInfo, + size_t * pRemainingLength, + size_t * pPacketSize, + uint32_t maxPacketSize ) { - /* Make sure response packet is a valid ack. */ - switch( pIncomingPacket->type ) + MQTTStatus_t status = MQTTSuccess; + size_t length = 0U; + size_t propertyLength = 0U; + size_t packetSize = 0U; + + /*Validate the parameters.*/ + if( ( pAckInfo == NULL ) || ( pRemainingLength == NULL ) || ( pPacketSize == NULL ) ) { - case MQTT_PACKET_TYPE_CONNACK: - status = deserializeConnack( pIncomingPacket, pSessionPresent ); - break; + status = MQTTBadParameter; + } + else if( maxPacketSize == 0U ) + { + status = MQTTBadParameter; + } + else + { + length += MQTT_PUBLISH_ACK_PACKET_SIZE_WITH_REASON; - case MQTT_PACKET_TYPE_SUBACK: - status = deserializeSuback( pIncomingPacket, pPacketId ); - break; + if( pAckInfo->reasonStringLength != 0U ) + { + if( pAckInfo->pReasonString == NULL ) + { + status = MQTTBadParameter; + } + else + { + propertyLength += pAckInfo->reasonStringLength; + propertyLength += MQTT_UTF8_LENGTH_SIZE; + } + } + } - case MQTT_PACKET_TYPE_PINGRESP: - status = deserializePingresp( pIncomingPacket ); - break; + #if ( MQTT_USER_PROPERTY_ENABLED ) + if( ( status == MQTTSuccess ) && ( pAckInfo->pUserProperty != NULL ) ) + { + status = MQTT_GetUserPropertySize( pAckInfo->pUserProperty->userProperty, pAckInfo->pUserProperty->count, &propertyLength ); + } + #endif - case MQTT_PACKET_TYPE_UNSUBACK: - case MQTT_PACKET_TYPE_PUBACK: - case MQTT_PACKET_TYPE_PUBREC: - case MQTT_PACKET_TYPE_PUBREL: - case MQTT_PACKET_TYPE_PUBCOMP: - status = deserializeSimpleAck( pIncomingPacket, pPacketId ); - break; + if( status == MQTTSuccess ) + { + /*Validate the length.*/ + if( ( propertyLength + 4U ) < MQTT_MAX_REMAINING_LENGTH ) + { + /*We have successfully calculated the property length.*/ + pAckInfo->propertyLength = propertyLength; + length += remainingLengthEncodedSize( propertyLength ) + propertyLength; + *pRemainingLength = length; + } + else + { + status = MQTTBadParameter; + } + } - /* Any other packet type is invalid. */ - default: - LogError( ( "IotMqtt_DeserializeResponse() called with unknown packet type:(%02x).", - ( unsigned int ) pIncomingPacket->type ) ); - status = MQTTBadResponse; - break; + if( status == MQTTSuccess ) + { + packetSize = length + 1U + remainingLengthEncodedSize( length ); + + if( packetSize > maxPacketSize ) + { + status = MQTTBadParameter; + } + else + { + *pPacketSize = packetSize; + } } - } - return status; -} + return status; + } -/*-----------------------------------------------------------*/ + uint8_t * MQTTV5_SerializeAckFixed( uint8_t * pIndex, + uint8_t packetType, + uint16_t packetId, + size_t remainingLength, + size_t propertyLength ) + { + uint8_t * pIndexLocal = pIndex; + + /* The first byte in the publish ack packet is the control packet type. */ + *pIndexLocal = packetType; + pIndexLocal++; + /*After the packet type fixed header has remaining length.*/ + pIndexLocal = encodeRemainingLength( pIndexLocal, remainingLength ); + /*Encode the packet id.*/ + pIndexLocal[ 0 ] = UINT16_HIGH_BYTE( packetId ); + pIndexLocal[ 1 ] = UINT16_LOW_BYTE( packetId ); + pIndexLocal = &pIndexLocal[ 2 ]; + /*We are only sending the ack back if the reason code is success.*/ + *pIndexLocal = MQTT_REASON_SUCCESS; + pIndexLocal++; + /*Encode the property length.*/ + pIndexLocal = encodeRemainingLength( pIndexLocal, propertyLength ); + return pIndexLocal; + } + + MQTTStatus_t MQTTV5_SerializePubAckWithProperty( const MQTTAckInfo_t * pAckInfo, + size_t remainingLength, + const MQTTFixedBuffer_t * pFixedBuffer, + uint8_t packetType, + uint16_t packetId ) + { + MQTTStatus_t status = MQTTSuccess; + size_t ackPacketSize = 0; + + /* Validate arguments. */ + if( ( pAckInfo == NULL ) || ( pFixedBuffer == NULL ) ) + { + LogError( ( "Argument cannot be NULL: pAckInfo=%p, " + "pFixedBuffer=%p.", + ( void * ) pAckInfo, + ( void * ) pFixedBuffer ) ); + status = MQTTBadParameter; + } + /* A buffer must be configured for serialization. */ + else if( pFixedBuffer->pBuffer == NULL ) + { + LogError( ( "Argument cannot be NULL: pFixedBuffer->pBuffer is NULL." ) ); + status = MQTTBadParameter; + } + else + { + ackPacketSize = remainingLength + remainingLengthEncodedSize( remainingLength ) + 1U; -MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( TransportRecv_t readFunc, - NetworkContext_t * pNetworkContext, - MQTTPacketInfo_t * pIncomingPacket ) -{ - MQTTStatus_t status = MQTTSuccess; - int32_t bytesReceived = 0; + /* Check that the full packet size fits within the given buffer. */ + if( ackPacketSize > pFixedBuffer->size ) + { + LogError( ( "Buffer size of %lu is not sufficient to hold " + "serialized ACK packet of size of %lu.", + ( unsigned long ) pFixedBuffer->size, + ( unsigned long ) ackPacketSize ) ); + status = MQTTNoMemory; + } + else + { + serializePubAckPacketV5( pAckInfo, packetType, packetId, remainingLength, pFixedBuffer ); + } + } - if( pIncomingPacket == NULL ) - { - LogError( ( "Invalid parameter: pIncomingPacket is NULL." ) ); - status = MQTTBadParameter; - } - else - { - /* Read a single byte. */ - bytesReceived = readFunc( pNetworkContext, - &( pIncomingPacket->type ), - 1U ); + return status; } - if( bytesReceived == 1 ) + MQTTStatus_t MQTTV5_GetDisconnectPacketSize( MQTTAckInfo_t * pDisconnectInfo, + size_t * pRemainingLength, + size_t * pPacketSize, + uint32_t maxPacketSize, + uint32_t sessionExpiry, + uint32_t prevSessionExpiry ) { - /* Check validity. */ - if( incomingPacketValid( pIncomingPacket->type ) == true ) + MQTTStatus_t status = MQTTSuccess; + size_t length = 0U; + size_t packetSize = 0U; + size_t propertyLength = 0U; + + /*Validate the arguments.*/ + if( ( pDisconnectInfo == NULL ) || ( pRemainingLength == NULL ) || ( pPacketSize == NULL ) ) { - pIncomingPacket->remainingLength = getRemainingLength( readFunc, - pNetworkContext ); + LogError( ( "Argument cannot be NULL: pDisconnectInfo=%p, " + "pRemainingLength=%p, pPacketSize=%p.", + ( void * ) pDisconnectInfo, + ( void * ) pRemainingLength, + ( void * ) pPacketSize ) ); + status = MQTTBadParameter; + } + else if( maxPacketSize == 0U ) + { + LogError( ( "Max packet size cannot be zero." ) ); + status = MQTTBadParameter; + } + /*Cannot overWrite a session expiry of 0.*/ + else if( ( prevSessionExpiry == 0U ) && ( sessionExpiry != 0U ) ) + { + LogError( ( "If the Session Expiry in the CONNECT packet was zero, then it is a Protocol Error to set a non-zero Session Expiry Interval in the DISCONNECT packet." ) ); + status = MQTTBadParameter; + } + else if( validateDisconnectResponseV5( pDisconnectInfo->reasonCode, false ) != MQTTSuccess ) + { + LogError( ( "Invalid reason code." ) ); + status = MQTTBadParameter; + } + else + { + /*Reason code.*/ + length += 1U; - if( pIncomingPacket->remainingLength == MQTT_REMAINING_LENGTH_INVALID ) + /*Add session expiry if provided.*/ + if( sessionExpiry != 0U ) { - LogError( ( "Incoming packet remaining length invalid." ) ); - status = MQTTBadResponse; + propertyLength += MQTT_SESSION_EXPIRY_SIZE; + } + + /*Validate the reason string if provided.*/ + if( pDisconnectInfo->reasonStringLength != 0U ) + { + if( pDisconnectInfo->pReasonString == NULL ) + { + status = MQTTBadParameter; + } + else + { + propertyLength += pDisconnectInfo->reasonStringLength; + propertyLength += MQTT_UTF8_LENGTH_SIZE; + } } } - else + + #if ( MQTT_USER_PROPERTY_ENABLED ) + if( ( status == MQTTSuccess ) && ( pDisconnectInfo->pUserProperty != NULL ) ) + { + status = MQTT_GetUserPropertySize( pDisconnectInfo->pUserProperty->userProperty, pDisconnectInfo->pUserProperty->count, &propertyLength ); + } + #endif + + if( status == MQTTSuccess ) { - LogError( ( "Incoming packet invalid: Packet type=%u.", - ( unsigned int ) pIncomingPacket->type ) ); - status = MQTTBadResponse; + /*Validate the length.*/ + if( ( propertyLength + 4U ) < MQTT_MAX_REMAINING_LENGTH ) + { + /*We have successfully calculated the property length.*/ + pDisconnectInfo->propertyLength = propertyLength; + length += remainingLengthEncodedSize( propertyLength ) + propertyLength; + *pRemainingLength = length; + } + else + { + status = MQTTBadParameter; + } } - } - else if( ( status != MQTTBadParameter ) && ( bytesReceived == 0 ) ) - { - status = MQTTNoDataAvailable; + + if( status == MQTTSuccess ) + { + /*Packet size should be less than max allowed by the server.*/ + packetSize = length + 1U + remainingLengthEncodedSize( length ); + + if( packetSize > maxPacketSize ) + { + status = MQTTBadParameter; + } + else + { + *pPacketSize = packetSize; + } + } + + return status; } - /* If the input packet was valid, then any other number of bytes received is - * a failure. */ - else if( status != MQTTBadParameter ) + uint8_t * MQTTV5_SerializeDisconnectFixed( uint8_t * pIndex, + const MQTTAckInfo_t * pDisconnectInfo, + size_t remainingLength, + uint32_t sessionExpiry ) { - LogError( ( "A single byte was not read from the transport: " - "transportStatus=%ld.", - ( long int ) bytesReceived ) ); - status = MQTTRecvFailed; + uint8_t * pIndexLocal = pIndex; + + assert( pDisconnectInfo != NULL ); + assert( pIndex != NULL ); + /* The first byte in the publish ack packet is the control packet type. */ + *pIndexLocal = MQTT_PACKET_TYPE_DISCONNECT; + pIndexLocal++; + /*After the packet type fixed header has remaining length.*/ + pIndexLocal = encodeRemainingLength( pIndexLocal, remainingLength ); + /*Encode the reason code.*/ + *pIndexLocal = pDisconnectInfo->reasonCode; + pIndexLocal++; + /*Encode the property length.*/ + pIndexLocal = encodeRemainingLength( pIndexLocal, pDisconnectInfo->propertyLength ); + + /*Encode the session expiry if provided. */ + if( sessionExpiry != 0U ) + { + *pIndexLocal = MQTT_SESSION_EXPIRY_ID; + pIndexLocal++; + pIndexLocal[ 0 ] = UINT32_BYTE3( sessionExpiry ); + pIndexLocal[ 1 ] = UINT32_BYTE2( sessionExpiry ); + pIndexLocal[ 2 ] = UINT32_BYTE1( sessionExpiry ); + pIndexLocal[ 3 ] = UINT32_BYTE0( sessionExpiry ); + pIndexLocal = &pIndexLocal[ 4 ]; + } + + return pIndexLocal; } - else + + static void serializeDisconnectPacketV5( const MQTTAckInfo_t * pDisconnectInfo, + const MQTTFixedBuffer_t * pFixedBuffer, + size_t remainingLength, + uint32_t sessionExpiry ) { - /* Empty else MISRA 15.7 */ - } + uint8_t * pIndex = NULL; - return status; -} + assert( pDisconnectInfo != NULL ); + assert( pFixedBuffer != NULL ); + assert( pFixedBuffer->pBuffer != NULL ); + pIndex = pFixedBuffer->pBuffer; + /* Serialize the header including reason code and property length */ + pIndex = MQTTV5_SerializeDisconnectFixed( pIndex, pDisconnectInfo, + remainingLength, + sessionExpiry ); -/*-----------------------------------------------------------*/ + /*Serialize the reason string if provided.*/ + if( pDisconnectInfo->reasonStringLength != 0U ) + { + *pIndex = MQTT_REASON_STRING_ID; + pIndex++; + pIndex = encodeString( pIndex, pDisconnectInfo->pReasonString, pDisconnectInfo->reasonStringLength ); + } -MQTTStatus_t MQTT_ProcessIncomingPacketTypeAndLength( const uint8_t * pBuffer, - const size_t * pIndex, - MQTTPacketInfo_t * pIncomingPacket ) -{ - MQTTStatus_t status = MQTTSuccess; + #if ( MQTT_USER_PROPERTY_ENABLED ) + /*Serialize the user properties if provided.*/ + if( pDisconnectInfo->pUserProperty != NULL ) + { + uint32_t i = 0; + uint32_t size = pDisconnectInfo->pUserProperty->count; + const MQTTUserProperty_t * pUserProperty = pDisconnectInfo->pUserProperty->userProperty; + + for( ; i < size; i++ ) + { + *pIndex = MQTT_USER_PROPERTY_ID; + pIndex++; + pIndex = encodeString( pIndex, pUserProperty[ i ].pKey, pUserProperty[ i ].keyLength ); + pIndex = encodeString( pIndex, pUserProperty[ i ].pValue, pUserProperty[ i ].valueLength ); + } + } + #endif /* if ( MQTT_USER_PROPERTY_ENABLED ) */ - if( pIncomingPacket == NULL ) - { - LogError( ( "Invalid parameter: pIncomingPacket is NULL." ) ); - status = MQTTBadParameter; - } - else if( pIndex == NULL ) - { - LogError( ( "Invalid parameter: pIndex is NULL." ) ); - status = MQTTBadParameter; - } - else if( pBuffer == NULL ) - { - LogError( ( "Invalid parameter: pBuffer is NULL." ) ); - status = MQTTBadParameter; - } - /* There should be at least one byte in the buffer */ - else if( *pIndex < 1U ) - { - /* No data is available. There are 0 bytes received from the network - * receive function. */ - status = MQTTNoDataAvailable; + /* Ensure that the difference between the end and beginning of the buffer + * is less than the buffer size. */ + assert( ( ( size_t ) ( pIndex - pFixedBuffer->pBuffer ) ) <= pFixedBuffer->size ); } - else + + + MQTTStatus_t MQTTV5_SerializeDisconnectWithProperty( const MQTTAckInfo_t * pDisconnectInfo, + size_t remainingLength, + const MQTTFixedBuffer_t * pFixedBuffer, + uint32_t sessionExpiry ) { - /* At least one byte is present which should be deciphered. */ - pIncomingPacket->type = pBuffer[ 0 ]; + MQTTStatus_t status = MQTTSuccess; + size_t packetSize = 0; + + /* Validate arguments. */ + if( ( pDisconnectInfo == NULL ) || ( pFixedBuffer == NULL ) ) + { + LogError( ( "Argument cannot be NULL: pDisconnectInfo=%p, " + "pFixedBuffer=%p.", + ( void * ) pDisconnectInfo, + ( void * ) pFixedBuffer ) ); + status = MQTTBadParameter; + } + /* A buffer must be configured for serialization. */ + else if( pFixedBuffer->pBuffer == NULL ) + { + LogError( ( "Argument cannot be NULL: pFixedBuffer->pBuffer is NULL." ) ); + status = MQTTBadParameter; + } + else + { + packetSize = remainingLength + remainingLengthEncodedSize( remainingLength ) + 1U; + + /* Check that the full packet size fits within the given buffer. */ + if( packetSize > pFixedBuffer->size ) + { + LogError( ( "Buffer size of %lu is not sufficient to hold " + "serialized ACK packet of size of %lu.", + ( unsigned long ) pFixedBuffer->size, + ( unsigned long ) packetSize ) ); + status = MQTTNoMemory; + } + else + { + serializeDisconnectPacketV5( pDisconnectInfo, pFixedBuffer, remainingLength, sessionExpiry ); + } + } + + return status; } - if( status == MQTTSuccess ) + MQTTStatus_t MQTTV5_DeserializeDisconnect( const MQTTPacketInfo_t * pPacket, + MQTTAckInfo_t * pDisconnectInfo, + const char ** pServerRef, + uint16_t * pServerRefLength, + uint32_t maxPacketSize ) { - /* Check validity. */ - if( incomingPacketValid( pIncomingPacket->type ) == true ) + MQTTStatus_t status = MQTTSuccess; + const uint8_t * pIndex = NULL; + size_t propertyLength = 0U; + + /*Validate the arguments*/ + if( ( pPacket == NULL ) || ( pPacket->pRemainingData == NULL ) ) { - status = processRemainingLength( pBuffer, - pIndex, - pIncomingPacket ); + status = MQTTBadParameter; + } + else if( ( pDisconnectInfo == NULL ) || ( pServerRef == NULL ) || ( pServerRefLength == NULL ) ) + { + status = MQTTBadParameter; + } + else if( maxPacketSize == 0U ) + { + status = MQTTBadParameter; + } + else if( pPacket->remainingLength == 0U ) + { + status = MQTTMalformedPacket; + } + /*Packet size should not be more than the max allowed by the client.*/ + else if( ( pPacket->remainingLength + remainingLengthEncodedSize( pPacket->remainingLength ) + 1U ) > maxPacketSize ) + { + status = MQTTProtocolError; } + + #if ( MQTT_USER_PROPERTY_ENABLED ) + else if( pDisconnectInfo->pUserProperty == NULL ) + { + status = MQTTBadParameter; + } + #endif else { - LogError( ( "Incoming packet invalid: Packet type=%u.", - ( unsigned int ) pIncomingPacket->type ) ); - status = MQTTBadResponse; + /* Extract the reason code */ + pIndex = pPacket->pRemainingData; + pDisconnectInfo->reasonCode = *pIndex; + pIndex++; + /*Validate the reason code.*/ + status = validateDisconnectResponseV5( pDisconnectInfo->reasonCode, true ); } - } - return status; -} + if( status == MQTTSuccess ) + { + if( ( pPacket->remainingLength > 1U ) ) + { + /*Extract the property length.*/ + status = decodeVariableLength( pIndex, &propertyLength ); + + if( status == MQTTSuccess ) + { + pIndex = &pIndex[ remainingLengthEncodedSize( propertyLength ) ]; + + /*Validate the remaining length.*/ + if( pPacket->remainingLength != ( propertyLength + remainingLengthEncodedSize( propertyLength ) + 1U ) ) + { + status = MQTTMalformedPacket; + } + } + } + } + + if( status == MQTTSuccess ) + { + while( ( propertyLength > 0U ) && ( status == MQTTSuccess ) ) + { + /*Decode the property id.*/ + uint8_t propertyId = *pIndex; + bool reasonString = false; + bool serverRef = false; + pIndex = &pIndex[ 1 ]; + propertyLength -= sizeof( uint8_t ); + + /*Validate the property id and decode accordingly.*/ + switch( propertyId ) + { + case MQTT_REASON_STRING_ID: + status = decodeutf_8( &pDisconnectInfo->pReasonString, &pDisconnectInfo->reasonStringLength, &propertyLength, &reasonString, &pIndex ); + break; + + case MQTT_USER_PROPERTY_ID: + #if ( MQTT_USER_PROPERTY_ENABLED ) + status = decodeutf_8pair( pDisconnectInfo->pUserProperty, &pDisconnectInfo->pUserProperty->count, &propertyLength, &pIndex ); + #else + status = decodeAndDiscard( &propertyLength, &pIndex ); + #endif + break; + + case MQTT_SERVER_REF_ID: + status = decodeutf_8( pServerRef, pServerRefLength, &propertyLength, &serverRef, &pIndex ); + break; + + default: + status = MQTTProtocolError; + break; + } + } + } + return status; + } +#endif /* if ( MQTT_VERSION_5_ENABLED ) */ /*-----------------------------------------------------------*/ diff --git a/source/include/core_mqtt.h b/source/include/core_mqtt.h index 8e0bad49e..43c811b24 100644 --- a/source/include/core_mqtt.h +++ b/source/include/core_mqtt.h @@ -165,6 +165,7 @@ typedef struct MQTTPubAckInfo MQTTPublishState_t publishState; /**< @brief The current state of the publish process. */ } MQTTPubAckInfo_t; + /** * @ingroup mqtt_struct_types * @brief A struct representing an MQTT connection. @@ -246,6 +247,18 @@ typedef struct MQTTContext uint16_t keepAliveIntervalSec; /**< @brief Keep Alive interval. */ uint32_t pingReqSendTimeMs; /**< @brief Timestamp of the last sent PINGREQ. */ bool waitingForPingResp; /**< @brief If the library is currently awaiting a PINGRESP. */ + #if ( MQTT_VERSION_5_ENABLED ) + + /** + * @brief Connect and Connack Properties. + */ + MQTTConnectProperties_t * pConnectProperties; + + /** + * @brief To store disconnect information. + */ + MQTTAckInfo_t * pDisconnectInfo; + #endif } MQTTContext_t; /** @@ -258,6 +271,10 @@ typedef struct MQTTDeserializedInfo uint16_t packetIdentifier; /**< @brief Packet ID of deserialized packet. */ MQTTPublishInfo_t * pPublishInfo; /**< @brief Pointer to deserialized publish info. */ MQTTStatus_t deserializationResult; /**< @brief Return code of deserialization. */ + #if ( MQTT_VERSION_5_ENABLED ) + MQTTAckInfo_t * pAckInfo; /**< @brief Pointer to deserialized ack info. */ + MQTTAckInfo_t * pNextAckInfo; /**< @brief Pointer to next ack info to send. */ + #endif } MQTTDeserializedInfo_t; /** @@ -520,7 +537,7 @@ MQTTStatus_t MQTT_InitStatefulQoS( MQTTContext_t * pContext, /* @[declare_mqtt_connect] */ MQTTStatus_t MQTT_Connect( MQTTContext_t * pContext, const MQTTConnectInfo_t * pConnectInfo, - const MQTTPublishInfo_t * pWillInfo, + MQTTPublishInfo_t * pWillInfo, uint32_t timeoutMs, bool * pSessionPresent ); /* @[declare_mqtt_connect] */ @@ -625,7 +642,7 @@ MQTTStatus_t MQTT_Subscribe( MQTTContext_t * pContext, */ /* @[declare_mqtt_publish] */ MQTTStatus_t MQTT_Publish( MQTTContext_t * pContext, - const MQTTPublishInfo_t * pPublishInfo, + MQTTPublishInfo_t * pPublishInfo, uint16_t packetId ); /* @[declare_mqtt_publish] */ @@ -1017,6 +1034,24 @@ MQTTStatus_t MQTT_GetSubAckStatusCodes( const MQTTPacketInfo_t * pSubackPacket, const char * MQTT_Status_strerror( MQTTStatus_t status ); /* @[declare_mqtt_status_strerror] */ +/** + * @brief Disconnect an MQTT session. + * + * @param[in] pContext Initialized and connected MQTT context. + * @param[in] pDisconnectInfo Reason code and properties to encode + * @param[in] sessionExpiry Session expiry interval. + * @return #MQTTNoMemory if the #MQTTContext_t.networkBuffer is too small to + * hold the MQTT packet; + * #MQTTBadParameter if invalid parameters are passed; + * #MQTTSendFailed if transport send failed; + * #MQTTSuccess otherwise. + */ +/* @[declare_mqttv5_disconnect] */ +MQTTStatus_t MQTTV5_Disconnect( MQTTContext_t * pContext, + MQTTAckInfo_t * pDisconnectInfo, + uint32_t sessionExpiry ); +/* @[declare_mqttv5_disconnect] */ + /* *INDENT-OFF* */ #ifdef __cplusplus } diff --git a/source/include/core_mqtt_config_defaults.h b/source/include/core_mqtt_config_defaults.h index 3fd79f974..3e7596520 100644 --- a/source/include/core_mqtt_config_defaults.h +++ b/source/include/core_mqtt_config_defaults.h @@ -68,6 +68,34 @@ #define MQTT_DO_NOT_USE_CUSTOM_CONFIG #endif +/** + * @ingroup mqtt_constants + * @brief Version of the MQTT protocol with default value as 3.1.1 + */ +#ifndef MQTT_VERSION_5_ENABLED + #define MQTT_VERSION_5_ENABLED ( false ) + +/** + * @ingroup mqtt_constants + * @brief Maximum number of user properties with default value as 5. + */ + #ifndef MAX_USER_PROPERTY + #define MAX_USER_PROPERTY ( 5U ) + #endif + /*Disable the user properties if max user property is set to 0.*/ + #if ( MAX_USER_PROPERTY == 0U ) + +/** + * @ingroup mqtt_constants + * @brief Enable and disable user properties. + */ + #define MQTT_USER_PROPERTY_ENABLED ( false ) + #else + #define MQTT_USER_PROPERTY_ENABLED ( true ) + #endif + +#endif /* ifndef MQTT_VERSION_5_ENABLED */ + /** * @ingroup mqtt_constants * @brief Maximum number of vectors in subscribe and unsubscribe packet. diff --git a/source/include/core_mqtt_serializer.h b/source/include/core_mqtt_serializer.h index 4837ee654..151ce76da 100644 --- a/source/include/core_mqtt_serializer.h +++ b/source/include/core_mqtt_serializer.h @@ -43,6 +43,7 @@ /* *INDENT-ON */ #include "transport_interface.h" +#include "core_mqtt_config_defaults.h" /* MQTT packet types. */ @@ -79,6 +80,10 @@ struct MQTTSubscribeInfo; struct MQTTPublishInfo; struct MQTTPacketInfo; +struct MQTTConnectProperties; +struct MQTTUserProperty; +struct MQTTUserProperties; +struct MQTTAuthInfo; /** * @ingroup mqtt_enum_types * @brief Return codes from MQTT functions. @@ -96,9 +101,13 @@ typedef enum MQTTStatus MQTTIllegalState, /**< An illegal state in the state record. */ MQTTStateCollision, /**< A collision with an existing state record entry. */ MQTTKeepAliveTimeout, /**< Timeout while waiting for PINGRESP. */ - MQTTNeedMoreBytes /**< MQTT_ProcessLoop/MQTT_ReceiveLoop has received + MQTTNeedMoreBytes, /**< MQTT_ProcessLoop/MQTT_ReceiveLoop has received incomplete data; it should be called again (probably after a delay). */ + + MQTTMalformedPacket=0x81,/**A malformed packet was received from the server. */ + MQTTProtocolError=0x82 /**A packet with protocol error was received from the server. */ + } MQTTStatus_t; /** @@ -194,6 +203,230 @@ typedef struct MQTTSubscribeInfo uint16_t topicFilterLength; } MQTTSubscribeInfo_t; + + /** + * @ingroup mqtt_struct_types + * @brief Struct to hold authentication method and authentication data. + */ +typedef struct MQTTAuthInfo +{ + /** + * @brief Authentication method used for authentication. + */ + const char* pAuthMethod; + /** + * @brief Length of the authentication mathod. + */ + uint16_t authMethodLength; + /** + * @brief Authentication data used for authentication. + */ + const char* pAuthData; + /** + * @brief Length of the authentication data. + */ + uint16_t authDataLength; +} MQTTAuthInfo_t; + +#if(MQTT_USER_PROPERTY_ENABLED) + /** + * @ingroup mqtt_struct_types + * @brief Struct to hold user property. + */ +typedef struct MQTTUserProperty +{ + /** + * @brief key. + */ + const char* pKey; + /** + * @brief Length of the key. + */ + uint16_t keyLength; + /** + * @brief value. + */ + const char* pValue; + /** + * @brief Length of the value. + */ + uint16_t valueLength; +} MQTTUserProperty_t; + + /** + * @ingroup mqtt_struct_types + * @brief Struct to hold user property. + */ +typedef struct MQTTUserProperties +{ + /** + * @brief Array to store the user properties. + */ + MQTTUserProperty_t userProperty[MAX_USER_PROPERTY]; + /** + * @brief Number of user property; + */ + uint32_t count; + +} MQTTUserProperties_t; + +#endif + /** + * @ingroup mqtt_struct_types + * @brief Struct to hold connect and connack properties. + */ +typedef struct MQTTConnectProperties +{ + /** + * @brief Four Byte Integer representing the Session Expiry Interval in seconds. + */ + uint32_t sessionExpiry; + /** + * @brief Maximum number of unacknowledged PUBLISH packets client is willing to receive. + */ + uint16_t receiveMax; + /** + * @brief Four Byte Integer representing the Maximum Packet Size the Client is willing to accept. + */ + uint32_t maxPacketSize; + /** + * @brief Two Byte Integer representing the Topic Alias Maximum value. + */ + uint16_t topicAliasMax; + /** + * @brief A value of 0 indicates that the Server MUST NOT return Response Information. + */ + bool requestResponseInfo; + /** + * @brief The Client uses this value to indicate whether the Reason String or User Properties + * are sent in the case of failures + */ + bool requestProblemInfo; + /** + * @brief Length of the connect properties. + */ + size_t propertyLength; + /** + * @brief Pointer to the incoming authentication information. + */ + MQTTAuthInfo_t *pOutgoingAuth; + + /** + * @brief Maximum number of unacknowledged PUBLISH packets client is willing to receive. + */ + uint16_t serverReceiveMax; + /** + * @brief Max qos supported by the server. + */ + uint8_t serverMaxQos; + /** + * @brief Byte declares whether the Server supports retained messages. + */ + uint8_t retainAvailable; + /** + * @brief Four Byte Integer representing the Maximum Packet Size the Server is willing to accept. + */ + uint32_t serverMaxPacketSize; + /** + * @brief Client identifier assigned by the client. + */ + const char* pClientIdentifier; + /** + * @brief Length of the assigned client identifier. + */ + uint16_t clientIdLength; + /** + * @brief Two Byte Integer representing the Topic Alias Maximum value. + */ + uint16_t serverTopicAliasMax; + /** + * @brief Reason String is a human readable string designed for diagnostics. + */ + const char* pReasonString; + /** + * @brief Length of reason string. + */ + uint16_t reasonStringLength; + /** + * @brief Whether wildcard subscription is available. + */ + uint8_t isWildcardAvaiable; + /** + * @brief Whether the Server supports Subscription Identifiers. + */ + uint8_t subscriptionId; + /** + * @brief Whether the Server supports Shared Subscription. + */ + uint8_t isSharedAvailable; + /** + * @brief Keep Alive value given by the server. + */ + uint16_t serverKeepAlive; + /** + * @brief UTF-8 Encoded String which is used as the basis for creating a Response Topic. + */ + const char* pResponseInfo; + /** + * @brief Length of the response information. + */ + uint16_t responseInfoLength; + /** + * @brief UTF-8 Encoded String which can be used by the Client to identify another Server to use + */ + const char* pServerRef; + /** + * @brief Length of the server reference. + */ + uint16_t serverRefLength; + /** + * @brief Pointer to the incoming authentication information. + */ + MQTTAuthInfo_t *pIncomingAuth; + #if(MQTT_USER_PROPERTY_ENABLED) + /** + * @brief Pointer to the outgoing user properties. + */ + MQTTUserProperties_t *pOutgoingUserProperty; + /** + * @brief Pointer to the incoming user properties. + */ + MQTTUserProperties_t *pIncomingUserProperty; + #endif + +} MQTTConnectProperties_t; + + /** + * @ingroup mqtt_struct_types + * @brief Struct to hold acknowledgment properties. + */ +typedef struct MQTTAckInfo +{ + /** + * @brief Response code; + */ + uint8_t reasonCode; + /** + * @brief Property Length. + */ + size_t propertyLength; + #if(MQTT_USER_PROPERTY_ENABLED) + /** + * @brief To store a key value pair. + */ + MQTTUserProperties_t* pUserProperty; + #endif + /** + * @brief Reason String is a human readable string designed for diagnostics. + */ + const char* pReasonString; + /** + * @brief Length of reason string. + */ + uint16_t reasonStringLength; +} MQTTAckInfo_t; + + /** * @ingroup mqtt_struct_types * @brief MQTT PUBLISH packet parameters. @@ -234,6 +467,64 @@ typedef struct MQTTPublishInfo * @brief Message payload length. */ size_t payloadLength; + +#if (MQTT_VERSION_5_ENABLED) + /** + * @brief Length of the properties. + */ + size_t propertyLength; + /** + * @brief Four Byte Integer representing the Will Delay Interval in seconds. + */ + uint32_t willDelay; + /** + * @brief Payload Format Indicator. + **/ + uint8_t payloadFormat; + /** + * @brief Topic alias value. + **/ + uint16_t topicAlias; + /** + * @brief Four Byte Integer representing the Message Expiry Interval. + */ + uint32_t msgExpiryInterval; + /** + * @brief Whether the message expiry is specified. + */ + bool msgExpiryPresent; + /** + * @brief Length of the content type. + */ + uint16_t contentTypeLength; + /** + * @brief UTF-8 Encoded String describing the content of the Will Message. + */ + const char *pContentType; + /** + * @brief Length of the response topic. + */ + uint16_t responseTopicLength; + /** + * @brief UTF-8 Encoded String which is used as the Topic Name for a response message. + */ + const char *pResponseTopic; + /** + * @brief Length of the correlation data. + */ + uint16_t correlationLength; + /** + * @brief To identify which request the Response Message is for. + */ + const void *pCorrelationData; + #if(MQTT_USER_PROPERTY_ENABLED) + /** + * @brief Pointer to the user properties. + */ + MQTTUserProperties_t* pUserProperty; + #endif +#endif + } MQTTPublishInfo_t; /** @@ -675,7 +966,7 @@ MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, * consider using #MQTT_SerializePublishHeader, which will serialize * only the PUBLISH header into the buffer. * - * #MQTT_GetPublishPacketSize should be called with @p pPublishInfo before + * #MQTT_GetPublishPacketSize or #MQTTV5_GetPublishPacketSize (for version 5 users) should be called with @p pPublishInfo before * invoking this function to get the size of the required #MQTTFixedBuffer_t and * @p remainingLength. The @p remainingLength must be the same as returned by * #MQTT_GetPublishPacketSize. The #MQTTFixedBuffer_t must be at least as large @@ -712,6 +1003,7 @@ MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, * status = MQTT_GetPublishPacketSize( * &publishInfo, &remainingLength, &packetSize * ); + * // or call MQTTV5_GetPublishPacketSize * assert( status == MQTTSuccess ); * assert( packetSize <= BUFFER_SIZE ); * @@ -1285,6 +1577,718 @@ uint8_t * MQTT_SerializeUnsubscribeHeader( size_t remainingLength, uint16_t packetId ); /** @endcond */ +/** + * @fn uint8_t * MQTTV5_SerializeConnectProperties(uint8_t * pIndex,const MQTTConnectProperties_t * pConnectProperties); + * @brief Serialize the connect properties of the connect packet header. + * + * @param[out] pIndex Pointer to the buffer where the header is to + * be serialized. + * @param[in] pConnectProperties The connect properties information. + * + * @return A pointer to the end of the encoded string. + */ + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this definition, this function is private. + */ +uint8_t * MQTTV5_SerializeConnectProperties( uint8_t * pIndex, + const MQTTConnectProperties_t * pConnectProperties); +/** @endcond */ + + +/** + * @fn uint8_t * MQTT_SerializePublishProperties(const MQTTPublishInfo_t * pPublishInfo, uint8_t * pIndex); + * @brief Serialize the will properties of the connect packet. + * @param[in] pPublishInfo The publish/will properties information. + * @param[out] pIndex Pointer to the buffer where the header is to + * be serialized. + * + * @return A pointer to the end of the encoded string. + */ + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this definition, this function is private. + */ +uint8_t * MQTT_SerializePublishProperties( const MQTTPublishInfo_t * pPublishInfo, + uint8_t * pIndex); +/** @endcond */ + + +/** + * @brief Deserialize an MQTT CONNACK packet. + * + * @param[out] pConnackProperties To store the deserialized connack properties. + * @param[in] pIncomingPacket #MQTTPacketInfo_t containing the buffer. + * @param[out] pSessionPresent Whether a previous session was present. + * + * @return #MQTTBadParameter, #MQTTBadResponse, #MQTTSuccess, #MQTTMalformedPacket, #MQTTProtocolError, #MQTTServerRefused + * + * Example + * @code{c} + * + * // TransportRecv_t function for reading from the network. + * int32_t socket_recv( + * NetworkContext_t * pNetworkContext, + * void * pBuffer, + * size_t bytesToRecv + * ); + * // Some context to be used with the above transport receive function. + * NetworkContext_t networkContext; + * + * // Other variables used in this example. + * MQTTStatus_t status; + * MQTTPacketInfo_t incomingPacket; + * MQTTConnectProperties_t properties = {0}; + * uint16_t packetId; + * + * int32_t bytesRecvd; + * // A buffer to hold remaining data of the incoming packet. + * uint8_t buffer[ BUFFER_SIZE ]; + * + * // Populate all fields of the incoming packet. + * status = MQTT_GetIncomingPacketTypeAndLength( + * socket_recv, + * &networkContext, + * &incomingPacket + * ); + * assert( status == MQTTSuccess ); + * assert( incomingPacket.remainingLength <= BUFFER_SIZE ); + * bytesRecvd = socket_recv( + * &networkContext, + * ( void * ) buffer, + * incomingPacket.remainingLength + * ); + * incomingPacket.pRemainingData = buffer; + * + * // Deserialize the publish information if the incoming packet is a publish. + * if( ( incomingPacket.type & 0xF0 ) == MQTT_PACKET_TYPE_CONNACK ) + * { + * status = MQTT_DeserializeConnack(&properties, &incomingPacket, &session); + * if( status == MQTTSuccess ) + * { + * // The deserialized connack information can now be used from `properties`. + * } + * } + * @endcode + */ +/* @[declare_mqttv5_deserializeconnack] */ +MQTTStatus_t MQTTV5_DeserializeConnack( MQTTConnectProperties_t *pConnackProperties, + const MQTTPacketInfo_t * pIncomingPacket, + bool * pSessionPresent ); +/* @[declare_mqttv5_deserializeconnack] */ + +/** + * @brief Initialize an MQTTConnectProperties_t. + * + * This function can be called on an #MQTTConnectProperties_t to set the properties to their default value before calling MQTT_Connect or #MQTTV5_GetConnectPacketSize. + * + * @param[in] pConnectProperties The connect properties to initialize. + * + * @return #MQTTBadParameter if invalid parameters are passed; + * #MQTTSuccess otherwise. + * + * Example + * @code{c} + * + * MQTTConnectProperties_t connectProperties; + * + * // Clear context. + * memset( ( void * ) &connectProperties, 0x00, sizeof( MQTTConnectProperties_t ) ); + * + * status = MQTTV5_InitConnect(&connectProperties); + * + * if( status == MQTTSuccess ) + * { + * //Set the values for connect properties. + * } + * @endcode + */ +/* @[declare_mqttv5_initconnect] */ +MQTTStatus_t MQTTV5_InitConnect(MQTTConnectProperties_t *pConnectProperties); +/* @[declare_mqttv5_initconnect] */ + +/** + * @brief Get the size and Remaining Length of an MQTT Version 5 CONNECT packet . + * + * This function must be called before #MQTTV5_SerializeConnect in order to get + * the size of the MQTT CONNECT packet that is generated from #MQTTConnectInfo_t, + * MQTTConnectProperties_t and optional #MQTTPublishInfo_t. The size of the #MQTTFixedBuffer_t supplied + * to #MQTT_SerializeConnect must be at least @p pPacketSize. The provided + * @p pConnectInfo, @p pConnectProperties and @p pWillInfo are valid for serialization with + * #MQTT_SerializeConnect only if this function returns #MQTTSuccess. The + * remaining length returned in @p pRemainingLength and the packet size returned + * in @p pPacketSize are valid only if this function returns #MQTTSuccess. + * + * @param[in] pConnectInfo MQTT CONNECT packet parameters. + * @param[in] pWillInfo Last Will and Testament. Pass NULL if not used. + * @param[in] pConnectProperties MQTT CONNECT properties parameters. + * @param[out] pRemainingLength The Remaining Length of the MQTT CONNECT packet. + * @param[out] pPacketSize The total size of the MQTT CONNECT packet. + * + * @return #MQTTBadParameter if the packet would exceed the size allowed by the + * MQTT spec; #MQTTSuccess otherwise. + * + * Example + * @code{c} + * + * // Variables used in this example. + * MQTTStatus_t status; + * MQTTConnectInfo_t connectInfo = { 0 }; + * MQTTPublishInfo_t willInfo = { 0 }; + * MQTTConnectProperties_t connectProperties ={0}; + * size_t remainingLength = 0, packetSize = 0; + * + * // Initialize the connection info, the details are out of scope for this example. + * initializeConnectInfo( &connectInfo ); + * + * // Initialize the optional will info, the details are out of scope for this example. + * initializeWillInfo( &willInfo ); + * + * // Initialize the connection properties, the details are out of scope for this example. + * initializeConnectProperties( &connectProperties ); + * + * // Get the size requirement for the connect packet. + * status = MQTTV5_GetConnectPacketSize( + * &connectInfo, &willInfo, &connectProperties, &remainingLength, &packetSize + * ); + * + * if( status == MQTTSuccess ) + * { + * // The application should allocate or use a static #MQTTFixedBuffer_t + * // of size >= packetSize to serialize the connect request. + * } + * @endcode + */ +/* @[declare_mqttv5_getconnectpacketsize] */ +MQTTStatus_t MQTTV5_GetConnectPacketSize(const MQTTConnectInfo_t* pConnectInfo, + MQTTPublishInfo_t* pWillInfo, + MQTTConnectProperties_t* pConnectProperties, + size_t* pRemainingLength, + size_t* pPacketSize); +/* @[declare_mqttv5_getconnectpacketsize] */ + +/** + * @brief Serialize an MQTT CONNECT packet in the given fixed buffer @p pFixedBuffer. + * + * #MQTTV5_GetConnectPacketSize should be called with @p pConnectInfo, @p pConnectProperties and + * @p pWillInfo before invoking this function to get the size of the required + * #MQTTFixedBuffer_t and @p remainingLength. The @p remainingLength must be + * the same as returned by #MQTTV5_GetConnectPacketSize. The #MQTTFixedBuffer_t + * must be at least as large as the size returned by #MQTTV5_GetConnectPacketSize. + * + * @param[in] pConnectInfo MQTT CONNECT packet parameters. + * @param[in] pWillInfo Last Will and Testament. Pass NULL if not used. + * @param[in] pConnectProperties MQTT CONNECT properties parameters. + * @param[in] remainingLength Remaining Length provided by #MQTTV5_GetConnectPacketSize. + * @param[out] pFixedBuffer Buffer for packet serialization. + * + * @return #MQTTNoMemory if pFixedBuffer is too small to hold the MQTT packet; + * #MQTTBadParameter if invalid parameters are passed; + * #MQTTSuccess otherwise. + * + * Example + * @code{c} + * + * // Variables used in this example. + * MQTTStatus_t status; + * MQTTConnectInfo_t connectInfo = { 0 }; + * MQTTPublishInfo_t willInfo = { 0 }; + * MQTTConnectProperties_t connectProperties ={0}; + * MQTTFixedBuffer_t fixedBuffer; + * uint8_t buffer[ BUFFER_SIZE ]; + * size_t remainingLength = 0, packetSize = 0; + * + * fixedBuffer.pBuffer = buffer; + * fixedBuffer.size = BUFFER_SIZE; + * + * // Assume connectInfo and willInfo are initialized. Get the size requirement for + * // the connect packet. + * status = MQTTV5_GetConnectPacketSize( + * &connectInfo, &willInfo,&connectProperties &remainingLength, &packetSize + * ); + * assert( status == MQTTSuccess ); + * assert( packetSize <= BUFFER_SIZE ); + * + * // Serialize the connect packet into the fixed buffer. + * status = MQTTV5_SerializeConnect( &connectInfo, &willInfo,&connectProperties,remainingLength, &fixedBuffer ); + * + * if( status == MQTTSuccess ) + * { + * // The connect packet can now be sent to the broker. + * } + * @endcode + */ +/* @[declare_mqttv5_serializeconnect] */ +MQTTStatus_t MQTTV5_SerializeConnect(const MQTTConnectInfo_t* pConnectInfo, + const MQTTPublishInfo_t* pWillInfo, + const MQTTConnectProperties_t *pConnectProperties, + size_t remainingLength, + const MQTTFixedBuffer_t* pFixedBuffer); +/* @[declare_mqttv5_serializeconnect] */ + + +/** + * @brief Validate the publish parameters present in the given publish structure @p pPublishInfo. + * + * This function must be called before #MQTTV5_GetPublishPacketSize in order to validate the publish parameters. + * + * @param[in] pPublishInfo MQTT publish packet parameters. + * @param[in] topicAliasMax Maximum topic alias allowed by the server. + * @param[in] retainAvailable Whether server allows retain or not. + * @param[in] maxQos Maximum QoS supported by the server. + * + * @return #MQTTBadParameter if invalid parameters are passed; + * #MQTTSuccess otherwise. + * + * Example + * @code{c} + * + * // Variables used in this example. + * MQTTStatus_t status; + * MQTTPublishInfo_t publishInfo = {0}; + * uint16_t topicAliasMax; + * uint8_t retainAvailable; + * uint8_t maxQos; + * + * //Set the publish info parameters. + * + * //Validate the publish packet + * status = MQTTV5_ValidatePublishParams(&publishInfo,topicAliasMax,retainAvailable,maxQos); + * + * if( status == MQTTSuccess ) + * { + * // Get the packet size and serialize the publish packet. + * } + * @endcode + */ +/* @[declare_mqttv5_validatepublishparams] */ +MQTTStatus_t MQTTV5_ValidatePublishParams(const MQTTPublishInfo_t* pPublishInfo, uint16_t topicAliasMax, uint8_t retainAvailable, uint8_t maxQos); +/* @[declare_mqttv5_validatepublishparams] */ + + +/** + * @brief Get the packet size and remaining length of an MQTT PUBLISH packet. + * + * #MQTTV5_ValidatePublishParams should be called with @p pPublishInfo before invoking this function + * to validate the publish parameters.This function must be called before #MQTT_SerializePublish + * in order to get the size of the MQTT PUBLISH packet that is generated from #MQTTPublishInfo_t. + * The size of the #MQTTFixedBuffer_t supplied to #MQTT_SerializePublish must be + * at least @p pPacketSize. The provided @p pPublishInfo is valid for + * serialization with #MQTT_SerializePublish only if this function returns + * #MQTTSuccess. The remaining length returned in @p pRemainingLength and the + * packet size returned in @p pPacketSize are valid only if this function + * returns #MQTTSuccess. + * + * @param[in] pPublishInfo MQTT PUBLISH packet parameters. + * @param[out] pRemainingLength The Remaining Length of the MQTT PUBLISH packet. + * @param[out] pPacketSize The total size of the MQTT PUBLISH packet. + * @param[in] maxPacketSize Maximum packet size allowed by the server. + * + * @return #MQTTBadParameter if the packet would exceed the size allowed by the + * MQTT spec or if invalid parameters are passed; #MQTTSuccess otherwise. + * + * Example + * @code{c} + * + * // Variables used in this example. + * MQTTStatus_t status; + * MQTTPublishInfo_t publishInfo = { 0 }; + * uint16_t topicAliasMax; + * uint8_t retainAvailable; + * uint8_t maxQos; + * size_t remainingLength = 0, packetSize = 0; + * + * // Initialize the publish info. + * publishInfo.qos = MQTTQoS0; + * publishInfo.pTopicName = "/some/topic/name"; + * publishInfo.topicNameLength = strlen( publishInfo.pTopicName ); + * publishInfo.pPayload = "Hello World!"; + * publishInfo.payloadLength = strlen( "Hello World!" ); + * + * //Set the publish properties. + * + * status = MQTTV5_ValidatePublishParams(&publishInfo,topicAliasMax,retainAvailable,maxQos); + + * // Get the size requirement for the publish packet. + * status = MQTTV5_GetPublishPacketSize( + * &publishInfo, &remainingLength, &packetSize, maxPacketSize + * ); + * + * if( status == MQTTSuccess ) + * { + * // The application should allocate or use a static #MQTTFixedBuffer_t + * // of size >= packetSize to serialize the publish. + * } + * @endcode + */ +/* @[declare_mqttv5_getpublishpacketsize] */ +MQTTStatus_t MQTTV5_GetPublishPacketSize(MQTTPublishInfo_t * pPublishInfo, + size_t * pRemainingLength, + size_t * pPacketSize , + uint32_t maxPacketSize); +/* @[declare_mqttv5_getpublishpacketsize] */ + + +/** + * @brief Deserialize an MQTT PUBACK, PUBREC, PUBREL, PUBCOMP, or PINGRESP. + * + * @param[in] pIncomingPacket #MQTTPacketInfo_t containing the buffer. + * @param[out] pPacketId The packet ID of obtained from the buffer. + * @param[out] pAckInfo Struct to store the deserialized ack information. + * @param[in] requestProblem Request problem value set in the connect packet. + * @param[in] maxPacketSize Maximum packet size allowed by the client. + * + * @return #MQTTBadParameter, #MQTTBadResponse, #MQTTServerRefused, #MQTTProtocolError, #MQTTMalformedPacket or #MQTTSuccess. + * + * Example + * @code{c} + * + * // Variables used in this example. + * MQTTStatus_t status; + * MQTTPacketInfo_t incomingPacket; + * uint16_t packetId; + * MQTTAckInfo_t ackInfo = {0}; + * bool requestProblem; + * uint32_t maxPacketSize; + * bool sessionPresent; + * + * // Receive an incoming packet and populate all fields. The details are out of scope + * // for this example. + * receiveIncomingPacket( &incomingPacket ); + * + * // Deserialize ack information if the incoming packet is a publish ack. + * status = MQTTV5_DeserializeAck(&incomingPacket, + &packetId, + &ackInfo, + requestProblem, + maxPacketSize); + * if( status == MQTTSuccess ) + * { + * // Ack information is now available. + * } + * } + * @endcode + */ +/* @[declare_mqttv5_deserializeack] */ +MQTTStatus_t MQTTV5_DeserializeAck( const MQTTPacketInfo_t * pIncomingPacket, + uint16_t * pPacketId, MQTTAckInfo_t *pAckInfo, bool requestProblem,uint32_t maxPacketSize); +/* @[declare_mqttv5_deserializeack] */ + +/** + * @brief Get the size of an MQTT DISCONNECT packet. + * + * @param[in] pAckInfo MQTT PUBLISH ACK packet parameters. + * @param[out] pRemainingLength The Remaining Length of the MQTT DISCONNECT packet. + * @param[out] pPacketSize The size of the MQTT DISCONNECT packet. + * @param[in] maxPacketSize Maximum packet size allowed by the server. + * + * @return #MQTTSuccess, or #MQTTBadParameter if parameters are invalid + * + * Example + * @code{c} + * + * // Variables used in this example. + * MQTTStatus_t status; + * MQTTAckInfo_t disconnectInfo ={0}; + * size_t remainingLength =0; + * size_t packetSize = 0; + * uint32_t maxPacketSize; + * + * //Set the parameters. + * // Get the size requirement for the disconnect packet. + * status = MQTTV5_GetAckPacketSize(&disconnectInfo,&remainingLength,&packetSize,maxPacketSize); + * + * if( status == MQTTSuccess ) + * { + * // The application should allocate or use a static #MQTTFixedBuffer_t + * // of size >= packetSize to serialize the ack packet. + * } + * @endcode + */ +/* @[declare_mqttv5_getackpacketsize] */ +MQTTStatus_t MQTTV5_GetAckPacketSize( MQTTAckInfo_t *pAckInfo, size_t* pRemainingLength,size_t * pPacketSize, uint32_t maxPacketSize); +/* @[declare_mqttv5_getackpacketsize] */ + +/** + * @fn uint8_t * MQTTV5_SerializeAckFixed(uint8_t * pIndex, + uint8_t packetType, + uint16_t packetId, + size_t remainingLength, + size_t propertyLength); + * @brief Serialize the fixed size part of the ack packet header. + * + * @param[out] pIndex Pointer to the buffer where the header is to + * be serialized. + * @param[in] packetType Type of publish ack + * @param[in] packetId Packed identifier of the ack packet. + * @param[in] remainingLength Remaining length of the ack packet. + * @param[in] propertyLength Property length of the ack packet. + * + * + * @return A pointer to the end of the encoded string. + */ + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this definition, this function is private. + */ +uint8_t * MQTTV5_SerializeAckFixed(uint8_t * pIndex, + uint8_t packetType, + uint16_t packetId, + size_t remainingLength, + size_t propertyLength); +/** @endcond */ + +/** + * @brief Serialize an MQTT DISCONNECT packet into the given buffer. + * + * The input #MQTTFixedBuffer_t.size must be at least as large as the size + * returned by #MQTTV5_GetAckPacketSize. + * + * @note If reason code is success and property length is zero then #MQTT_SerializeAck can also be used. + * + * @param[in] pAckInfo Struct containing information about the publish ack. + * @param[in] remainingLength The remaining length of the packet to be + * serialized. + * @param[out] pFixedBuffer Buffer for packet serialization. + * @param[in] packetType Type of publish ack. + * @param[in] packetId Packed identifier of the ack packet. + * + * @return #MQTTNoMemory if pFixedBuffer is too small to hold the MQTT packet; + * #MQTTBadParameter if invalid parameters are passed; + * #MQTTSuccess otherwise. + * + * Example + * @code{c} + * + * // Variables used in this example. + * MQTTStatus_t status; + * MQTTFixedBuffer_t fixedBuffer; + * uint8_t buffer[ BUFFER_SIZE ]; + * MQTTAckInfo_t ackInfo; + * uint16_t sessionExpiry; + * + * fixedBuffer.pBuffer = buffer; + * fixedBuffer.size = BUFFER_SIZE; + * // Variables used in this example. + * MQTTStatus_t status; + * MQTTAckInfo_t disconnectInfo ={0}; + * size_t remainingLength =0; + * size_t packetSize = 0; + * uint8_t packetType; + * uint8_t packedId; + * uint32_t maxPacketSize; + * //set the parameters. + * // Get the size requirement for the ack packet. + * status = MQTTV5_GetAckPacketSize(&disconnectInfo,&remainingLength,&packetSize,maxPacketSize); + * + * + * assert( status == MQTTSuccess ); + * assert( packetSize <= BUFFER_SIZE ); + * + * // Serialize the disconnect into the fixed buffer. + * MQTTV5_SerializePubAckWithProperty( &ackInfo, + remainingLength, + &fixedBuffer, + packetType, + packetId); + * * if( status == MQTTSuccess ) + * { + * // The ack packet can now be sent to the broker. + * } + * + * @endcode + */ +/* @[declare_mqttv5_serializepubackwithproperty] */ +MQTTStatus_t MQTTV5_SerializePubAckWithProperty( const MQTTAckInfo_t *pAckInfo, + size_t remainingLength, + const MQTTFixedBuffer_t * pFixedBuffer, + uint8_t packetType, + uint16_t packetId); +/* @[declare_mqttv5_serializepubackwithproperty] */ + + +/** + * @brief Get the size of an MQTT DISCONNECT packet. + * + * @param[in] pDisconnectInfo MQTT DISCONNECT packet parameters. + * @param[out] pRemainingLength The Remaining Length of the MQTT DISCONNECT packet. + * @param[out] pPacketSize The size of the MQTT DISCONNECT packet. + * @param[in] maxPacketSize Maximum packet size allowed by the server. + * @param[in] sessionExpiry Session expiry interval in the disconnect packet. + * @param[in] prevSessionExpiry Session expiry interval set in the connect packet. + * + * @return #MQTTSuccess, or #MQTTBadParameter if parameters are invalid + * + * Example + * @code{c} + * + * // Variables used in this example. + * MQTTStatus_t status; + * MQTTAckInfo_t disconnectInfo ={0}; + * size_t remainingLength =0; + * size_t packetSize = 0; + * uint32_t maxPacketSize; + * uint32_t sessionExpiry; + * uint32_t prevSessionExpiry + * + * //Set the parameters. + * // Get the size requirement for the disconnect packet. + * status = MQTTV5_GetDisconnectPacketSize(&disconnectInfo,&remainingLength,&packetSize,maxPacketSize, sessionExpiry,prevSessionExpiry ); + * + * if( status == MQTTSuccess ) + * { + * // The application should allocate or use a static #MQTTFixedBuffer_t + * // of size >= packetSize to serialize the disconnect. + * } + * @endcode + */ +/* @[declare_mqttv5_getdisconnectpacketsize] */ +MQTTStatus_t MQTTV5_GetDisconnectPacketSize( MQTTAckInfo_t* pDisconnectInfo, size_t * pRemainingLength, size_t * pPacketSize,uint32_t maxPacketSize, uint32_t sessionExpiry,uint32_t prevSessionExpiry ); +/* @[declare_mqttv5_getdisconnectpacketsize] */ + + +/** + * @fn uint8_t * MQTTV5_SerializeDisconnectFixed(uint8_t * pIndex, + const MQTTAckInfo_t * pDisconnectInfo, + size_t remainingLength, + uint32_t sessionExpiry); + * @brief Serialize the fixed part of the disconnect packet header. + * + * @param[out] pIndex Pointer to the buffer where the fixed size parameters is to + * be serialized. + * @param[in] pDisconnectInfo The disconnect information. + * @param[in] remainingLength The remaining length of the packet to be + * serialized. + * @param[in] sessionExpiry Session expiry to be serialized. + * + * @return A pointer to the end of the encoded string. + */ + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this definition, this function is private. + */ +uint8_t * MQTTV5_SerializeDisconnectFixed(uint8_t * pIndex, + const MQTTAckInfo_t * pDisconnectInfo, + size_t remainingLength, + uint32_t sessionExpiry); +/** @endcond */ + +/** + * @brief Deserialize an MQTT Disconnect packet. + * + * @param[in] pPacket #MQTTPacketInfo_t containing the buffer. + * @param[out] pDisconnectInfo Struct containing information about the disconnect. + * @param[out] pServerRef The server reference obtained from the buffer. + * @param[out] pServerRefLength The server reference length obtained from the buffer. + * @param[in] maxPacketSize MAximum packet size allowed by the client. + * + * @return #MQTTBadParameter, #MQTTBadResponse, #MQTTServerRefused, #MQTTProtocolError, #MQTTMalformedPacket or #MQTTSuccess. + * + * Example + * @code{c} + * + * // Variables used in this example. + * MQTTStatus_t status; + * MQTTPacketInfo_t incomingPacket; + * MQTTAckInfo_t disconnectInfo; + * const char * serverRef; + * uint16_t serverRefLength; + * uint32_t maxPacketSize; + * // Receive an incoming packet and populate all fields. The details are out of scope + * // for this example. + * receiveIncomingPacket( &incomingPacket ); + * + * // Deserialize disconnect information. + * if( ( incomingPacket.type) == MQTT_PACKET_TYPE_DISCONNECT ) + * { + * status = MQTTV5_DeserializeDisconnect(&incomingPacket, + &disconnectInfo, + &serverRef, + &serverRefLength, + maxPacketSize ); + * if( status == MQTTSuccess ) + * { + * // Disconnect information is available. + * } + * } + * @endcode + */ +/* @[declare_mqttv5_deserializedisconnect] */ + MQTTStatus_t MQTTV5_DeserializeDisconnect( const MQTTPacketInfo_t * pPacket, + MQTTAckInfo_t *pDisconnectInfo, + const char ** pServerRef, + uint16_t * pServerRefLength, + uint32_t maxPacketSize ); +/* @[declare_mqttv5_deserializedisconnect] */ + + +/** + * @brief Serialize an MQTT DISCONNECT packet into the given buffer. + * + * The input #MQTTFixedBuffer_t.size must be at least as large as the size + * returned by #MQTTV5_GetDisconnectPacketSize. + * + * @param[in] pDisconnectInfo Struct containing information about the disconnect. + * @param[in] remainingLength The remaining length of the packet to be + * serialized. + * @param[out] pFixedBuffer Buffer for packet serialization. + * @param[in] sessionExpiry Session expiry to be serialized. + * + * @return #MQTTNoMemory if pFixedBuffer is too small to hold the MQTT packet; + * #MQTTBadParameter if invalid parameters are passed; + * #MQTTSuccess otherwise. + * + * Example + * @code{c} + * + * // Variables used in this example. + * MQTTStatus_t status; + * MQTTFixedBuffer_t fixedBuffer; + * uint8_t buffer[ BUFFER_SIZE ]; + * MQTTAckInfo_t disconnectInfo; + * uint16_t sessionExpiry; + * + * fixedBuffer.pBuffer = buffer; + * fixedBuffer.size = BUFFER_SIZE; + * // Variables used in this example. + * MQTTStatus_t status; + * MQTTAckInfo_t disconnectInfo ={0}; + * size_t remainingLength =0; + * size_t packetSize = 0; + * uint32_t maxPacketSize; + * uint32_t sessionExpiry; + * uint32_t prevSessionExpiry + * + * //set the parameters. + * // Get the size requirement for the disconnect packet. + * status = MQTTV5_GetDisconnectPacketSize(&disconnectInfo,&remainingLength,&packetSize,maxPacketSize, sessionExpiry,prevSessionExpiry ); + * + * assert( status == MQTTSuccess ); + * assert( packetSize <= BUFFER_SIZE ); + * + * // Serialize the disconnect into the fixed buffer. + * MQTTV5_SerializeDisconnectWithProperty( &disconnectInfo, + remainingLength, + &fixedBuffer, + sessionExpiry); + * + * if( status == MQTTSuccess ) + * { + * // The disconnect packet can now be sent to the broker. + * } + * @endcode + */ +/* @[declare_mqttv5_serializedisconnectwithproperty] */ +MQTTStatus_t MQTTV5_SerializeDisconnectWithProperty( const MQTTAckInfo_t *pDisconnectInfo, + size_t remainingLength, + const MQTTFixedBuffer_t * pFixedBuffer, + uint32_t sessionExpiry); +/* @[declare_mqttv5_serializedisconnectwithproperty] */ + /* *INDENT-OFF* */ #ifdef __cplusplus } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 675bce541..6d8aae2a5 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -52,6 +52,9 @@ if( COV_ANALYSIS ) # Build MQTT library target without custom config dependency. target_compile_definitions( coverity_analysis PUBLIC MQTT_DO_NOT_USE_CUSTOM_CONFIG=1 ) + target_compile_definitions( coverity_analysis PUBLIC MQTT_VERSION_5_ENABLED=1 ) + target_compile_definitions( coverity_analysis PUBLIC MAX_USER_PROPERTY=5) + target_compile_definitions( coverity_analysis PUBLIC MQTT_USER_PROPERTY_ENABLED=1) # MQTT public include path. target_include_directories( coverity_analysis PUBLIC ${MQTT_INCLUDE_PUBLIC_DIRS} ) @@ -93,7 +96,11 @@ if( UNITTEST ) include( ${MODULE_ROOT_DIR}/tools/cmock/create_test.cmake ) # Include build configuration for unit tests. - add_subdirectory( unit-test ) + add_subdirectory( unit-test/MQTT) + add_subdirectory( unit-test/MQTTv5) + add_subdirectory( unit-test/WithoutUP) + + # ==================================== Coverage Analysis configuration ======================================== diff --git a/test/unit-test/CMakeLists.txt b/test/unit-test/MQTT/CMakeLists.txt similarity index 100% rename from test/unit-test/CMakeLists.txt rename to test/unit-test/MQTT/CMakeLists.txt diff --git a/test/unit-test/cmock_opaque_types.h b/test/unit-test/MQTT/cmock_opaque_types.h similarity index 100% rename from test/unit-test/cmock_opaque_types.h rename to test/unit-test/MQTT/cmock_opaque_types.h diff --git a/test/unit-test/core_mqtt_config.h b/test/unit-test/MQTT/core_mqtt_config.h similarity index 97% rename from test/unit-test/core_mqtt_config.h rename to test/unit-test/MQTT/core_mqtt_config.h index 24b577f57..57b97c82a 100644 --- a/test/unit-test/core_mqtt_config.h +++ b/test/unit-test/MQTT/core_mqtt_config.h @@ -43,7 +43,7 @@ * 3. Include the header file "logging_stack.h", if logging is enabled for MQTT. */ -#include "logging_levels.h" +#include "../logging/logging_levels.h" /* Logging configuration for the MQTT library. */ #ifndef LIBRARY_LOG_NAME @@ -54,7 +54,7 @@ #define LIBRARY_LOG_LEVEL LOG_NONE #endif -#include "logging_stack.h" +#include "../logging/logging_stack.h" /************ End of logging configuration ****************/ diff --git a/test/unit-test/core_mqtt_serializer_utest.c b/test/unit-test/MQTT/core_mqtt_serializer_utest.c similarity index 99% rename from test/unit-test/core_mqtt_serializer_utest.c rename to test/unit-test/MQTT/core_mqtt_serializer_utest.c index e91aae72a..84efbfc21 100644 --- a/test/unit-test/core_mqtt_serializer_utest.c +++ b/test/unit-test/MQTT/core_mqtt_serializer_utest.c @@ -1878,6 +1878,12 @@ void test_MQTT_GetIncomingPacketTypeAndLength( void ) status = MQTT_GetIncomingPacketTypeAndLength( mockReceive, &networkContext, &mqttPacket ); TEST_ASSERT_EQUAL( MQTTBadResponse, status ); + /* Test with disconnect packet type. */ + bufPtr = buffer; + buffer[ 0 ] = MQTT_PACKET_TYPE_DISCONNECT; + status = MQTT_GetIncomingPacketTypeAndLength( mockReceive, &networkContext, &mqttPacket ); + TEST_ASSERT_EQUAL( MQTTBadResponse, status ); + /* Test with invalid remaining length. */ bufPtr = buffer; buffer[ 0 ] = 0x20; /* CONN ACK */ diff --git a/test/unit-test/core_mqtt_state_utest.c b/test/unit-test/MQTT/core_mqtt_state_utest.c similarity index 100% rename from test/unit-test/core_mqtt_state_utest.c rename to test/unit-test/MQTT/core_mqtt_state_utest.c diff --git a/test/unit-test/core_mqtt_utest.c b/test/unit-test/MQTT/core_mqtt_utest.c similarity index 100% rename from test/unit-test/core_mqtt_utest.c rename to test/unit-test/MQTT/core_mqtt_utest.c diff --git a/test/unit-test/MQTTv5/CMakeLists.txt b/test/unit-test/MQTTv5/CMakeLists.txt new file mode 100644 index 000000000..3c32bf467 --- /dev/null +++ b/test/unit-test/MQTTv5/CMakeLists.txt @@ -0,0 +1,115 @@ +# Include filepaths for source and include. +include( ${MODULE_ROOT_DIR}/mqttFilePaths.cmake ) + +# ==================== Define your project name (edit) ======================== +set(project_name "core_mqttv5") + + +# ===================== Create your mock here (edit) ======================== + +# list the files to mock here +list(APPEND mock_list + "${MODULE_ROOT_DIR}/source/include/core_mqtt_serializer.h" + "${MODULE_ROOT_DIR}/source/include/core_mqtt_state.h" + ) +# list the directories your mocks need +list(APPEND mock_include_list + . + ${CMAKE_CURRENT_LIST_DIR}/logging + ${MQTT_INCLUDE_PUBLIC_DIRS} + ) +#list the definitions of your mocks to control what to be included +list(APPEND mock_define_list + "" + ) + +# ================= Create the library under test here (edit) ================== + +# list the files you would like to test here +list(APPEND real_source_files + ${MQTT_SOURCES} + ${MQTT_SERIALIZER_SOURCES} + ) +# list the directories the module under test includes +list(APPEND real_include_directories + . + ${CMAKE_CURRENT_LIST_DIR}/logging + ${MQTT_INCLUDE_PUBLIC_DIRS} + ) + +# ===================== Create UnitTest Code here (edit) ===================== + +# list the directories your test needs to include +list(APPEND test_include_directories + . + ${MQTT_INCLUDE_PUBLIC_DIRS} + ) + +# ============================= (end edit) =================================== + +set(mock_name "${project_name}_mock") +set(real_name "${project_name}_real") + +create_mock_list(${mock_name} + "${mock_list}" + "${MODULE_ROOT_DIR}/tools/cmock/project.yml" + "${mock_include_list}" + "${mock_define_list}" + ) + +create_real_library(${real_name} + "${real_source_files}" + "${real_include_directories}" + "${mock_name}" + ) + +list(APPEND utest_link_list + -l${mock_name} + lib${real_name}.a + ) + +list(APPEND utest_dep_list + ${real_name} + ) + +set(utest_name "${project_name}_utest") +set(utest_source "${project_name}_utest.c") +create_test(${utest_name} + ${utest_source} + "${utest_link_list}" + "${utest_dep_list}" + "${test_include_directories}" + ) + +# need to redefine because the tests below don't use any mocks +set(utest_link_list "") +list(APPEND utest_link_list + lib${real_name}.a + ) + +# mqtt_state_utest +set(utest_name "${project_name}_state_utest") +set(utest_source "${project_name}_state_utest.c") + +create_test(${utest_name} + ${utest_source} + "${utest_link_list}" + "${utest_dep_list}" + "${test_include_directories}" + ) + +# mqtt_serializer_utest +set(utest_name "${project_name}_serializer_utest") +set(utest_source "${project_name}_serializer_utest.c") + +set(utest_link_list "") +list(APPEND utest_link_list + lib${real_name}.a + ) + +create_test(${utest_name} + ${utest_source} + "${utest_link_list}" + "${utest_dep_list}" + "${test_include_directories}" + ) \ No newline at end of file diff --git a/test/unit-test/MQTTv5/cmock_opaque_types.h b/test/unit-test/MQTTv5/cmock_opaque_types.h new file mode 100644 index 000000000..d29ef7b9b --- /dev/null +++ b/test/unit-test/MQTTv5/cmock_opaque_types.h @@ -0,0 +1,36 @@ +/* + * coreMQTT + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * SPDX-License-Identifier: MIT + * + * 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. + */ + +#ifndef CMOCK_OPAQUE_TYPES_H_ +#define CMOCK_OPAQUE_TYPES_H_ + +/* CMock does not support opaque types so needs concrete definitions for them. + * This file is included in CMock .c files. */ + +struct NetworkContext +{ + int a; +}; + +#endif /* ifndef CMOCK_OPAQUE_TYPES_H_ */ diff --git a/test/unit-test/MQTTv5/core_mqtt_config.h b/test/unit-test/MQTTv5/core_mqtt_config.h new file mode 100644 index 000000000..35eec5b01 --- /dev/null +++ b/test/unit-test/MQTTv5/core_mqtt_config.h @@ -0,0 +1,79 @@ +/* + * coreMQTT + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * SPDX-License-Identifier: MIT + * + * 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. + */ + +/** + * @file core_mqtt_config.h + * @brief This header sets configuration macros for the MQTT library. + */ +#ifndef CORE_MQTT_CONFIG_H_ +#define CORE_MQTT_CONFIG_H_ + +/* Standard include. */ +#include + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Include logging header files and define logging macros in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define the LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL macros depending on + * the logging configuration for MQTT. + * 3. Include the header file "logging_stack.h", if logging is enabled for MQTT. + */ + +#include "../logging/logging_levels.h" + +/* Logging configuration for the MQTT library. */ +#ifndef LIBRARY_LOG_NAME + #define LIBRARY_LOG_NAME "MQTT" +#endif + +#ifndef LIBRARY_LOG_LEVEL + #define LIBRARY_LOG_LEVEL LOG_NONE +#endif + +#include "../logging/logging_stack.h" + +/************ End of logging configuration ****************/ + +/** + * @brief Retry count for reading CONNACK from network. + * + * #MQTT_Connect() can be using retries. If timeout passed as 0 to MQTT_Connect(), + * retries are used to attempt to read from network. The maximum retry count is + * specified by this config. + * + * These unit tests expect retrying only twice. + */ +#define MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT ( 2U ) + +#define MQTT_SUB_UNSUB_MAX_VECTORS ( 6U ) + +#define MQTT_SEND_TIMEOUT_MS ( 200U ) +#define MQTT_VERSION_5_ENABLED ( true ) +#define MAX_USER_PROPERTY ( 5000U ) +#define MQTT_USER_PROPERTY_ENABLED ( true ) + +#endif /* ifndef CORE_MQTT_CONFIG_H_ */ diff --git a/test/unit-test/MQTTv5/core_mqttv5_serializer_utest.c b/test/unit-test/MQTTv5/core_mqttv5_serializer_utest.c new file mode 100644 index 000000000..2da446624 --- /dev/null +++ b/test/unit-test/MQTTv5/core_mqttv5_serializer_utest.c @@ -0,0 +1,3280 @@ +/* + * coreMQTT + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * SPDX-License-Identifier: MIT + * + * 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. + */ + +/** + * @file core_mqtt_serializer_utest.c + * @brief Unit tests for functions in core_mqtt_serializer.h. + */ +#include +#include +#include +#include "unity.h" + +/* Include paths for public enums, structures, and macros. */ +#include "core_mqtt_serializer.h" + +/* Set network context to double pointer to buffer (uint8_t**). */ +struct NetworkContext +{ + uint8_t ** buffer; +}; + +#define MQTT_MAX_REMAINING_LENGTH ( 268435455UL ) +#define MQTT_PACKET_CONNACK_REMAINING_LENGTH ( ( uint8_t ) 2U ) /**< @brief A CONNACK packet always has a "Remaining length" of 2. */ +#define MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ( ( uint8_t ) 0x01U ) /**< @brief The "Session Present" bit is always the lowest bit. */ +#define MQTT_PACKET_SIMPLE_ACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief PUBACK, PUBREC, PUBREl, PUBCOMP, UNSUBACK Remaining length. */ +#define MQTT_PACKET_PINGRESP_REMAINING_LENGTH ( 0U ) /**< @brief A PINGRESP packet always has a "Remaining length" of 0. */ +#define MQTT_PACKET_PUBACK_REMAINING_LENGTH ( 2U ) +#define MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ( 2U ) + +/* + * MQTT client identifier. + */ +#define MQTT_CLIENT_IDENTIFIER "testclient" + +/* + * Client identifier and length to use for the MQTT API tests. + */ +#define CLIENT_IDENTIFIER ( "test" ) /**< @brief Client identifier. */ +#define CLIENT_IDENTIFIER_LENGTH ( ( uint16_t ) ( sizeof( CLIENT_IDENTIFIER ) - 1 ) ) /**< @brief Length of client identifier. */ + +/* + * Topic name and length to use for the MQTT API tests. + */ +#define TEST_TOPIC_NAME ( "/test/topic" ) /**< @brief An arbitrary topic name. */ +#define TEST_TOPIC_NAME_LENGTH ( ( uint16_t ) ( sizeof( TEST_TOPIC_NAME ) - 1 ) ) /**< @brief Length of topic name. */ + +/** + * @brief MQTT protocol version 3.1.1. + */ +#define MQTT_VERSION_3_1_1 ( ( uint8_t ) 4U ) + +/** + * @brief Test-defined macro for MQTT username. + */ +#define MQTT_TEST_USERNAME "username" +#define MQTT_TEST_USERNAME_LEN ( sizeof( MQTT_TEST_USERNAME ) - 1 ) + +/** + * @brief Test-defined macro for MQTT password. + */ +#define MQTT_TEST_PASSWORD "password" +#define MQTT_TEST_PASSWORD_LEN ( sizeof( MQTT_TEST_PASSWORD ) - 1 ) + +/** + * @brief Length of the client identifier. + */ +#define MQTT_CLIENT_IDENTIFIER_LEN ( sizeof( MQTT_CLIENT_IDENTIFIER ) - 1 ) + +/** + * @brief Sample payload. + */ +#define MQTT_SAMPLE_PAYLOAD "Hello World" +#define MQTT_SAMPLE_PAYLOAD_LEN ( sizeof( MQTT_SAMPLE_PAYLOAD ) - 1 ) + +#define TEST_TOPIC_ALIAS ( 2U ) +#define TEST_MSG_EXPIRY ( 100U ) + + +#define MQTT_TEST_UTF8_STRING ( "test" ) +#define MQTT_TEST_UTF8_STRING_LENGTH ( sizeof( MQTT_TEST_UTF8_STRING ) - 1 ) +#define MQTT_TEST_UINT8 ( 1U ) +#define MQTT_TEST_UINT16 ( 32U ) +#define MQTT_TEST_UINT32 ( 300U ) + +/* MQTT CONNECT flags. */ +#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 QoS 1. */ +#define MQTT_CONNECT_FLAG_WILL_QOS2 ( 4 ) /**< @brief Will QoS 2. */ +#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 User name present. */ + +/*Default connect properties. */ +#define DEFAULT_RECEIVE_MAX ( 65535U ) +#define DEFAULT_REQUEST_PROBLEM ( 1 ) + +/** + * @brief The Remaining Length field of MQTT disconnect packets, per MQTT spec. + */ +#define MQTT_DISCONNECT_REMAINING_LENGTH ( ( uint8_t ) 0 ) + +/** + * @brief Set a bit in an 8-bit unsigned integer. + */ +#define UINT8_SET_BIT( x, position ) ( ( x ) = ( uint8_t ) ( ( x ) | ( 0x01U << ( 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 ) & ( 0x01U << ( position ) ) ) == ( 0x01U << ( position ) ) ) + +/** + * @brief Get the high byte of a 16-bit unsigned integer. + */ +#define UINT16_HIGH_BYTE( x ) ( ( uint8_t ) ( ( x ) >> 8 ) ) + +/** + * @brief Get the low byte of a 16-bit unsigned integer. + */ +#define UINT16_LOW_BYTE( x ) ( ( uint8_t ) ( ( x ) & 0x00ffU ) ) + +/** + * @brief Maximum number of bytes in the Remaining Length field is four according + * to MQTT 3.1.1 spec. + */ +#define MQTT_REMAINING_BUFFER_MAX_LENGTH ( 4 ) + +/** + * @brief Length of buffer padding to use in under/overflow checks. + */ +#define BUFFER_PADDING_LENGTH ( 4 ) + +/** + * @brief Byte to use for buffer padding in under/overflow checks. + */ +#define BUFFER_PADDING_BYTE ( 0xA5 ) + +/** + * @brief Length of the MQTT network buffer. + */ +#define MQTT_TEST_BUFFER_LENGTH ( 1024 ) + +#define UINT16_DECODE( ptr ) \ + ( uint16_t ) ( ( ( ( uint16_t ) ptr[ 0 ] ) << 8 ) | \ + ( ( uint16_t ) ptr[ 1 ] ) ) + +#define UINT32_DECODE( ptr ) \ + ( uint32_t ) ( ( ( ( uint32_t ) ptr[ 0 ] ) << 24 ) | \ + ( ( ( uint32_t ) ptr[ 1 ] ) << 16 ) | \ + ( ( ( uint32_t ) ptr[ 2 ] ) << 8 ) | \ + ( ( uint32_t ) ptr[ 3 ] ) ) + +#define UINT32_BYTE3( x ) ( ( uint8_t ) ( ( x ) >> 24 ) ) + +#define UINT32_BYTE2( x ) ( ( uint8_t ) ( ( x ) >> 16 ) ) + +#define UINT32_BYTE1( x ) ( ( uint8_t ) ( ( x ) >> 8 ) ) + +#define UINT32_BYTE0( x ) ( ( uint8_t ) ( ( x ) & 0x000000FFU ) ) + + +#define MQTT_MAX_PACKET_SIZE ( 268435460UL ) +#define MQTT_VERSION_5 ( 5U ) +#define MQTT_SESSION_EXPIRY_SIZE ( 5U ) +#define MQTT_RECEIVE_MAX_SIZE ( 3U ) +#define MQTT_MAX_PACKET_PROPERTY_SIZE ( 5U ) +#define MQTT_TOPIC_ALIAS_SIZE ( 3U ) +#define MQTT_REQUEST_RESPONSE_SIZE ( 2U ) +#define MQTT_REQUEST_PROBLEM_SIZE ( 2U ) + +#define MQTT_SESSION_EXPIRY_ID ( 0x11 ) +#define MQTT_RECEIVE_MAX_ID ( 0x21 ) +#define MQTT_MAX_PACKET_SIZE_ID ( 0x27 ) +#define MQTT_TOPIC_ALIAS_MAX_ID ( 0x22 ) +#define MQTT_REQUEST_RESPONSE_ID ( 0x19 ) +#define MQTT_REQUEST_PROBLEM_ID ( 0x17 ) +#define MQTT_USER_PROPERTY_ID ( 0x26 ) +#define MQTT_AUTH_METHOD_ID ( 0x15 ) +#define MQTT_AUTH_DATA_ID ( 0x16 ) + +#define MQTT_WILL_DELAY_ID ( 0x18 ) +#define MQTT_PAYLOAD_FORMAT_ID ( 0x01 ) +#define MQTT_MSG_EXPIRY_ID ( 0x02 ) +#define MQTT_CONTENT_TYPE_ID ( 0x03 ) +#define MQTT_RESPONSE_TOPIC_ID ( 0x08 ) +#define MQTT_CORRELATION_DATA_ID ( 0x09 ) + +#define MQTT_MAX_QOS_ID ( 0x24 ) +#define MQTT_RETAIN_AVAILABLE_ID ( 0x25 ) +#define MQTT_ASSIGNED_CLIENT_ID ( 0x12 ) +#define MQTT_REASON_STRING_ID ( 0x1F ) +#define MQTT_WILDCARD_ID ( 0x28 ) +#define MQTT_SUB_AVAILABLE_ID ( 0x29 ) +#define MQTT_SHARED_SUB_ID ( 0x2A ) +#define MQTT_SERVER_KEEP_ALIVE_ID ( 0x13 ) +#define MQTT_RESPONSE_INFO_ID ( 0x1A ) +#define MQTT_SERVER_REF_ID ( 0x1C ) + +#define MQTT_REASON_SUCCESS ( 0x00 ) +#define MQTT_REASON_SEND_WILL ( 0x04 ) +#define MQTT_REASON_NO_MATCHING_SUBSCRIBERS ( 0x10 ) +#define MQTT_REASON_UNSPECIFIED_ERR ( 0x80 ) +#define MQTT_REASON_MALFORMED_PACKET ( 0x81 ) +#define MQTT_REASON_PROTOCOL_ERR ( 0x82 ) +#define MQTT_REASON_IMPL_SPECIFIC_ERR ( 0x83 ) +#define MQTT_REASON_UNSUPPORTED_PROTO_VER ( 0x84 ) +#define MQTT_REASON_CLIENT_ID_NOT_VALID ( 0x85 ) +#define MQTT_REASON_BAD_USER_OR_PASS ( 0x86 ) +#define MQTT_REASON_NOT_AUTHORIZED ( 0x87 ) +#define MQTT_REASON_SERVER_UNAVAILABLE ( 0x88 ) +#define MQTT_REASON_SERVER_BUSY ( 0x89 ) +#define MQTT_REASON_BANNED ( 0x8A ) +#define MQTT_REASON_SERVER_SHUTTING_DOWN ( 0x8B ) +#define MQTT_REASON_BAD_AUTH_METHOD ( 0x8C ) +#define MQTT_REASON_KEEP_ALIVE_TIMEOUT ( 0x8D ) +#define MQTT_REASON_SESSION_TAKEN_OVER ( 0x8E ) +#define MQTT_REASON_TOPIC_FILTER_INVALID ( 0x8F ) +#define MQTT_REASON_TOPIC_NAME_INVALID ( 0x90 ) +#define MQTT_REASON_PACKET_ID_IN_USE ( 0x91 ) +#define MQTT_REASON_PACKET_ID_NOT_FOUND ( 0x92 ) +#define MQTT_REASON_RX_MAX_EXCEEDED ( 0x93 ) +#define MQTT_REASON_TOPIC_ALIAS_INVALID ( 0x94 ) +#define MQTT_REASON_PACKET_TOO_LARGE ( 0x95 ) +#define MQTT_REASON_MSG_RATE_TOO_HIGH ( 0x96 ) +#define MQTT_REASON_QUOTA_EXCEEDED ( 0x97 ) +#define MQTT_REASON_ADMIN_ACTION ( 0x98 ) +#define MQTT_REASON_PAYLOAD_FORMAT_INVALID ( 0x99 ) +#define MQTT_REASON_RETAIN_NOT_SUPPORTED ( 0x9A ) +#define MQTT_REASON_QOS_NOT_SUPPORTED ( 0x9B ) +#define MQTT_REASON_USE_ANOTHER_SERVER ( 0x9C ) +#define MQTT_REASON_SERVER_MOVED ( 0x9D ) +#define MQTT_REASON_SS_NOT_SUPPORTED ( 0x9E ) +#define MQTT_REASON_CON_RATE_EXCEED ( 0x9F ) +#define MQTT_REASON_MAX_CON_TIME ( 0xA0 ) +#define MQTT_REASON_SUB_ID_NOT_SUP ( 0xA1 ) +#define MQTT_REASON_WILDCARD_SUB_NOT_SUP ( 0xA2 ) + +#define CORE_MQTT_ID_SIZE ( 1U ) +#define MQTT_REMAINING_LENGTH_INVALID ( ( size_t ) 268435456 ) + +static uint8_t remainingLengthBuffer[ MQTT_REMAINING_BUFFER_MAX_LENGTH ] = { 0 }; + +static uint8_t encodedStringBuffer[ MQTT_TEST_BUFFER_LENGTH ] = { 0 }; + +static uint8_t mqttBuffer[ MQTT_TEST_BUFFER_LENGTH ] = { 0 }; + + +/* Variables common to testcases */ +MQTTConnectProperties_t properties; +MQTTUserProperties_t userProperties; +MQTTPublishInfo_t publishInfo; +MQTTConnectInfo_t connectInfo; +MQTTPacketInfo_t packetInfo; +MQTTStatus_t status; + +/* ============================ UNITY FIXTURES ============================ */ + +/* Called before each test method. */ +void setUp( void ) +{ + memset( &properties, 0x0, sizeof( properties ) ); + memset( &userProperties, 0x0, sizeof( userProperties ) ); + memset( &publishInfo, 0x0, sizeof( publishInfo ) ); + memset( &connectInfo, 0x0, sizeof( connectInfo ) ); + memset( &packetInfo, 0x0, sizeof( packetInfo ) ); +} + +/* Called after each test method. */ +void tearDown( void ) +{ +} + +/* Called at the beginning of the whole suite. */ +void suiteSetUp() +{ +} + +/* Called at the end of the whole suite. */ +int suiteTearDown( int numFailures ) +{ + return numFailures; +} + +/* ========================================================================== */ + +/** + * @brief Initialize pNetworkBuffer using static buffer. + * + * @param[in] pNetworkBuffer Network buffer provided for the context. + */ +static void setupNetworkBuffer( MQTTFixedBuffer_t * const pNetworkBuffer ) +{ + pNetworkBuffer->pBuffer = mqttBuffer; + pNetworkBuffer->size = 2048; +} + +/** + * @brief Initialize pConnectInfo using test-defined macros. + * + * @param[in] pConnectInfo MQTT CONNECT packet parameters. + */ +static void setupConnectInfo( MQTTConnectInfo_t * const pConnectInfo ) +{ + pConnectInfo->cleanSession = true; + pConnectInfo->pClientIdentifier = MQTT_CLIENT_IDENTIFIER; + pConnectInfo->clientIdentifierLength = MQTT_CLIENT_IDENTIFIER_LEN; + pConnectInfo->keepAliveSeconds = 0; + pConnectInfo->pUserName = MQTT_TEST_USERNAME; + pConnectInfo->userNameLength = MQTT_TEST_USERNAME_LEN; + pConnectInfo->pPassword = MQTT_TEST_PASSWORD; + pConnectInfo->passwordLength = MQTT_TEST_PASSWORD_LEN; +} + +static size_t remainingLengthEncodedSize( size_t length ) +{ + size_t encodedSize; + + /* 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 < 128U ) + { + encodedSize = 1U; + } + /* 2 bytes are needed to encode lengths between 128 and 16,383. */ + else if( length < 16384U ) + { + encodedSize = 2U; + } + /* 3 bytes are needed to encode lengths between 16,384 and 2,097,151. */ + else if( length < 2097152U ) + { + encodedSize = 3U; + } + /* 4 bytes are needed to encode lengths between 2,097,152 and 268,435,455. */ + else + { + encodedSize = 4U; + } + + LogDebug( ( "Encoded size for length %lu is %lu bytes.", + ( unsigned long ) length, + ( unsigned long ) encodedSize ) ); + + return encodedSize; +} + +/** + * @brief Initialize pPublishInfo using test-defined macros. + * + * @param[in] pPublishInfo Publish information. + */ +static void setupPublishInfo( MQTTPublishInfo_t * pPublishInfo ) +{ + pPublishInfo->pTopicName = TEST_TOPIC_NAME; + pPublishInfo->topicNameLength = TEST_TOPIC_NAME_LENGTH; + pPublishInfo->pPayload = MQTT_SAMPLE_PAYLOAD; + pPublishInfo->payloadLength = MQTT_SAMPLE_PAYLOAD_LEN; + pPublishInfo->qos = MQTTQoS0; + pPublishInfo->dup = false; + pPublishInfo->retain = false; +} + +/** + * @brief Initialize pPublishInfo using test-defined macros. + * + * @param[in] pPublishInfo Publish information. + */ +static void setupPublishProperties( MQTTPublishInfo_t * pPublishInfo ) +{ + pPublishInfo->payloadFormat = 1; + pPublishInfo->topicAlias = TEST_TOPIC_ALIAS; + pPublishInfo->msgExpiryInterval = TEST_MSG_EXPIRY; + pPublishInfo->msgExpiryPresent = 1; + pPublishInfo->contentTypeLength = MQTT_TEST_UTF8_STRING_LENGTH; + pPublishInfo->pContentType = MQTT_TEST_UTF8_STRING; + pPublishInfo->responseTopicLength = MQTT_TEST_UTF8_STRING_LENGTH; + pPublishInfo->pResponseTopic = MQTT_TEST_UTF8_STRING; + pPublishInfo->correlationLength = MQTT_TEST_UTF8_STRING_LENGTH; + pPublishInfo->pCorrelationData = MQTT_TEST_UTF8_STRING; +} + +/** + * @brief Encode remaining length into pDestination for packet serialization + * using MQTT v3.1.1 spec. + * + * @param[in] pDestination Buffer to write encoded remaining length. + * @param[in] length Actual remaining length. + */ +static size_t encodeRemainingLength( uint8_t * pDestination, + size_t length ) +{ + uint8_t lengthByte; + uint8_t * pLengthEnd = NULL; + size_t remainingLength = length; + + TEST_ASSERT_NOT_NULL( pDestination ); + + pLengthEnd = pDestination; + + /* This algorithm is copied from the MQTT v3.1.1 spec. */ + do + { + lengthByte = ( uint8_t ) ( remainingLength % 128U ); + remainingLength = remainingLength / 128U; + + /* Set the high bit of this byte, indicating that there's more data. */ + if( remainingLength > 0U ) + { + UINT8_SET_BIT( lengthByte, 7 ); + } + + /* Output a single encoded byte. */ + *pLengthEnd = lengthByte; + pLengthEnd++; + } while( remainingLength > 0U ); + + return ( size_t ) ( pLengthEnd - pDestination ); +} + +/** + * @brief Encode UTF-8 string and its length into pDestination for + * packet serialization. + * + * @param[in] pDestination Buffer to write encoded string. + * @param[in] source String to encode. + * @param[in] sourceLength Length of the string to encode. + */ +static size_t encodeString( uint8_t * pDestination, + const char * source, + uint16_t sourceLength ) +{ + uint8_t * pBuffer = NULL; + + /* Typecast const char * typed source buffer to const uint8_t *. + * This is to use same type buffers in memcpy. */ + const uint8_t * pSourceBuffer = ( const uint8_t * ) source; + + TEST_ASSERT_NOT_NULL( pSourceBuffer ); + TEST_ASSERT_NOT_NULL( pDestination ); + + pBuffer = pDestination; + + /* The first byte of a UTF-8 string is the high byte of the string length. */ + *pBuffer = UINT16_HIGH_BYTE( sourceLength ); + pBuffer++; + + /* The second byte of a UTF-8 string is the low byte of the string length. */ + *pBuffer = UINT16_LOW_BYTE( sourceLength ); + pBuffer++; + + /* Copy the string into pBuffer. */ + ( void ) memcpy( pBuffer, pSourceBuffer, sourceLength ); + + /* Return the pointer to the end of the encoded string. */ + pBuffer += sourceLength; + + return ( size_t ) ( pBuffer - pDestination ); +} + +/** + * @brief Pad beginning and end of buffer with non-zero bytes to be used in + * checking for under/overflow after serialization. + * + * @param[in] pBuffer Buffer to pad. + * @param[in] bufferLength Total length of buffer. + */ +static void padAndResetBuffer( uint8_t * pBuffer, + size_t bufferLength ) +{ + int i = 0; + + for( i = 0; i < BUFFER_PADDING_LENGTH; i++ ) + { + pBuffer[ i ] = BUFFER_PADDING_BYTE; + pBuffer[ bufferLength - 1 - i ] = BUFFER_PADDING_BYTE; + } + + /* Zero out rest of buffer. */ + memset( &pBuffer[ BUFFER_PADDING_LENGTH ], 0x0, bufferLength - 2 * BUFFER_PADDING_LENGTH ); +} + +/** + * @brief Test buffer for under/overflow. + * + * @param[in] pBuffer Buffer to check. + * @param[in] bufferLength Total length of buffer. + */ +static void checkBufferOverflow( uint8_t * pBuffer, + size_t bufferLength ) +{ + /* Check beginning of buffer. */ + TEST_ASSERT_EACH_EQUAL_UINT8( BUFFER_PADDING_BYTE, + pBuffer, + BUFFER_PADDING_LENGTH ); + /* Check end. */ + TEST_ASSERT_EACH_EQUAL_UINT8( BUFFER_PADDING_BYTE, + pBuffer + bufferLength - BUFFER_PADDING_LENGTH, + BUFFER_PADDING_LENGTH ); +} + +/** + * @brief Mock successful transport receive by reading data from a buffer. + */ +static int32_t mockReceive( NetworkContext_t * pNetworkContext, + void * pBuffer, + size_t bytesToRecv ) +{ + uint8_t * returnBuffer = ( uint8_t * ) pBuffer; + uint8_t * mockNetwork; + size_t bytesRead = 0; + + /* Treat network context as pointer to buffer for mocking */ + mockNetwork = *( pNetworkContext->buffer ); + + while( bytesRead++ < bytesToRecv ) + { + /* Read single byte and advance buffer. */ + *returnBuffer++ = *mockNetwork++; + } + + /* Move stream by bytes read. */ + *( pNetworkContext->buffer ) = mockNetwork; + + return bytesToRecv; +} + + +static void setupProperties( MQTTConnectProperties_t * pProperties ) +{ + pProperties->receiveMax = DEFAULT_RECEIVE_MAX; + pProperties->requestProblemInfo = DEFAULT_REQUEST_PROBLEM; + pProperties->maxPacketSize = MQTT_MAX_PACKET_SIZE; +} + +static uint8_t * initializeDeserialize( MQTTPacketInfo_t * packetInfo, + uint8_t * pIndex ) +{ + uint8_t * pIndexLocal = pIndex; + + packetInfo->pRemainingData = pIndexLocal; + packetInfo->type = MQTT_PACKET_TYPE_CONNACK; + *pIndexLocal = 0x01; + pIndexLocal++; + *pIndexLocal = 0x00; + pIndexLocal++; + return pIndexLocal; +} + +static uint8_t * serializeuint_32( uint8_t * pIndex, + uint8_t propertyId ) +{ + uint8_t * pIndexLocal = pIndex; + + *pIndexLocal = propertyId; + pIndexLocal++; + pIndexLocal[ 0 ] = UINT32_BYTE3( MQTT_TEST_UINT32 ); + pIndexLocal[ 1 ] = UINT32_BYTE2( MQTT_TEST_UINT32 ); + pIndexLocal[ 2 ] = UINT32_BYTE1( MQTT_TEST_UINT32 ); + pIndexLocal[ 3 ] = UINT32_BYTE0( MQTT_TEST_UINT32 ); + pIndexLocal = &pIndexLocal[ 4 ]; + return pIndexLocal; +} + + +static uint8_t * serializeuint_16( uint8_t * pIndex, + uint8_t propertyId ) +{ + uint8_t * pIndexLocal = pIndex; + + *pIndexLocal = propertyId; + pIndexLocal++; + pIndexLocal[ 0 ] = UINT16_HIGH_BYTE( MQTT_TEST_UINT16 ); + pIndexLocal[ 1 ] = UINT16_LOW_BYTE( MQTT_TEST_UINT16 ); + pIndexLocal = &pIndexLocal[ 2 ]; + return pIndexLocal; +} + +static uint8_t * serializeuint_8( uint8_t * pIndex, + uint8_t propertyId ) +{ + uint8_t * pIndexLocal = pIndex; + + *pIndexLocal = propertyId; + pIndexLocal++; + pIndexLocal[ 0 ] = MQTT_TEST_UINT8; + pIndexLocal++; + return pIndexLocal; +} +static uint8_t * serializeutf_8( uint8_t * pIndex, + uint8_t propertyId ) +{ + uint8_t * pIndexLocal = pIndex; + + *pIndexLocal = propertyId; + pIndexLocal++; + size_t dummy = encodeString( pIndexLocal, MQTT_TEST_UTF8_STRING, MQTT_TEST_UTF8_STRING_LENGTH ); + pIndexLocal = &pIndexLocal[ dummy ]; + return pIndexLocal; +} + +static uint8_t * serializeutf_8pair( uint8_t * pIndex ) +{ + uint8_t * pIndexLocal = pIndex; + + *pIndexLocal = MQTT_USER_PROPERTY_ID; + pIndexLocal++; + size_t dummy = encodeString( pIndexLocal, MQTT_TEST_UTF8_STRING, MQTT_TEST_UTF8_STRING_LENGTH ); + pIndexLocal = &pIndexLocal[ dummy ]; + dummy = encodeString( pIndexLocal, MQTT_TEST_UTF8_STRING, MQTT_TEST_UTF8_STRING_LENGTH ); + pIndexLocal = &pIndexLocal[ dummy ]; + return pIndexLocal; +} +static MQTTStatus_t MQTT_GetUserPropertySize( const MQTTUserProperty_t * pUserProperty, + uint32_t number, + size_t * pSize ) +{ + MQTTStatus_t status = MQTTSuccess; + uint32_t i = 0; + + /*Number of user properties can't be more than the max user properties specified*/ + if( number > ( uint32_t ) MAX_USER_PROPERTY ) + { + status = MQTTBadParameter; + } + else + { + for( ; ( i < number ); i++ ) + { + /*Validate the key and value*/ + if( ( pUserProperty[ i ].keyLength == 0U ) || ( pUserProperty[ i ].valueLength == 0U ) || ( pUserProperty[ i ].pKey == NULL ) || ( pUserProperty[ i ].pValue == NULL ) ) + { + status = MQTTBadParameter; + break; + } + else + { + *pSize += ( pUserProperty[ i ].keyLength ); + *pSize += 3U; + *pSize += ( pUserProperty[ i ].valueLength ); + *pSize += 2U; + } + } + } + + return status; +} + +static MQTTStatus_t MQTT_GetPublishPropertiesSize( MQTTPublishInfo_t * pPublishProperties ) +{ + size_t propertyLength = 0U; + MQTTStatus_t status = MQTTSuccess; + + /*Add the length of all the parameters which are applicable*/ + if( pPublishProperties->willDelay != 0U ) + { + propertyLength += 5U; + } + + if( pPublishProperties->payloadFormat != 0U ) + { + propertyLength += 2U; + } + + if( pPublishProperties->msgExpiryPresent == true ) + { + propertyLength += 5U; + } + + if( pPublishProperties->contentTypeLength != 0U ) + { + if( pPublishProperties->pContentType == NULL ) + { + status = MQTTBadParameter; + } + else + { + propertyLength += pPublishProperties->contentTypeLength + 3U; + } + } + + /*Validate if length and pointers are valid*/ + if( ( status == MQTTSuccess ) && ( pPublishProperties->responseTopicLength != 0U ) ) + { + if( pPublishProperties->pResponseTopic == NULL ) + { + status = MQTTBadParameter; + } + else + { + propertyLength += pPublishProperties->responseTopicLength + 3U; + } + } + + if( ( status == MQTTSuccess ) && ( pPublishProperties->correlationLength != 0U ) ) + { + if( pPublishProperties->pCorrelationData == NULL ) + { + status = MQTTBadParameter; + } + else + { + propertyLength += pPublishProperties->correlationLength + 3U; + } + } + + /*Get the length of user properties*/ + if( ( status == MQTTSuccess ) && ( pPublishProperties->pUserProperty != NULL ) ) + { + status = MQTT_GetUserPropertySize( pPublishProperties->pUserProperty->userProperty, pPublishProperties->pUserProperty->count, &propertyLength ); + } + + /*Variable encoded can't have a value more than 268435455UL*/ + if( propertyLength > MQTT_MAX_REMAINING_LENGTH ) + { + status = MQTTBadParameter; + } + + if( status == MQTTSuccess ) + { + pPublishProperties->propertyLength = propertyLength; + } + + return status; +} + +static uint8_t * serializeUserProperties( uint8_t * pIndex, + const MQTTUserProperty_t * pUserProperty, + uint16_t size ) +{ + uint16_t i = 0; + + assert( pIndex != NULL ); + uint8_t * pIndexLocal = pIndex; + uint8_t dummy; + + for( ; i < size; i++ ) + { + *pIndexLocal = MQTT_USER_PROPERTY_ID; + pIndexLocal++; + dummy = encodeString( pIndexLocal, ( pUserProperty + i )->pKey, ( pUserProperty + i )->keyLength ); + pIndexLocal += dummy; + dummy = encodeString( pIndexLocal, ( pUserProperty + i )->pValue, ( pUserProperty + i )->valueLength ); + pIndexLocal += dummy; + } + + i = dummy; + return pIndexLocal; +} + +MQTTStatus_t decodeVariableLength( const uint8_t * pBuffer, + size_t * length ) +{ + size_t remainingLength = 0; + size_t multiplier = 1; + size_t bytesDecoded = 0; + size_t expectedSize = 0; + uint8_t encodedByte = 0; + MQTTStatus_t status = MQTTSuccess; + + /* This algorithm is copied from the MQTT v3.1.1 spec. */ + do + { + if( multiplier > 2097152U ) /* 128 ^ 3 */ + { + remainingLength = MQTT_REMAINING_LENGTH_INVALID; + + LogError( ( "Invalid remaining length in the packet.\n" ) ); + + status = MQTTBadResponse; + } + else + { + /* Get the next byte. It is at the next position after the bytes + * decoded till now since the header of one byte was read before. */ + encodedByte = pBuffer[ bytesDecoded ]; + remainingLength += ( ( size_t ) encodedByte & 0x7FU ) * multiplier; + multiplier *= 128U; + bytesDecoded++; + } + + /* If the response is incorrect, or no more data is available, then + * break out of the loop. */ + if( ( remainingLength == MQTT_REMAINING_LENGTH_INVALID ) || + ( status != MQTTSuccess ) ) + { + break; + } + } while( ( encodedByte & 0x80U ) != 0U ); + + if( status == MQTTSuccess ) + { + /* Check that the decoded remaining length conforms to the MQTT specification. */ + expectedSize = remainingLengthEncodedSize( remainingLength ); + + if( bytesDecoded != expectedSize ) + { + LogError( ( "Expected and actual length of decoded bytes do not match.\n" ) ); + status = MQTTBadResponse; + } + else + { + *length = remainingLength; + } + } + + return status; +} + +static void verifySerializedConnectPacket( const MQTTConnectInfo_t * const pConnectInfo, + const MQTTPublishInfo_t * const pWillInfo, + const MQTTConnectProperties_t * pConnectProperties, + size_t remainingLength, + const MQTTFixedBuffer_t * const pBuffer ) +{ + uint8_t connectFlags = 0U; + uint8_t encodedRemainingLength = 0U; + uint8_t encodedStringLength = 0U; + uint8_t * pIndex = NULL; + + pIndex = pBuffer->pBuffer; + /* The first byte in the CONNECT packet is the control packet type. */ + TEST_ASSERT_EQUAL_MESSAGE( MQTT_PACKET_TYPE_CONNECT, *pIndex, "MQTT_PACKET_TYPE_CONNECT is not equal to *pIndex" ); + pIndex++; + + /* 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. */ + encodedRemainingLength = encodeRemainingLength( remainingLengthBuffer, remainingLength ); + TEST_ASSERT_EQUAL_MEMORY( remainingLengthBuffer, pIndex, encodedRemainingLength ); + pIndex += encodedRemainingLength; + + /* The string "MQTT" is placed at the beginning of the CONNECT packet's variable + * header. This string is 4 bytes long. */ + encodedStringLength = encodeString( encodedStringBuffer, "MQTT", 4 ); + TEST_ASSERT_EQUAL_MEMORY( encodedStringBuffer, pIndex, encodedStringLength ); + pIndex += encodedStringLength; + + /* The MQTT protocol version is the second field of the variable header. */ + TEST_ASSERT_EQUAL( 5, *pIndex ); + pIndex++; + + /* Set the clean session flag if needed. */ + if( pConnectInfo->cleanSession == true ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_CLEAN ); + } + + /* Set the flags for username and password if provided. */ + if( pConnectInfo->pUserName != NULL ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_USERNAME ); + } + + if( pConnectInfo->pPassword != NULL ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_PASSWORD ); + } + + /* Set will flag if a Last Will and Testament is provided. */ + if( pWillInfo != NULL ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL ); + + /* Flags only need to be changed for Will QoS 1 or 2. */ + if( pWillInfo->qos == MQTTQoS1 ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_QOS1 ); + } + else if( pWillInfo->qos == MQTTQoS2 ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_QOS2 ); + } + else + { + /* Empty else MISRA 15.7 */ + } + + if( pWillInfo->retain == true ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_RETAIN ); + } + } + + TEST_ASSERT_EQUAL( connectFlags, *pIndex ); + pIndex++; + + /* Verify the 2 bytes of the keep alive interval into the CONNECT packet. */ + TEST_ASSERT_EQUAL( UINT16_HIGH_BYTE( pConnectInfo->keepAliveSeconds ), + *pIndex ); + pIndex++; + TEST_ASSERT_EQUAL( UINT16_LOW_BYTE( pConnectInfo->keepAliveSeconds ), + *pIndex ); + pIndex++; + /* Verify the connect properties into the CONNECT packet. */ + pIndex = MQTTV5_SerializeConnectProperties( pIndex, pConnectProperties ); + + if( pConnectProperties->pOutgoingUserProperty != NULL ) + { + pIndex = serializeUserProperties( pIndex, pConnectProperties->pOutgoingUserProperty->userProperty, pConnectProperties->pOutgoingUserProperty->count ); + } + + if( pConnectProperties->pOutgoingAuth != NULL ) + { + if( pConnectProperties->pOutgoingAuth->authMethodLength != 0 ) + { + encodedStringLength = encodeString( encodedStringBuffer, + pConnectProperties->pOutgoingAuth->pAuthMethod, + pConnectProperties->pOutgoingAuth->authMethodLength ); + TEST_ASSERT_EQUAL_MEMORY( encodedStringBuffer, pIndex, encodedStringLength ); + pIndex += encodedStringLength; + + if( pConnectProperties->pOutgoingAuth->authDataLength != 0 ) + { + encodedStringLength = encodeString( encodedStringBuffer, + pConnectProperties->pOutgoingAuth->pAuthData, + pConnectProperties->pOutgoingAuth->authDataLength ); + TEST_ASSERT_EQUAL_MEMORY( encodedStringBuffer, pIndex, encodedStringLength ); + pIndex += encodedStringLength; + } + } + } + + /* Verify the client identifier into the CONNECT packet. */ + encodedStringLength = encodeString( encodedStringBuffer, + pConnectInfo->pClientIdentifier, + pConnectInfo->clientIdentifierLength ); + TEST_ASSERT_EQUAL_MEMORY( encodedStringBuffer, pIndex, encodedStringLength ); + pIndex += encodedStringLength; + + /* Verify the will topic name and message into the CONNECT packet if provided. */ + if( pWillInfo != NULL ) + { + /*Will Properties*/ + pIndex = MQTT_SerializePublishProperties( pWillInfo, pIndex ); + + if( pWillInfo->contentTypeLength != 0U ) + { + TEST_ASSERT_EQUAL_INT( MQTT_CONTENT_TYPE_ID, *pIndex ); + pIndex++; + encodedStringLength = encodeString( encodedStringBuffer, + pWillInfo->pContentType, + pWillInfo->contentTypeLength ); + TEST_ASSERT_EQUAL_MEMORY( encodedStringBuffer, pIndex, encodedStringLength ); + pIndex += encodedStringLength; + } + + if( pWillInfo->responseTopicLength != 0U ) + { + TEST_ASSERT_EQUAL_INT( MQTT_RESPONSE_TOPIC_ID, *pIndex ); + pIndex++; + encodedStringLength = encodeString( encodedStringBuffer, + pWillInfo->pResponseTopic, + pWillInfo->responseTopicLength ); + TEST_ASSERT_EQUAL_MEMORY( encodedStringBuffer, pIndex, encodedStringLength ); + pIndex += encodedStringLength; + } + + if( pWillInfo->correlationLength != 0U ) + { + TEST_ASSERT_EQUAL_INT( MQTT_CORRELATION_DATA_ID, *pIndex ); + pIndex++; + encodedStringLength = encodeString( encodedStringBuffer, + pWillInfo->pCorrelationData, + pWillInfo->correlationLength ); + TEST_ASSERT_EQUAL_MEMORY( encodedStringBuffer, pIndex, encodedStringLength ); + pIndex += encodedStringLength; + } + + if( pWillInfo->pUserProperty != NULL ) + { + pIndex = serializeUserProperties( pIndex, pWillInfo->pUserProperty->userProperty, pWillInfo->pUserProperty->count ); + } + + encodedStringLength = encodeString( encodedStringBuffer, + pWillInfo->pTopicName, + pWillInfo->topicNameLength ); + TEST_ASSERT_EQUAL_MEMORY( encodedStringBuffer, pIndex, encodedStringLength ); + pIndex += encodedStringLength; + encodedStringLength = encodeString( encodedStringBuffer, + pWillInfo->pPayload, + ( uint16_t ) pWillInfo->payloadLength ); + TEST_ASSERT_EQUAL_MEMORY( encodedStringBuffer, pIndex, encodedStringLength ); + pIndex += encodedStringLength; + } + + /* Verify the user name if provided. */ + if( pConnectInfo->pUserName != NULL ) + { + encodedStringLength = encodeString( encodedStringBuffer, + pConnectInfo->pUserName, + pConnectInfo->userNameLength ); + TEST_ASSERT_EQUAL_MEMORY( encodedStringBuffer, pIndex, encodedStringLength ); + pIndex += encodedStringLength; + } + + /* Verify the password if provided. */ + if( pConnectInfo->pPassword != NULL ) + { + encodedStringLength = encodeString( encodedStringBuffer, + pConnectInfo->pPassword, + pConnectInfo->passwordLength ); + TEST_ASSERT_EQUAL_MEMORY( encodedStringBuffer, pIndex, encodedStringLength ); + pIndex += encodedStringLength; + } +} + +void test_MQTT_GetPublishPropertiesSize( void ) +{ + MQTTStatus_t status = MQTTSuccess; + + /* Call MQTT_GetPublishPropertiesSize() with various combinations of + * incorrect paramters */ + + /* + * Max Packet Size cannot be null + */ + + status = MQTT_GetPublishPropertiesSize( &publishInfo ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_size_t( 0, publishInfo.propertyLength ); + + publishInfo.willDelay = 10; + status = MQTT_GetPublishPropertiesSize( &publishInfo ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_size_t( 5, publishInfo.propertyLength ); + + publishInfo.payloadFormat = 1; + status = MQTT_GetPublishPropertiesSize( &publishInfo ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_size_t( 7, publishInfo.propertyLength ); + + publishInfo.msgExpiryPresent = 1; + publishInfo.msgExpiryInterval = 10; + status = MQTT_GetPublishPropertiesSize( &publishInfo ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_size_t( 12, publishInfo.propertyLength ); + + publishInfo.msgExpiryPresent = 1; + publishInfo.msgExpiryInterval = 10; + status = MQTT_GetPublishPropertiesSize( &publishInfo ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_size_t( 12, publishInfo.propertyLength ); + + publishInfo.contentTypeLength = MQTT_TEST_UTF8_STRING_LENGTH; + status = MQTT_GetPublishPropertiesSize( &publishInfo ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + publishInfo.pContentType = MQTT_TEST_UTF8_STRING; + status = MQTT_GetPublishPropertiesSize( &publishInfo ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_size_t( 19, publishInfo.propertyLength ); + + publishInfo.responseTopicLength = MQTT_TEST_UTF8_STRING_LENGTH; + status = MQTT_GetPublishPropertiesSize( &publishInfo ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + publishInfo.pResponseTopic = MQTT_TEST_UTF8_STRING; + status = MQTT_GetPublishPropertiesSize( &publishInfo ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_size_t( 26, publishInfo.propertyLength ); + + publishInfo.correlationLength = MQTT_TEST_UTF8_STRING_LENGTH; + status = MQTT_GetPublishPropertiesSize( &publishInfo ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + publishInfo.pCorrelationData = MQTT_TEST_UTF8_STRING; + status = MQTT_GetPublishPropertiesSize( &publishInfo ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_size_t( 33, publishInfo.propertyLength ); + + MQTTUserProperties_t userProperties; + userProperties.userProperty[ 0 ].pKey = MQTT_TEST_UTF8_STRING; + userProperties.userProperty[ 0 ].keyLength = MQTT_TEST_UTF8_STRING_LENGTH; + userProperties.userProperty[ 0 ].valueLength = MQTT_TEST_UTF8_STRING_LENGTH; + userProperties.userProperty[ 0 ].pValue = MQTT_TEST_UTF8_STRING; + userProperties.userProperty[ 1 ].pKey = MQTT_TEST_UTF8_STRING; + userProperties.userProperty[ 1 ].keyLength = MQTT_TEST_UTF8_STRING_LENGTH; + userProperties.userProperty[ 1 ].valueLength = MQTT_TEST_UTF8_STRING_LENGTH; + userProperties.userProperty[ 1 ].pValue = MQTT_TEST_UTF8_STRING; + publishInfo.pUserProperty = &userProperties; + userProperties.count = 2; + status = MQTT_GetPublishPropertiesSize( &publishInfo ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_size_t( 59, publishInfo.propertyLength ); +} + +void test_MQTTV5_SerializeConnectProperties( void ) +{ + uint8_t properties[ 24U ]; + uint8_t * pIndex = properties; + uint8_t * index = properties; + MQTTConnectProperties_t connect; + + size_t propertyLength; + + memset( &connect, 0x0, sizeof( connect ) ); + connect.sessionExpiry = 12; + connect.receiveMax = 32; + connect.maxPacketSize = 56; + connect.topicAliasMax = 11; + connect.requestResponseInfo = 1; + connect.requestProblemInfo = 0; + connect.propertyLength = 20; + pIndex = MQTTV5_SerializeConnectProperties( pIndex, &connect ); + TEST_ASSERT_EQUAL_INT( 21, ( pIndex - properties ) ); + status = decodeVariableLength( properties, &propertyLength ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_size_t( connect.propertyLength, propertyLength ); + index++; + + TEST_ASSERT_EQUAL_INT( MQTT_SESSION_EXPIRY_ID, *index ); + index++; + TEST_ASSERT_EQUAL_UINT32( connect.sessionExpiry, UINT32_DECODE( index ) ); + index += 4; + + TEST_ASSERT_EQUAL_INT( MQTT_RECEIVE_MAX_ID, *index ); + index++; + TEST_ASSERT_EQUAL_UINT32( connect.receiveMax, UINT16_DECODE( index ) ); + index += 2; + + TEST_ASSERT_EQUAL_INT( MQTT_MAX_PACKET_SIZE_ID, *index ); + index++; + TEST_ASSERT_EQUAL_INT( connect.maxPacketSize, UINT32_DECODE( index ) ); + index += 4; + + TEST_ASSERT_EQUAL_INT( MQTT_TOPIC_ALIAS_MAX_ID, *index ); + index++; + TEST_ASSERT_EQUAL_INT( connect.topicAliasMax, UINT16_DECODE( index ) ); + index += 2; + + TEST_ASSERT_EQUAL_INT( MQTT_REQUEST_RESPONSE_ID, *index ); + index++; + TEST_ASSERT_EQUAL_INT( connect.requestResponseInfo, *index ); + index++; + + TEST_ASSERT_EQUAL_INT( MQTT_REQUEST_PROBLEM_ID, *index ); + index++; + TEST_ASSERT_EQUAL_INT( connect.requestProblemInfo, *index ); + index++; +} + + +void test_MQTTV5_DeserializeConnackOnlyStatus( void ) +{ + uint8_t buffer[ 50 ]; + uint8_t * pIndex = buffer; + MQTTUserProperties_t incomingProperty; + + properties.pIncomingUserProperty = &incomingProperty; + status = MQTTV5_DeserializeConnack( NULL, NULL, NULL ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + bool sessionPresent; + status = MQTTV5_DeserializeConnack( NULL, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + status = MQTTV5_DeserializeConnack( &properties, NULL, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, NULL ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + packetInfo.type = MQTT_PACKET_TYPE_CONNACK; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + packetInfo.pRemainingData = pIndex; + packetInfo.type = MQTT_PACKET_TYPE_CONNECT; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + /*Reserved bit incorrect*/ + buffer[ 0 ] = 0x11; + packetInfo.type = MQTT_PACKET_TYPE_CONNACK; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTBadResponse, status ); + + /* + * Session Present Bit is set but reason code is not equal to 0; + */ + buffer[ 0 ] = 0x01; + buffer[ 1 ] = 0x01; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTBadResponse, status ); + + /* 5 + 1 + 2 = 8 */ + size_t propertyLength = encodeRemainingLength( pIndex, 5 ); + packetInfo.remainingLength = propertyLength + 7; + /*Not a valid reason code*/ + buffer[ 0 ] = 0x00; + buffer[ 1 ] = 0x03; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTProtocolError, status ); + /*All the valid response code*/ + buffer[ 1 ] = 0x80; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTServerRefused, status ); + + buffer[ 1 ] = 0x80; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTServerRefused, status ); + + buffer[ 1 ] = 0x81; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTServerRefused, status ); + + buffer[ 1 ] = 0x82; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTServerRefused, status ); + + buffer[ 1 ] = 0x83; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTServerRefused, status ); + + buffer[ 1 ] = 0x80; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTServerRefused, status ); + + buffer[ 1 ] = 0x84; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTServerRefused, status ); + + buffer[ 1 ] = 0x80; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTServerRefused, status ); + + buffer[ 1 ] = 0x85; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTServerRefused, status ); + + buffer[ 1 ] = 0x86; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTServerRefused, status ); + + buffer[ 1 ] = 0x87; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTServerRefused, status ); + + buffer[ 1 ] = 0x88; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTServerRefused, status ); + + buffer[ 1 ] = 0x89; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTServerRefused, status ); + + buffer[ 1 ] = 0x8A; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTServerRefused, status ); + + buffer[ 1 ] = 0x8C; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTServerRefused, status ); + + buffer[ 1 ] = 0x88; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTServerRefused, status ); + + buffer[ 1 ] = 0x90; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTServerRefused, status ); + + buffer[ 1 ] = 0x95; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTServerRefused, status ); + + buffer[ 1 ] = 0x97; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTServerRefused, status ); + + buffer[ 1 ] = 0x99; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTServerRefused, status ); + + buffer[ 1 ] = 0x9A; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTServerRefused, status ); + + buffer[ 1 ] = 0x9A; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTServerRefused, status ); + + buffer[ 1 ] = 0x9B; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTServerRefused, status ); + + buffer[ 1 ] = 0x9C; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTServerRefused, status ); + + buffer[ 1 ] = 0x9D; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTServerRefused, status ); + + buffer[ 1 ] = 0x9F; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTServerRefused, status ); + + /*Exceeds the max packet size set by the client*/ + properties.maxPacketSize = 2; + buffer[ 1 ] = 0x00; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTProtocolError, status ); + + /*Validate the remaining length*/ + properties.maxPacketSize = MQTT_MAX_PACKET_SIZE; + packetInfo.remainingLength = 7; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTMalformedPacket, status ); + + /*Invalid property length*/ + packetInfo.remainingLength = 20; + pIndex = &buffer[ 2 ]; + propertyLength = encodeRemainingLength( pIndex, 20971556356235 ); + LogDebug( ( "Encoded size for length is %lu bytes.", + ( unsigned long ) propertyLength ) ); + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status ); + + /*Invalid property length*/ + pIndex = &buffer[ 2 ]; + *pIndex = 0x81; + pIndex++; + *pIndex = 0x00; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status ); + + /*Incoming user property not initialized*/ + properties.pIncomingUserProperty = NULL; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); +} + +void test_MQTTV5_DeserializeConnackOnlyuint_32( void ) +{ + uint8_t buffer[ 200 ] = { 0 }; + bool session = false; + uint8_t * pIndexLocal = initializeDeserialize( &packetInfo, buffer ); + size_t propertyLength = encodeRemainingLength( pIndexLocal, 10 ); + + packetInfo.remainingLength = propertyLength + 12; + properties.maxPacketSize = 150; + properties.pIncomingUserProperty = &userProperties; + pIndexLocal++; + pIndexLocal = serializeuint_32( pIndexLocal, MQTT_SESSION_EXPIRY_ID ); + pIndexLocal = serializeuint_32( pIndexLocal, MQTT_MAX_PACKET_SIZE_ID ); + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &session ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_INT( MQTT_TEST_UINT32, properties.sessionExpiry ); + TEST_ASSERT_EQUAL_INT( MQTT_TEST_UINT32, properties.serverMaxPacketSize ); + + /*Protocol error to include the same property twice*/ + packetInfo.remainingLength = 13; + pIndexLocal = &buffer[ 2 ]; + propertyLength = encodeRemainingLength( pIndexLocal, 10 ); + pIndexLocal++; + pIndexLocal = serializeuint_32( pIndexLocal, MQTT_SESSION_EXPIRY_ID ); + pIndexLocal = serializeuint_32( pIndexLocal, MQTT_SESSION_EXPIRY_ID ); + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &session ); + TEST_ASSERT_EQUAL_INT( MQTTProtocolError, status ); + + /*Invalid property length*/ + packetInfo.remainingLength = 7; + pIndexLocal = &buffer[ 2 ]; + propertyLength = encodeRemainingLength( pIndexLocal, 4 ); + pIndexLocal++; + *pIndexLocal = MQTT_SESSION_EXPIRY_ID; + pIndexLocal++; + pIndexLocal = serializeuint_32( pIndexLocal, MQTT_SESSION_EXPIRY_ID ); + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &session ); + TEST_ASSERT_EQUAL_INT( MQTTMalformedPacket, status ); + + /*Invalid id*/ + packetInfo.remainingLength = 8; + pIndexLocal = &buffer[ 2 ]; + propertyLength = encodeRemainingLength( pIndexLocal, 5 ); + pIndexLocal++; + pIndexLocal = serializeuint_32( pIndexLocal, 0x00 ); + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &session ); + TEST_ASSERT_EQUAL_INT( MQTTProtocolError, status ); + + /* Max packet size cannot have a value 0*/ + pIndexLocal = &buffer[ 2 ]; + propertyLength = encodeRemainingLength( pIndexLocal, 5 ); + packetInfo.remainingLength = propertyLength + 7; + pIndexLocal++; + *pIndexLocal = MQTT_MAX_PACKET_SIZE_ID; + pIndexLocal++; + pIndexLocal[ 0 ] = UINT32_BYTE3( 0 ); + pIndexLocal[ 1 ] = UINT32_BYTE2( 0 ); + pIndexLocal[ 2 ] = UINT32_BYTE1( 0 ); + pIndexLocal[ 3 ] = UINT32_BYTE0( 0 ); + pIndexLocal = &pIndexLocal[ 4 ]; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &session ); + TEST_ASSERT_EQUAL_INT( MQTTProtocolError, status ); +} + +void test_MQTTV5_DeserializeConnackOnlyuint_16( void ) +{ + uint8_t buffer[ 200 ] = { 0 }; + uint8_t * pIndexLocal = buffer; + + buffer[ 0 ] = 0x01; + buffer[ 1 ] = 0x00; + bool session = false; + packetInfo.pRemainingData = buffer; + packetInfo.type = MQTT_PACKET_TYPE_CONNACK; + pIndexLocal = &buffer[ 2 ]; + properties.pIncomingUserProperty = &userProperties; + properties.maxPacketSize = MQTT_MAX_PACKET_SIZE; + size_t propertyLength = encodeRemainingLength( pIndexLocal, 9 ); + packetInfo.remainingLength = propertyLength + 11; + pIndexLocal++; + pIndexLocal = serializeuint_16( pIndexLocal, MQTT_RECEIVE_MAX_ID ); + pIndexLocal = serializeuint_16( pIndexLocal, MQTT_TOPIC_ALIAS_MAX_ID ); + pIndexLocal = serializeuint_16( pIndexLocal, MQTT_SERVER_KEEP_ALIVE_ID ); + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &session ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_INT( MQTT_TEST_UINT16, properties.serverReceiveMax ); + TEST_ASSERT_EQUAL_INT( MQTT_TEST_UINT16, properties.serverTopicAliasMax ); + TEST_ASSERT_EQUAL_INT( MQTT_TEST_UINT16, properties.serverKeepAlive ); + + /*Receive Max cannot have a value 0*/ + pIndexLocal = &buffer[ 2 ]; + propertyLength = encodeRemainingLength( pIndexLocal, 3 ); + packetInfo.remainingLength = propertyLength + 5; + pIndexLocal++; + *pIndexLocal = MQTT_RECEIVE_MAX_ID; + pIndexLocal++; + pIndexLocal[ 0 ] = UINT16_HIGH_BYTE( 0 ); + pIndexLocal[ 1 ] = UINT16_LOW_BYTE( 0 ); + pIndexLocal = &pIndexLocal[ 2 ]; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &session ); + TEST_ASSERT_EQUAL_INT( MQTTProtocolError, status ); + + /*Protocol error to include the same property twice*/ + pIndexLocal = &buffer[ 2 ]; + propertyLength = encodeRemainingLength( pIndexLocal, 6 ); + packetInfo.remainingLength = propertyLength + 8; + pIndexLocal++; + pIndexLocal = serializeuint_16( pIndexLocal, MQTT_RECEIVE_MAX_ID ); + pIndexLocal = serializeuint_16( pIndexLocal, MQTT_RECEIVE_MAX_ID ); + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &session ); + TEST_ASSERT_EQUAL_INT( MQTTProtocolError, status ); + + /*Invalid property length*/ + packetInfo.remainingLength = 5; + pIndexLocal = &buffer[ 2 ]; + propertyLength = encodeRemainingLength( pIndexLocal, 2 ); + pIndexLocal++; + pIndexLocal = serializeuint_16( pIndexLocal, MQTT_RECEIVE_MAX_ID ); + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &session ); + TEST_ASSERT_EQUAL_INT( MQTTMalformedPacket, status ); +} + +void test_MQTTV5_DeserializeConnackOnlyuint_8( void ) +{ + uint8_t buffer[ 200 ] = { 0 }; + uint8_t * pIndexLocal = buffer; + + buffer[ 0 ] = 0x01; + buffer[ 1 ] = 0x00; + bool session = false; + packetInfo.pRemainingData = buffer; + packetInfo.type = MQTT_PACKET_TYPE_CONNACK; + packetInfo.remainingLength = 13; + pIndexLocal = &buffer[ 2 ]; + properties.pIncomingUserProperty = &userProperties; + properties.maxPacketSize = MQTT_MAX_PACKET_SIZE; + size_t propertyLength = encodeRemainingLength( pIndexLocal, 10 ); + pIndexLocal++; + pIndexLocal = serializeuint_8( pIndexLocal, MQTT_MAX_QOS_ID ); + pIndexLocal = serializeuint_8( pIndexLocal, MQTT_RETAIN_AVAILABLE_ID ); + pIndexLocal = serializeuint_8( pIndexLocal, MQTT_WILDCARD_ID ); + pIndexLocal = serializeuint_8( pIndexLocal, MQTT_SHARED_SUB_ID ); + pIndexLocal = serializeuint_8( pIndexLocal, MQTT_SUB_AVAILABLE_ID ); + + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &session ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_INT( MQTT_TEST_UINT8, properties.serverMaxQos ); + TEST_ASSERT_EQUAL_INT( MQTT_TEST_UINT8, properties.retainAvailable ); + TEST_ASSERT_EQUAL_INT( MQTT_TEST_UINT8, properties.isWildcardAvaiable ); + TEST_ASSERT_EQUAL_INT( MQTT_TEST_UINT8, properties.isSharedAvailable ); + TEST_ASSERT_EQUAL_INT( MQTT_TEST_UINT8, properties.subscriptionId ); + + /*Protocol error to have a value other than 0 or 1*/ + packetInfo.remainingLength = 5; + pIndexLocal = &buffer[ 2 ]; + propertyLength = encodeRemainingLength( pIndexLocal, 2 ); + pIndexLocal++; + *pIndexLocal = MQTT_MAX_QOS_ID; + pIndexLocal++; + pIndexLocal[ 0 ] = 3; + pIndexLocal++; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &session ); + TEST_ASSERT_EQUAL_INT( MQTTProtocolError, status ); + + /*Protocol error to include the same property twice*/ + pIndexLocal = &buffer[ 2 ]; + propertyLength = encodeRemainingLength( pIndexLocal, 4 ); + packetInfo.remainingLength = propertyLength + 6; + pIndexLocal++; + pIndexLocal = serializeuint_8( pIndexLocal, MQTT_MAX_QOS_ID ); + pIndexLocal = serializeuint_8( pIndexLocal, MQTT_MAX_QOS_ID ); + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &session ); + TEST_ASSERT_EQUAL_INT( MQTTProtocolError, status ); + + /*Invalid property length*/ + packetInfo.remainingLength = 4; + pIndexLocal = &buffer[ 2 ]; + propertyLength = encodeRemainingLength( pIndexLocal, 1 ); + pIndexLocal++; + *pIndexLocal = MQTT_MAX_QOS_ID; + pIndexLocal++; + pIndexLocal[ 0 ] = 0; + pIndexLocal++; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &session ); + TEST_ASSERT_EQUAL_INT( MQTTMalformedPacket, status ); +} + + +void test_MQTTV5_DeserializeConnackOnlyutf_8( void ) +{ + uint8_t buffer[ 200 ] = { 0 }; + uint8_t * pIndexLocal = buffer; + + buffer[ 0 ] = 0x01; + buffer[ 1 ] = 0x00; + + bool session = false; + packetInfo.pRemainingData = buffer; + packetInfo.type = MQTT_PACKET_TYPE_CONNACK; + pIndexLocal = &buffer[ 2 ]; + properties.pIncomingUserProperty = &userProperties; + properties.requestResponseInfo = 1; + properties.maxPacketSize = MQTT_MAX_PACKET_SIZE; + size_t propertyLength = encodeRemainingLength( pIndexLocal, 28 ); + packetInfo.remainingLength = propertyLength + 28 + 2; + pIndexLocal++; + pIndexLocal = serializeutf_8( pIndexLocal, MQTT_ASSIGNED_CLIENT_ID ); + pIndexLocal = serializeutf_8( pIndexLocal, MQTT_REASON_STRING_ID ); + pIndexLocal = serializeutf_8( pIndexLocal, MQTT_RESPONSE_INFO_ID ); + pIndexLocal = serializeutf_8( pIndexLocal, MQTT_SERVER_REF_ID ); + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &session ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + + /*Protocol error to include the same property twice*/ + packetInfo.remainingLength = 17; + pIndexLocal = &buffer[ 2 ]; + propertyLength = encodeRemainingLength( pIndexLocal, 14 ); + pIndexLocal++; + pIndexLocal = serializeutf_8( pIndexLocal, MQTT_ASSIGNED_CLIENT_ID ); + pIndexLocal = serializeutf_8( pIndexLocal, MQTT_ASSIGNED_CLIENT_ID ); + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &session ); + TEST_ASSERT_EQUAL_INT( MQTTProtocolError, status ); + + /*Invalid property length*/ + packetInfo.remainingLength = 7; + pIndexLocal = &buffer[ 2 ]; + propertyLength = encodeRemainingLength( pIndexLocal, 4 ); + pIndexLocal++; + pIndexLocal = serializeutf_8( pIndexLocal, MQTT_ASSIGNED_CLIENT_ID ); + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &session ); + TEST_ASSERT_EQUAL_INT( MQTTMalformedPacket, status ); + + /*Invalid property length*/ + pIndexLocal = &buffer[ 2 ]; + propertyLength = encodeRemainingLength( pIndexLocal, 2 ); + packetInfo.remainingLength = propertyLength + 4; + pIndexLocal++; + serializeutf_8( pIndexLocal, MQTT_ASSIGNED_CLIENT_ID ); + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &session ); + TEST_ASSERT_EQUAL_INT( MQTTMalformedPacket, status ); + + /*Protocol error to include response information if is is set to false by client*/ + properties.requestResponseInfo = 0; + packetInfo.remainingLength = 10; + pIndexLocal = &buffer[ 2 ]; + propertyLength = encodeRemainingLength( pIndexLocal, 7 ); + pIndexLocal++; + pIndexLocal = serializeutf_8( pIndexLocal, MQTT_RESPONSE_INFO_ID ); + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &session ); + TEST_ASSERT_EQUAL_INT( MQTTProtocolError, status ); +} + + +void test_MQTTV5_DeserializeConnackOnlyUserProperty( void ) +{ + properties.pIncomingUserProperty = &userProperties; + uint8_t buffer[ 70000 ] = { 0 }; + uint8_t * pIndexLocal = buffer; + buffer[ 0 ] = 0x01; + buffer[ 1 ] = 0x00; + + bool session = false; + properties.maxPacketSize = MQTT_MAX_PACKET_SIZE; + packetInfo.pRemainingData = buffer; + packetInfo.type = MQTT_PACKET_TYPE_CONNACK; + packetInfo.remainingLength = 16; + pIndexLocal = &buffer[ 2 ]; + size_t propertyLength = encodeRemainingLength( pIndexLocal, 13 ); + pIndexLocal++; + pIndexLocal = serializeutf_8pair( pIndexLocal ); + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &session ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_EQUAL( 1, properties.pIncomingUserProperty->count ); + TEST_ASSERT_EQUAL( MQTT_TEST_UTF8_STRING_LENGTH, ( properties.pIncomingUserProperty->userProperty[ 0 ].valueLength ) ); + TEST_ASSERT_EQUAL( MQTT_TEST_UTF8_STRING_LENGTH, ( properties.pIncomingUserProperty->userProperty[ 0 ].keyLength ) ); + + /*Invalid property length*/ + packetInfo.remainingLength = 5; + pIndexLocal = &buffer[ 2 ]; + propertyLength = encodeRemainingLength( pIndexLocal, 2 ); + pIndexLocal++; + pIndexLocal = serializeutf_8pair( pIndexLocal ); + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &session ); + TEST_ASSERT_EQUAL_INT( MQTTMalformedPacket, status ); + + /*Invalid property length*/ + packetInfo.remainingLength = 6; + pIndexLocal = &buffer[ 2 ]; + propertyLength = encodeRemainingLength( pIndexLocal, 3 ); + pIndexLocal++; + pIndexLocal = serializeutf_8pair( pIndexLocal ); + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &session ); + TEST_ASSERT_EQUAL_INT( MQTTMalformedPacket, status ); + + /*Invalid property length*/ + pIndexLocal = &buffer[ 2 ]; + propertyLength = encodeRemainingLength( pIndexLocal, 8 ); + packetInfo.remainingLength = propertyLength + 10; + pIndexLocal++; + pIndexLocal = serializeutf_8pair( pIndexLocal ); + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &session ); + TEST_ASSERT_EQUAL_INT( MQTTMalformedPacket, status ); + + /*Invalid property length*/ + packetInfo.remainingLength = 15; + pIndexLocal = &buffer[ 2 ]; + propertyLength = encodeRemainingLength( pIndexLocal, 12 ); + pIndexLocal++; + pIndexLocal = serializeutf_8pair( pIndexLocal ); + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &session ); + TEST_ASSERT_EQUAL_INT( MQTTMalformedPacket, status ); + + /*Discard user property*/ + packetInfo.remainingLength = 65018; + pIndexLocal = &buffer[ 2 ]; + propertyLength = encodeRemainingLength( pIndexLocal, 65013 ); + pIndexLocal += 3; + uint32_t i = 0U; + + for( ; i < 5001; i++ ) + { + pIndexLocal = serializeutf_8pair( pIndexLocal ); + } + + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &session ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + + properties.pIncomingUserProperty->count = 0; + packetInfo.remainingLength = 65017; + pIndexLocal = &buffer[ 2 ]; + propertyLength = encodeRemainingLength( pIndexLocal, 65012 ); + pIndexLocal += 3; + + for( ; i < 5001; i++ ) + { + pIndexLocal = serializeutf_8pair( pIndexLocal ); + } + + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &session ); + TEST_ASSERT_EQUAL_INT( MQTTMalformedPacket, status ); +} + + + +void test_MQTTV5_DeserializeConnackOnlyAuthInfo( void ) +{ + MQTTAuthInfo_t auth; + MQTTAuthInfo_t auth1; + + properties.pIncomingAuth = &auth; + properties.pOutgoingAuth = &auth1; + uint8_t buffer[ 200 ] = { 0 }; + uint8_t * pIndexLocal = buffer; + buffer[ 0 ] = 0x01; + buffer[ 1 ] = 0x00; + + bool session = false; + packetInfo.pRemainingData = buffer; + packetInfo.type = MQTT_PACKET_TYPE_CONNACK; + packetInfo.remainingLength = 17; + pIndexLocal = &buffer[ 2 ]; + properties.pIncomingUserProperty = &userProperties; + properties.maxPacketSize = MQTT_MAX_PACKET_SIZE; + size_t propertyLength = encodeRemainingLength( pIndexLocal, 14 ); + packetInfo.remainingLength = propertyLength + 14 + 2; + pIndexLocal++; + pIndexLocal = serializeutf_8( pIndexLocal, MQTT_AUTH_METHOD_ID ); + pIndexLocal = serializeutf_8( pIndexLocal, MQTT_AUTH_DATA_ID ); + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &session ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_EQUAL( MQTT_TEST_UTF8_STRING_LENGTH, properties.pIncomingAuth->authMethodLength ); + TEST_ASSERT_EQUAL( MQTT_TEST_UTF8_STRING_LENGTH, properties.pIncomingAuth->authDataLength ); + + + /*Outgoing auth is null*/ + properties.pOutgoingAuth = NULL; + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &session ); + TEST_ASSERT_EQUAL_INT( MQTTProtocolError, status ); +} + + +void test_MQTTV5_GetConnectPacketSize( void ) +{ + size_t remainingLength = 0; + size_t packetSize = 0; + MQTTUserProperties_t incomingProperty; + + + /* Call MQTTV5_GetConnectPacketSize() with various combinations of + * incorrect paramters */ + properties.receiveMax = 65535U; + properties.maxPacketSize = MQTT_MAX_PACKET_SIZE; + properties.requestProblemInfo = 1; + properties.pIncomingUserProperty = &incomingProperty; + status = MQTTV5_GetConnectPacketSize( NULL, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, NULL, &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, NULL ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Verify empty connect info fails. */ + memset( &connectInfo, 0x0, sizeof( connectInfo ) ); + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Verify empty client identifier fails. */ + connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; + connectInfo.clientIdentifierLength = 0; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + connectInfo.pClientIdentifier = NULL; + connectInfo.clientIdentifierLength = CLIENT_IDENTIFIER_LENGTH; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, NULL, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Test a will message payload length that is too large. */ + connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; + connectInfo.clientIdentifierLength = UINT16_MAX; + connectInfo.pPassword = ""; + connectInfo.passwordLength = UINT16_MAX; + connectInfo.pUserName = ""; + connectInfo.userNameLength = UINT16_MAX; + publishInfo.pTopicName = TEST_TOPIC_NAME; + publishInfo.topicNameLength = UINT16_MAX; + /* A valid will message payload is less than the maximum 16 bit integer. */ + publishInfo.payloadLength = UINT16_MAX + 2; + status = MQTTV5_GetConnectPacketSize( &connectInfo, &publishInfo, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + /* Verify good case */ + memset( &connectInfo, 0x0, sizeof( connectInfo ) ); + connectInfo.cleanSession = true; + connectInfo.pClientIdentifier = "TEST"; + connectInfo.clientIdentifierLength = 4; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_INT( 0, properties.propertyLength ); + /* Make sure remaining size returned is 17. */ + TEST_ASSERT_EQUAL_INT( 17, remainingLength ); + /* Make sure packet size is 19. */ + TEST_ASSERT_EQUAL_INT( 19, packetSize ); + + /* With will. These parameters will cause the packet to be + * 4 + 2 + 8 + 2 = 16 bytes larger. */ + publishInfo.pTopicName = "test"; + publishInfo.topicNameLength = 4; + publishInfo.pPayload = "testload"; + publishInfo.payloadLength = 8; + status = MQTTV5_GetConnectPacketSize( &connectInfo, &publishInfo, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + /* Make sure remaining size returned is 32 = 16 + 16 + 2. */ + TEST_ASSERT_EQUAL_INT( 34, remainingLength ); + /* Make sure packet size is 34 = 18 + 16 + 2. */ + TEST_ASSERT_EQUAL_INT( 36, packetSize ); + + /* With username and password. This will add 4 + 2 + 4 + 2 = 12 bytes. */ + connectInfo.cleanSession = true; + connectInfo.pUserName = "USER"; + connectInfo.userNameLength = 4; + connectInfo.pPassword = "PASS"; + connectInfo.passwordLength = 4; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + /* Make sure remaining size returned is 28 = 16 + 12. */ + TEST_ASSERT_EQUAL_INT( 29, remainingLength ); + /* Make sure packet size is 30 = 18 + 12. */ + TEST_ASSERT_EQUAL_INT( 31, packetSize ); + + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, NULL, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /*Validating the function Get Connect properties*/ + + /* Call MQTT_GetConnectPropertiesSize() with various combinations of + * incorrect paramters */ + + /* + * Max Packet Size cannot be null + */ + properties.receiveMax = 24; + properties.maxPacketSize = 0; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + /* + * Receive Maximum cannot be 0 + */ + properties.maxPacketSize = 40; + properties.receiveMax = 0; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + properties.receiveMax = 24; + properties.requestProblemInfo = 1; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_size_t( 8, properties.propertyLength ); + + properties.sessionExpiry = 24; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_size_t( 13, properties.propertyLength ); + + properties.topicAliasMax = 24; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_size_t( 16, properties.propertyLength ); + + properties.requestResponseInfo = 1; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_size_t( 18, properties.propertyLength ); + + properties.requestProblemInfo = 0; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_size_t( 20, properties.propertyLength ); + + MQTTUserProperties_t userProperties; + memset( &userProperties, 0x0, sizeof( userProperties ) ); + + userProperties.count = 1; + properties.pOutgoingUserProperty = &userProperties; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + userProperties.userProperty[ 0 ].keyLength = 3; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + userProperties.userProperty[ 0 ].valueLength = 1; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + userProperties.userProperty[ 0 ].pValue = "1"; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + userProperties.userProperty[ 0 ].pKey = "2"; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + + userProperties.userProperty[ 0 ].pValue = NULL; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + userProperties.userProperty[ 0 ].keyLength = 0; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + userProperties.userProperty[ 0 ].valueLength = 0; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + userProperties.userProperty[ 0 ].keyLength = 1; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + userProperties.userProperty[ 0 ].valueLength = 1; + userProperties.userProperty[ 0 ].keyLength = 0; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + properties.pOutgoingUserProperty->count = 6000; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + properties.pOutgoingUserProperty->count = 1; + + /* + * Incoming AuthInfo not intialized. + */ + properties.pOutgoingUserProperty = &userProperties; + MQTTAuthInfo_t auth; + memset( &auth, 0x0, sizeof( auth ) ); + properties.pOutgoingAuth = &auth; + auth.authDataLength = 1; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + MQTTAuthInfo_t auth2; + memset( &auth2, 0x0, sizeof( auth2 ) ); + properties.pIncomingAuth = &auth2; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + auth.authDataLength = 0; + auth.authMethodLength = 0; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + /* + * Protocol Error to include Authentication Data if there is no Authentication Method + */ + auth.pAuthData = "1"; + auth.authDataLength = 1; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + auth.authMethodLength = 3; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + auth.pAuthMethod = "234"; + auth.authDataLength = 0; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + auth.pAuthData = NULL; + auth.authDataLength = 1; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + /*Connect Properties*/ + memset( &properties, 0x0, sizeof( properties ) ); + properties.sessionExpiry = 22; + properties.receiveMax = 34; + properties.maxPacketSize = 32; + properties.topicAliasMax = 12; + properties.requestResponseInfo = 1; + properties.requestProblemInfo = 0; + userProperties.userProperty[ 0 ].keyLength = 3; + userProperties.userProperty[ 0 ].valueLength = 1; + userProperties.userProperty[ 0 ].pValue = "1"; + userProperties.userProperty[ 0 ].pKey = "211"; + userProperties.count = 1; + properties.pOutgoingUserProperty = &userProperties; + properties.pIncomingUserProperty = &incomingProperty; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + /* Make sure remaining size returned is 58. */ + TEST_ASSERT_EQUAL_INT( 58, remainingLength ); + /* Make sure packet size is 60. */ + TEST_ASSERT_EQUAL_INT( 60, packetSize ); + + /*Validating the will properties*/ + memset( &publishInfo, 0x0, sizeof( publishInfo ) ); + status = MQTTV5_GetConnectPacketSize( &connectInfo, &publishInfo, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_size_t( 0, publishInfo.propertyLength ); + + publishInfo.willDelay = 10; + status = MQTTV5_GetConnectPacketSize( &connectInfo, &publishInfo, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_size_t( 5, publishInfo.propertyLength ); + + publishInfo.payloadFormat = 1; + status = MQTTV5_GetConnectPacketSize( &connectInfo, &publishInfo, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_size_t( 7, publishInfo.propertyLength ); + + publishInfo.msgExpiryPresent = 1; + publishInfo.msgExpiryInterval = 10; + status = MQTTV5_GetConnectPacketSize( &connectInfo, &publishInfo, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_size_t( 12, publishInfo.propertyLength ); + + publishInfo.contentTypeLength = 2; + status = MQTTV5_GetConnectPacketSize( &connectInfo, &publishInfo, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + publishInfo.pContentType = MQTT_TEST_UTF8_STRING; + status = MQTTV5_GetConnectPacketSize( &connectInfo, &publishInfo, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_size_t( 17, publishInfo.propertyLength ); + + publishInfo.responseTopicLength = 2; + status = MQTTV5_GetConnectPacketSize( &connectInfo, &publishInfo, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + publishInfo.pResponseTopic = MQTT_TEST_UTF8_STRING; + status = MQTTV5_GetConnectPacketSize( &connectInfo, &publishInfo, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_size_t( 22, publishInfo.propertyLength ); + + publishInfo.correlationLength = 2; + status = MQTTV5_GetConnectPacketSize( &connectInfo, &publishInfo, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + publishInfo.pCorrelationData = MQTT_TEST_UTF8_STRING; + status = MQTTV5_GetConnectPacketSize( &connectInfo, &publishInfo, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_size_t( 27, publishInfo.propertyLength ); + + MQTTUserProperties_t userProperties1; + userProperties1.userProperty[ 0 ].pKey = "2"; + userProperties1.userProperty[ 0 ].keyLength = 1; + userProperties1.userProperty[ 0 ].valueLength = 3; + userProperties1.userProperty[ 0 ].pValue = "abc"; + userProperties1.userProperty[ 1 ].pKey = "2"; + userProperties1.userProperty[ 1 ].keyLength = 1; + userProperties1.userProperty[ 1 ].valueLength = 2; + userProperties1.userProperty[ 1 ].pValue = MQTT_TEST_UTF8_STRING; + userProperties1.count = 2; + publishInfo.pUserProperty = &userProperties1; + status = MQTT_GetPublishPropertiesSize( &publishInfo ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_size_t( 44, publishInfo.propertyLength ); + + publishInfo.payloadFormat = 1; + publishInfo.msgExpiryPresent = 1; + publishInfo.msgExpiryInterval = 10; + publishInfo.msgExpiryPresent = 1; + publishInfo.msgExpiryInterval = 10; + publishInfo.contentTypeLength = 2; + publishInfo.pContentType = MQTT_TEST_UTF8_STRING; + publishInfo.responseTopicLength = 2; + publishInfo.pResponseTopic = MQTT_TEST_UTF8_STRING; + publishInfo.correlationLength = 2; + publishInfo.pCorrelationData = MQTT_TEST_UTF8_STRING; + publishInfo.willDelay = 3; + /* 34 + 12 + 29 */ + status = MQTTV5_GetConnectPacketSize( &connectInfo, &publishInfo, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + /* Make sure remaining size returned is 107. */ + TEST_ASSERT_EQUAL_INT( 107, remainingLength ); + /* Make sure packet size is 109. */ + TEST_ASSERT_EQUAL_INT( 109, packetSize ); + /*Limit of property length*/ + memset( &properties, 0x0, sizeof( properties ) ); + memset( &publishInfo, 0x0, sizeof( publishInfo ) ); + /*5*/ + properties.receiveMax = UINT16_MAX; + properties.maxPacketSize = MQTT_MAX_PACKET_SIZE; + properties.requestProblemInfo = 1; + userProperties.count = 2078; + properties.pOutgoingUserProperty = &userProperties; + properties.pIncomingUserProperty = &incomingProperty; + uint16_t i = 0; + char str[ 65535 ]; + memset( str, '.', 65535 * sizeof( char ) ); + + for( ; i < 3500; i++ ) + { + userProperties.userProperty[ i ].keyLength = UINT16_MAX; + userProperties.userProperty[ i ].pKey = str; + userProperties.userProperty[ i ].pValue = str; + userProperties.userProperty[ i ].valueLength = UINT16_MAX; + } + + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + properties.pOutgoingUserProperty = NULL; + userProperties.count = 2048; + publishInfo.pUserProperty = &userProperties; + status = MQTTV5_GetConnectPacketSize( &connectInfo, &publishInfo, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + userProperties.count = 2051; + status = MQTTV5_GetConnectPacketSize( &connectInfo, &publishInfo, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + /*Incoming user property not initialized*/ + properties.pIncomingUserProperty = NULL; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); +} + +/* / ** */ +/* * @brief Tests that MQTTV5_SerializeConnect works as intended. */ +/* * / */ +void test_MQTTV5_SerializeConnect( void ) +{ + size_t remainingLength = 0; + uint8_t buffer[ 140 + 2 * BUFFER_PADDING_LENGTH ]; + size_t bufferSize = sizeof( buffer ) - 2 * BUFFER_PADDING_LENGTH; + size_t packetSize = bufferSize; + MQTTStatus_t status = MQTTSuccess; + MQTTUserProperties_t incomingProperty; + + properties.pIncomingUserProperty = &incomingProperty; + setupProperties( &properties ); + MQTTFixedBuffer_t fixedBuffer = { .pBuffer = &buffer[ BUFFER_PADDING_LENGTH ], .size = bufferSize }; + + /* Verify bad parameter errors. */ + status = MQTTV5_SerializeConnect( NULL, &publishInfo, &properties, remainingLength, &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + status = MQTTV5_SerializeConnect( &connectInfo, &publishInfo, &properties, remainingLength, NULL ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + status = MQTTV5_SerializeConnect( &connectInfo, &publishInfo, NULL, remainingLength, &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + memset( &connectInfo, 0x0, sizeof( connectInfo ) ); + status = MQTTV5_SerializeConnect( &connectInfo, NULL, &properties, 112345, &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTNoMemory, status ); + + /* Create a good connection info. */ + connectInfo.pClientIdentifier = "TEST"; + connectInfo.clientIdentifierLength = 4; + + /* Inject a invalid fixed buffer test with a good connectInfo. */ + memset( &fixedBuffer, 0x0, sizeof( fixedBuffer ) ); + status = MQTTV5_SerializeConnect( &connectInfo, NULL, &properties, remainingLength, &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Good case succeeds. */ + /* Set the fixedBuffer properly for the rest of the succeeding test. */ + fixedBuffer.pBuffer = &buffer[ BUFFER_PADDING_LENGTH ]; + fixedBuffer.size = bufferSize; + + /* Calculate a good packet size. */ + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + /* Make sure buffer has enough space */ + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); + /* Make sure test succeeds. */ + padAndResetBuffer( buffer, sizeof( buffer ) ); + status = MQTTV5_SerializeConnect( &connectInfo, NULL, &properties, remainingLength, &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + checkBufferOverflow( buffer, sizeof( buffer ) ); + + /* Encode user name. Also try clean session. */ + connectInfo.cleanSession = true; + connectInfo.pUserName = "USER"; + connectInfo.userNameLength = 4; + connectInfo.pPassword = "PASS"; + connectInfo.passwordLength = 4; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); + padAndResetBuffer( buffer, sizeof( buffer ) ); + status = MQTTV5_SerializeConnect( &connectInfo, NULL, &properties, remainingLength, &fixedBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + checkBufferOverflow( buffer, sizeof( buffer ) ); + + /* Serialize connect with LWT. */ + /* Test for NULL topic name. */ + ( void ) memset( &publishInfo, 0x00, sizeof( MQTTPublishInfo_t ) ); + publishInfo.retain = true; + publishInfo.qos = MQTTQoS1; + publishInfo.pPayload = "test"; + publishInfo.payloadLength = ( uint16_t ) strlen( publishInfo.pPayload ); + status = MQTTV5_GetConnectPacketSize( &connectInfo, &publishInfo, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); + status = MQTTV5_SerializeConnect( &connectInfo, &publishInfo, &properties, remainingLength, &fixedBuffer ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + /* Success. */ + ( void ) memset( &publishInfo, 0x00, sizeof( MQTTPublishInfo_t ) ); + publishInfo.retain = true; + publishInfo.qos = MQTTQoS1; + publishInfo.pTopicName = "test"; + publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); + publishInfo.pPayload = "test"; + publishInfo.payloadLength = ( uint16_t ) strlen( publishInfo.pPayload ); + status = MQTTV5_GetConnectPacketSize( &connectInfo, &publishInfo, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); + padAndResetBuffer( buffer, sizeof( buffer ) ); + status = MQTTV5_SerializeConnect( &connectInfo, &publishInfo, &properties, remainingLength, &fixedBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + checkBufferOverflow( buffer, sizeof( buffer ) ); + + /* Again with QoS 2 and 0. */ + + publishInfo.qos = MQTTQoS2; + status = MQTTV5_GetConnectPacketSize( &connectInfo, &publishInfo, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); + padAndResetBuffer( buffer, sizeof( buffer ) ); + status = MQTTV5_SerializeConnect( &connectInfo, &publishInfo, &properties, remainingLength, &fixedBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + checkBufferOverflow( buffer, sizeof( buffer ) ); + + publishInfo.qos = MQTTQoS0; + publishInfo.retain = false; + /* NULL payload is acceptable. */ + publishInfo.pPayload = NULL; + publishInfo.payloadLength = 0; + status = MQTTV5_GetConnectPacketSize( &connectInfo, &publishInfo, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); + padAndResetBuffer( buffer, sizeof( buffer ) ); + status = MQTTV5_SerializeConnect( &connectInfo, &publishInfo, &properties, remainingLength, &fixedBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + checkBufferOverflow( buffer, sizeof( buffer ) ); + + /* Success right on the buffer boundary. */ + connectInfo.pUserName = "USER"; + connectInfo.userNameLength = 4; + /* Throwing in a possible valid zero length password. */ + connectInfo.pPassword = "PASS"; + connectInfo.passwordLength = 0; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); + /* Set the fixed buffer to exactly the size of the packet. */ + fixedBuffer.size = packetSize; + padAndResetBuffer( buffer, sizeof( buffer ) ); + status = MQTTV5_SerializeConnect( &connectInfo, NULL, &properties, remainingLength, &fixedBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + checkBufferOverflow( buffer, sizeof( buffer ) ); + /*Connect properties not initialized*/ + status = MQTTV5_SerializeConnect( &connectInfo, &publishInfo, NULL, remainingLength, &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /*Connect Properties*/ + properties.sessionExpiry = 22; + properties.receiveMax = 34; + properties.maxPacketSize = 32; + properties.topicAliasMax = 12; + properties.requestResponseInfo = 1; + properties.requestProblemInfo = 0; + MQTTAuthInfo_t auth; + memset( &auth, 0x0, sizeof( auth ) ); + auth.pAuthMethod = MQTT_TEST_UTF8_STRING; + auth.authMethodLength = 2; + auth.pAuthData = "abc"; + auth.authDataLength = 3; + userProperties.userProperty[ 0 ].keyLength = 3; + userProperties.userProperty[ 0 ].valueLength = 1; + userProperties.userProperty[ 0 ].pValue = "1"; + userProperties.userProperty[ 0 ].pKey = "211"; + userProperties.count = 1; + properties.pOutgoingUserProperty = &userProperties; + properties.pOutgoingAuth = &auth; + properties.pIncomingAuth = &auth; + /* 29 */ + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); + /* Set the fixed buffer to exactly the size of the packet. */ + fixedBuffer.size = packetSize; + padAndResetBuffer( buffer, sizeof( buffer ) ); + status = MQTTV5_SerializeConnect( &connectInfo, NULL, &properties, remainingLength, &fixedBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + checkBufferOverflow( buffer, sizeof( buffer ) ); + + auth.authDataLength = 0; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); + /* Set the fixed buffer to exactly the size of the packet. */ + fixedBuffer.size = packetSize; + padAndResetBuffer( buffer, sizeof( buffer ) ); + status = MQTTV5_SerializeConnect( &connectInfo, NULL, &properties, remainingLength, &fixedBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + checkBufferOverflow( buffer, sizeof( buffer ) ); + + properties.pOutgoingAuth = NULL; + status = MQTTV5_GetConnectPacketSize( &connectInfo, NULL, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); + /* Set the fixed buffer to exactly the size of the packet. */ + fixedBuffer.size = packetSize; + padAndResetBuffer( buffer, sizeof( buffer ) ); + status = MQTTV5_SerializeConnect( &connectInfo, NULL, &properties, remainingLength, &fixedBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + checkBufferOverflow( buffer, sizeof( buffer ) ); + + /*Will Properties*/ + publishInfo.payloadFormat = 1; + publishInfo.msgExpiryPresent = 1; + publishInfo.msgExpiryInterval = 10; + publishInfo.msgExpiryPresent = 1; + publishInfo.msgExpiryInterval = 10; + publishInfo.contentTypeLength = MQTT_TEST_UTF8_STRING_LENGTH; + publishInfo.pContentType = MQTT_TEST_UTF8_STRING; + publishInfo.responseTopicLength = MQTT_TEST_UTF8_STRING_LENGTH; + publishInfo.pResponseTopic = MQTT_TEST_UTF8_STRING; + publishInfo.correlationLength = MQTT_TEST_UTF8_STRING_LENGTH; + publishInfo.pCorrelationData = MQTT_TEST_UTF8_STRING; + publishInfo.willDelay = 3; + publishInfo.pUserProperty = &userProperties; + /* 27 */ + status = MQTTV5_GetConnectPacketSize( &connectInfo, &publishInfo, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); + /* Set the fixed buffer to exactly the size of the packet. */ + fixedBuffer.size = packetSize; + padAndResetBuffer( buffer, sizeof( buffer ) ); + status = MQTTV5_SerializeConnect( &connectInfo, &publishInfo, &properties, remainingLength, &fixedBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + checkBufferOverflow( buffer, sizeof( buffer ) ); +} + +/** + * @brief This method calls MQTT_SerializeConnect successfully using different parameters + * until we have full coverage on the private method, serializeConnectPacket(...). + */ +void test_MQTTV5_SerializeConnect_Happy_Paths() +{ + MQTTStatus_t mqttStatus = MQTTSuccess; + size_t remainingLength = 0; + size_t packetSize = 0; + MQTTFixedBuffer_t networkBuffer; + MQTTUserProperties_t incomingProperty; + + properties.sessionExpiry = 22; + properties.receiveMax = 34; + properties.maxPacketSize = 32; + properties.topicAliasMax = 12; + properties.requestResponseInfo = 1; + properties.requestProblemInfo = 0; + properties.pIncomingUserProperty = &incomingProperty; + publishInfo.payloadFormat = 1; + publishInfo.msgExpiryPresent = 1; + publishInfo.msgExpiryInterval = 10; + publishInfo.msgExpiryPresent = 1; + publishInfo.msgExpiryInterval = 10; + publishInfo.contentTypeLength = MQTT_TEST_UTF8_STRING_LENGTH; + publishInfo.pContentType = MQTT_TEST_UTF8_STRING; + publishInfo.responseTopicLength = MQTT_TEST_UTF8_STRING_LENGTH; + publishInfo.pResponseTopic = MQTT_TEST_UTF8_STRING; + publishInfo.correlationLength = MQTT_TEST_UTF8_STRING_LENGTH; + publishInfo.pCorrelationData = MQTT_TEST_UTF8_STRING; + publishInfo.willDelay = 3; + /* Fill structs to pass into methods to be tested. */ + setupNetworkBuffer( &networkBuffer ); + setupConnectInfo( &connectInfo ); + setupPublishInfo( &publishInfo ); + publishInfo.dup = true; + publishInfo.retain = true; + /* Get MQTT connect packet size and remaining length. */ + mqttStatus = MQTTV5_GetConnectPacketSize( &connectInfo, + &publishInfo, + &properties, + &remainingLength, + &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + /* Make sure buffer has enough space. */ + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, networkBuffer.size ); + mqttStatus = MQTTV5_SerializeConnect( &connectInfo, &publishInfo, &properties, + remainingLength, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + verifySerializedConnectPacket( &connectInfo, &publishInfo, &properties, + remainingLength, &networkBuffer ); + + /* / * Repeat with MQTTQoS1. * / */ + publishInfo.qos = MQTTQoS1; + mqttStatus = MQTTV5_GetConnectPacketSize( &connectInfo, + &publishInfo, + &properties, + &remainingLength, + &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + /* Make sure buffer has enough space. */ + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, networkBuffer.size ); + mqttStatus = MQTTV5_SerializeConnect( &connectInfo, &publishInfo, &properties, + remainingLength, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + verifySerializedConnectPacket( &connectInfo, &publishInfo, &properties, + remainingLength, &networkBuffer ); + + /* Re-initialize objects for branch coverage. */ + publishInfo.pPayload = MQTT_SAMPLE_PAYLOAD; + publishInfo.payloadLength = MQTT_SAMPLE_PAYLOAD_LEN; + publishInfo.pTopicName = MQTT_CLIENT_IDENTIFIER; + publishInfo.topicNameLength = MQTT_CLIENT_IDENTIFIER_LEN; + publishInfo.dup = true; + publishInfo.qos = MQTTQoS2; + publishInfo.retain = false; + connectInfo.cleanSession = false; + connectInfo.pClientIdentifier = MQTT_CLIENT_IDENTIFIER; + connectInfo.clientIdentifierLength = MQTT_CLIENT_IDENTIFIER_LEN; + connectInfo.pUserName = NULL; + connectInfo.userNameLength = 0; + connectInfo.pPassword = NULL; + connectInfo.passwordLength = 0; + + mqttStatus = MQTTV5_GetConnectPacketSize( &connectInfo, + &publishInfo, + &properties, + &remainingLength, + &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + /* Make sure buffer has enough space. */ + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, networkBuffer.size ); + mqttStatus = MQTTV5_SerializeConnect( &connectInfo, &publishInfo, &properties, + remainingLength, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + verifySerializedConnectPacket( &connectInfo, &publishInfo, &properties, + remainingLength, &networkBuffer ); + + /* Repeat with NULL publishInfo. */ + mqttStatus = MQTTV5_GetConnectPacketSize( &connectInfo, + NULL, + &properties, + &remainingLength, + &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + /* Make sure buffer has enough space. */ + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, networkBuffer.size ); + mqttStatus = MQTTV5_SerializeConnect( &connectInfo, NULL, &properties, + remainingLength, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + verifySerializedConnectPacket( &connectInfo, NULL, &properties, + remainingLength, &networkBuffer ); +} + + +void test_RemaininglengthLimit( void ) +{ + /* Test will property length more than the max value allowed. */ + size_t remainingLength = 0; + size_t packetSize = 0; + uint32_t maxPacketSize; + MQTTStatus_t status = MQTTSuccess; + MQTTUserProperties_t incomingProperty; + MQTTAckInfo_t ackInfo; + + connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; + connectInfo.clientIdentifierLength = UINT16_MAX; + connectInfo.pPassword = ""; + connectInfo.passwordLength = UINT16_MAX; + connectInfo.pUserName = ""; + properties.receiveMax = UINT16_MAX; + properties.requestProblemInfo = 1; + properties.pIncomingUserProperty = &incomingProperty; + publishInfo.payloadFormat = 1; + publishInfo.msgExpiryPresent = 1; + publishInfo.msgExpiryInterval = 10; + publishInfo.contentTypeLength = MQTT_TEST_UTF8_STRING_LENGTH; + publishInfo.pContentType = MQTT_TEST_UTF8_STRING; + publishInfo.responseTopicLength = MQTT_TEST_UTF8_STRING_LENGTH; + publishInfo.pResponseTopic = MQTT_TEST_UTF8_STRING; + publishInfo.correlationLength = MQTT_TEST_UTF8_STRING_LENGTH; + publishInfo.pCorrelationData = MQTT_TEST_UTF8_STRING; + publishInfo.willDelay = 3; + uint16_t i = 0; + char str[ 65535 ]; + memset( str, '.', 65535 * sizeof( char ) ); + + for( ; i < 3500; i++ ) + { + userProperties.userProperty[ i ].keyLength = UINT16_MAX; + userProperties.userProperty[ i ].pKey = str; + userProperties.userProperty[ i ].pValue = str; + userProperties.userProperty[ i ].valueLength = UINT16_MAX; + } + + userProperties.count = 2048; + publishInfo.pUserProperty = &userProperties; + status = MQTT_GetPublishPropertiesSize( &publishInfo ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + status = MQTTV5_GetConnectPacketSize( &connectInfo, &publishInfo, &properties, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + publishInfo.topicNameLength = 0U; + publishInfo.topicAlias = 1U; + status = MQTTV5_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize, MQTT_MAX_REMAINING_LENGTH ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + memset( &ackInfo, 0x0, sizeof( ackInfo ) ); + ackInfo.pUserProperty = &userProperties; + maxPacketSize = UINT32_MAX; + status = MQTTV5_GetAckPacketSize( &ackInfo, &remainingLength, &packetSize, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + status = MQTTV5_GetDisconnectPacketSize( &ackInfo, &remainingLength, &packetSize, maxPacketSize, 0, 0 ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); +} + +void test_MQTTV5_ValidatePublishParams() +{ + uint16_t topicAliasMax = 10U; + uint8_t maxQos = 0U; + uint8_t retain = 0U; + + /*Publish info cannot be null*/ + status = MQTTV5_ValidatePublishParams( NULL, topicAliasMax, retain, maxQos ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + /*Topic alias greater than the allowed value. */ + publishInfo.topicAlias = 12U; + status = MQTTV5_ValidatePublishParams( &publishInfo, topicAliasMax, retain, maxQos ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + /*Retain is not allowed. */ + publishInfo.topicAlias = 2U; + publishInfo.retain = true; + status = MQTTV5_ValidatePublishParams( &publishInfo, topicAliasMax, retain, maxQos ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + /*Qos invalid*/ + publishInfo.retain = false; + publishInfo.qos = 1; + status = MQTTV5_ValidatePublishParams( &publishInfo, topicAliasMax, retain, maxQos ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + /*Valid parameters should return success*/ + publishInfo.qos = 1; + maxQos = 1; + publishInfo.retain = true; + retain = 1; + status = MQTTV5_ValidatePublishParams( &publishInfo, topicAliasMax, retain, maxQos ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + + /*Valid parameters should return success*/ + publishInfo.qos = 0; + status = MQTTV5_ValidatePublishParams( &publishInfo, topicAliasMax, retain, maxQos ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); +} + +void test_MQTTV5_GetPublishPacketSize() +{ + size_t remainingLength = 0U; + size_t packetSize = 0U; + uint32_t maxPacketSize = 0U; + + setupPublishInfo( &publishInfo ); + /*Test with invalid paramters*/ + status = MQTTV5_GetPublishPacketSize( NULL, &remainingLength, &packetSize, maxPacketSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + status = MQTTV5_GetPublishPacketSize( &publishInfo, NULL, &packetSize, maxPacketSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + status = MQTTV5_GetPublishPacketSize( &publishInfo, &remainingLength, NULL, maxPacketSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + status = MQTTV5_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize, maxPacketSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + /*Topic name invalid*/ + publishInfo.pTopicName = NULL; + status = MQTTV5_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize, maxPacketSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + publishInfo.pTopicName = TEST_TOPIC_NAME; + + /*Topic alias is not allowed and topic name is not provided.*/ + publishInfo.topicNameLength = 0; + status = MQTTV5_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize, maxPacketSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + maxPacketSize = 100; + publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; + /*Packet size too large*/ + publishInfo.payloadLength = MQTT_MAX_REMAINING_LENGTH; + status = MQTTV5_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize, maxPacketSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + publishInfo.payloadLength = MQTT_MAX_REMAINING_LENGTH - TEST_TOPIC_NAME_LENGTH - 4; + status = MQTTV5_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize, maxPacketSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + /* Good case succeeds. */ + publishInfo.pPayload = ""; + publishInfo.payloadLength = 0; + status = MQTTV5_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize, maxPacketSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + + /* Again with QoS 2. */ + publishInfo.qos = MQTTQoS2; + status = MQTTV5_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize, maxPacketSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + + setupPublishProperties( &publishInfo ); + publishInfo.retain = true; + /*Valid properties*/ + status = MQTTV5_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize, maxPacketSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + + /* No topic name*/ + publishInfo.topicNameLength = 0U; + publishInfo.pTopicName = NULL; + status = MQTTV5_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize, maxPacketSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + + /*Packet size is more than the server allowed max packet size*/ + maxPacketSize = 4; + status = MQTTV5_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize, maxPacketSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); +} + +void test_MQTT_SerializePublish() +{ + uint32_t maxPacketSize = 200U; + size_t remainingLength = 98; + uint8_t buffer[ 200 + 2 * BUFFER_PADDING_LENGTH ]; + size_t bufferSize = sizeof( buffer ) - 2 * BUFFER_PADDING_LENGTH; + size_t packetSize = bufferSize; + MQTTStatus_t status = MQTTSuccess; + MQTTFixedBuffer_t fixedBuffer = { .pBuffer = &buffer[ BUFFER_PADDING_LENGTH ], .size = bufferSize }; + const uint16_t PACKET_ID = 1; + + setupPublishInfo( &publishInfo ); + fixedBuffer.size = bufferSize; + /* Calculate exact packet size and remaining length. */ + status = MQTTV5_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + /* Make sure buffer has enough space */ + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); + + padAndResetBuffer( buffer, sizeof( buffer ) ); + status = MQTT_SerializePublish( &publishInfo, + PACKET_ID, + remainingLength, + &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + checkBufferOverflow( buffer, sizeof( buffer ) ); + + fixedBuffer.pBuffer = &buffer[ BUFFER_PADDING_LENGTH ]; + setupPublishProperties( &publishInfo ); + /* Calculate exact packet size and remaining length. */ + status = MQTTV5_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + /* Make sure buffer has enough space */ + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); + + padAndResetBuffer( buffer, sizeof( buffer ) ); + status = MQTT_SerializePublish( &publishInfo, + PACKET_ID, + remainingLength, + &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + checkBufferOverflow( buffer, sizeof( buffer ) ); +} + +void test_MQTTV5_DeserializeAck_puback( void ) +{ + MQTTPacketInfo_t mqttPacketInfo; + MQTTAckInfo_t ackInfo; + uint16_t packetIdentifier; + uint32_t maxPacketSize = 0U; + bool requestProblem = false; + MQTTStatus_t status = MQTTSuccess; + uint8_t buffer[ 100 ] = { 0 }; + uint8_t * pIndex = buffer; + size_t dummy; + + /* Verify parameters */ + memset( &mqttPacketInfo, 0x00, sizeof( mqttPacketInfo ) ); + memset( &ackInfo, 0x00, sizeof( ackInfo ) ); + mqttPacketInfo.type = MQTT_PACKET_TYPE_PUBACK; + status = MQTTV5_DeserializeAck( NULL, &packetIdentifier, &ackInfo, requestProblem, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + status = MQTTV5_DeserializeAck( &mqttPacketInfo, NULL, &ackInfo, requestProblem, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + /* ackInfo not set. */ + status = MQTTV5_DeserializeAck( &mqttPacketInfo, &packetIdentifier, NULL, requestProblem, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /*Remaining data cannot be NULL.*/ + status = MQTTV5_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &ackInfo, requestProblem, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /*Max packet size cannot be 0*/ + mqttPacketInfo.pRemainingData = buffer; + status = MQTTV5_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &ackInfo, requestProblem, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + maxPacketSize = 200U; + /* Packet identifier 0 is not valid (per spec). */ + buffer[ 0 ] = 0; + buffer[ 1 ] = 0; + mqttPacketInfo.remainingLength = MQTT_PACKET_SIMPLE_ACK_REMAINING_LENGTH; + status = MQTTV5_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &ackInfo, requestProblem, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status ); + + + /*Remaining length connot be less than 2*/ + mqttPacketInfo.remainingLength = 1; + status = MQTTV5_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &ackInfo, requestProblem, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTMalformedPacket, status ); + + /*Packet size greater than allowed.*/ + mqttPacketInfo.remainingLength = 1000U; + status = MQTTV5_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &ackInfo, requestProblem, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTProtocolError, status ); + + /* Process a valid PUBACK. */ + mqttPacketInfo.remainingLength = 2; + buffer[ 1 ] = 1; + status = MQTTV5_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &ackInfo, requestProblem, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_INT( 1, packetIdentifier ); + + + mqttPacketInfo.remainingLength = 3; + buffer[ 2 ] = 0x00; + status = MQTTV5_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &ackInfo, requestProblem, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_INT( 1, packetIdentifier ); + + /*Property length should be zero when request problem is set to false*/ + mqttPacketInfo.remainingLength = 24; + status = MQTTV5_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &ackInfo, requestProblem, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTProtocolError, status ); + + requestProblem = true; + /*User properties not initialized.*/ + status = MQTTV5_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &ackInfo, requestProblem, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /*Valid parameters.*/ + ackInfo.pUserProperty = &userProperties; + pIndex = &buffer[ 3 ]; + dummy = encodeRemainingLength( pIndex, 20 ); + pIndex++; + pIndex = serializeutf_8( pIndex, MQTT_REASON_STRING_ID ); + pIndex = serializeutf_8pair( pIndex ); + status = MQTTV5_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &ackInfo, requestProblem, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + + /*Invalid property id*/ + pIndex = &buffer[ 3 ]; + dummy = encodeRemainingLength( pIndex, 7 ); + mqttPacketInfo.remainingLength = dummy + 7 + 3; + pIndex++; + pIndex = serializeutf_8( pIndex, MQTT_CORRELATION_DATA_ID ); + status = MQTTV5_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &ackInfo, requestProblem, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTProtocolError, status ); + + /*Invalid remaining length*/ + pIndex = &buffer[ 3 ]; + dummy = encodeRemainingLength( pIndex, 12 ); + pIndex++; + pIndex = serializeutf_8( pIndex, MQTT_REASON_STRING_ID ); + status = MQTTV5_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &ackInfo, requestProblem, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTMalformedPacket, status ); + + + /*Invalid property length*/ + pIndex = &buffer[ 3 ]; + dummy = encodeRemainingLength( pIndex, 20971556356235 ); + status = MQTTV5_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &ackInfo, requestProblem, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status ); +} + +void test_MQTTV5_DeserializeAck_LogPuback() +{ + MQTTPacketInfo_t mqttPacketInfo; + MQTTAckInfo_t ackInfo; + uint16_t packetIdentifier; + uint32_t maxPacketSize = 10U; + bool requestProblem = false; + MQTTStatus_t status = MQTTSuccess; + uint8_t buffer[ 4 ] = { 0 }; + + memset( &mqttPacketInfo, 0x00, sizeof( mqttPacketInfo ) ); + memset( &ackInfo, 0x00, sizeof( ackInfo ) ); + mqttPacketInfo.pRemainingData = buffer; + mqttPacketInfo.type = MQTT_PACKET_TYPE_PUBACK; + mqttPacketInfo.remainingLength = 4; + /*Validate all the correct reason codes.*/ + buffer[ 1 ] = 1; + buffer[ 2 ] = MQTT_REASON_SUCCESS; + status = MQTTV5_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &ackInfo, requestProblem, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + + buffer[ 2 ] = MQTT_REASON_NO_MATCHING_SUBSCRIBERS; + status = MQTTV5_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &ackInfo, requestProblem, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + + buffer[ 2 ] = MQTT_REASON_UNSPECIFIED_ERR; + status = MQTTV5_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &ackInfo, requestProblem, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTServerRefused, status ); + + buffer[ 2 ] = MQTT_REASON_IMPL_SPECIFIC_ERR; + status = MQTTV5_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &ackInfo, requestProblem, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTServerRefused, status ); + + buffer[ 2 ] = MQTT_REASON_NOT_AUTHORIZED; + status = MQTTV5_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &ackInfo, requestProblem, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTServerRefused, status ); + + buffer[ 2 ] = MQTT_REASON_TOPIC_NAME_INVALID; + status = MQTTV5_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &ackInfo, requestProblem, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTServerRefused, status ); + + buffer[ 2 ] = MQTT_REASON_PACKET_ID_IN_USE; + status = MQTTV5_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &ackInfo, requestProblem, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTServerRefused, status ); + + buffer[ 2 ] = MQTT_REASON_QUOTA_EXCEEDED; + status = MQTTV5_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &ackInfo, requestProblem, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTServerRefused, status ); + + buffer[ 2 ] = MQTT_REASON_PAYLOAD_FORMAT_INVALID; + status = MQTTV5_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &ackInfo, requestProblem, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTServerRefused, status ); + + /*Invlaid reason code.*/ + buffer[ 2 ] = MQTT_REASON_BANNED; + status = MQTTV5_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &ackInfo, requestProblem, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTProtocolError, status ); +} + +void test_MQTTV5_DeserializeAck_Pubrel() +{ + MQTTPacketInfo_t mqttPacketInfo; + MQTTAckInfo_t ackInfo; + uint32_t maxPacketSize = 10U; + uint16_t packetIdentifier; + bool requestProblem = false; + MQTTStatus_t status = MQTTSuccess; + uint8_t buffer[ 4 ] = { 0 }; + + memset( &mqttPacketInfo, 0x00, sizeof( mqttPacketInfo ) ); + memset( &ackInfo, 0x00, sizeof( ackInfo ) ); + mqttPacketInfo.pRemainingData = buffer; + mqttPacketInfo.type = MQTT_PACKET_TYPE_PUBREL; + mqttPacketInfo.remainingLength = 4; + /*Validate all the correct reason codes.*/ + buffer[ 1 ] = 1; + buffer[ 2 ] = MQTT_REASON_SUCCESS; + status = MQTTV5_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &ackInfo, requestProblem, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + + buffer[ 2 ] = MQTT_REASON_PACKET_ID_NOT_FOUND; + status = MQTTV5_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &ackInfo, requestProblem, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTServerRefused, status ); + + /*Invalid reason code.*/ + buffer[ 2 ] = MQTT_REASON_BANNED; + status = MQTTV5_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &ackInfo, requestProblem, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTProtocolError, status ); + + /*Invalid reason code.*/ + buffer[ 2 ] = MQTT_REASON_SEND_WILL; + status = MQTTV5_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &ackInfo, requestProblem, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTProtocolError, status ); + + /*Invalid packet id*/ + buffer[ 1 ] = 0; + status = MQTTV5_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &ackInfo, requestProblem, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status ); + + /*Invalid packet type. */ + mqttPacketInfo.type = MQTT_PACKET_TYPE_CONNACK; + status = MQTTV5_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &ackInfo, requestProblem, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status ); +} + + +void test_MQTTV5_GetAckPacketSize() +{ + MQTTStatus_t status; + MQTTAckInfo_t ackInfo; + MQTTUserProperties_t userProperties; + size_t remainingLength; + size_t packetSize; + uint32_t maxPacketSize = 0U; + + memset( &ackInfo, 0x0, sizeof( ackInfo ) ); + memset( &userProperties, 0x0, sizeof( userProperties ) ); + /*Invalid parameters*/ + status = MQTTV5_GetAckPacketSize( NULL, &remainingLength, &packetSize, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + status = MQTTV5_GetAckPacketSize( &ackInfo, NULL, &packetSize, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + + status = MQTTV5_GetAckPacketSize( &ackInfo, &remainingLength, NULL, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + +/*Max packet size cannot be 0*/ + status = MQTTV5_GetAckPacketSize( &ackInfo, &remainingLength, NULL, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + +/*Valid parameters*/ + maxPacketSize = UINT32_MAX; + status = MQTTV5_GetAckPacketSize( &ackInfo, &remainingLength, &packetSize, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + +/*With properties*/ + ackInfo.pReasonString = MQTT_TEST_UTF8_STRING; + ackInfo.reasonStringLength = MQTT_TEST_UTF8_STRING_LENGTH; + ackInfo.pUserProperty = &userProperties; + userProperties.count = 0; + status = MQTTV5_GetAckPacketSize( &ackInfo, &remainingLength, &packetSize, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + +/*Packet size greater than max allowed.*/ + maxPacketSize = 2; + status = MQTTV5_GetAckPacketSize( &ackInfo, &remainingLength, &packetSize, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + +/*Max packet size cannot be 0*/ + maxPacketSize = 0; + status = MQTTV5_GetAckPacketSize( &ackInfo, &remainingLength, &packetSize, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + +/*Reason string is null but length is not 0*/ + ackInfo.pReasonString = NULL; + maxPacketSize = 30; + status = MQTTV5_GetAckPacketSize( &ackInfo, &remainingLength, &packetSize, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); +} + +void test_MQTTV5_SerializePubAckWithProperty() +{ + MQTTStatus_t status; + MQTTAckInfo_t ackInfo; + size_t remainingLength = 0U; + size_t packetSize; + uint8_t packetType = MQTT_PACKET_TYPE_PUBREL; + uint16_t packetId = 1U; + uint32_t maxPacketSize = 1000U; + uint8_t buffer[ 440 + 2 * BUFFER_PADDING_LENGTH ]; + size_t bufferSize = sizeof( buffer ) - 2 * BUFFER_PADDING_LENGTH; + MQTTFixedBuffer_t fixedBuffer = { .pBuffer = &buffer[ BUFFER_PADDING_LENGTH ], .size = bufferSize }; + + /*Invalid parameters*/ + status = MQTTV5_SerializePubAckWithProperty( NULL, remainingLength, &fixedBuffer, packetType, packetId ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + status = MQTTV5_SerializePubAckWithProperty( &ackInfo, remainingLength, NULL, packetType, packetId ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + fixedBuffer.pBuffer = NULL; + status = MQTTV5_SerializePubAckWithProperty( &ackInfo, remainingLength, &fixedBuffer, packetType, packetId ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /*Buffer size not sufficient*/ + status = MQTTV5_GetAckPacketSize( &ackInfo, &remainingLength, &packetSize, maxPacketSize ); + fixedBuffer.size = 5; + fixedBuffer.pBuffer = &buffer[ BUFFER_PADDING_LENGTH ]; + status = MQTTV5_SerializePubAckWithProperty( &ackInfo, remainingLength, &fixedBuffer, packetType, packetId ); + TEST_ASSERT_EQUAL_INT( MQTTNoMemory, status ); + + /*With correct parameters*/ + memset( &ackInfo, 0x0, sizeof( ackInfo ) ); + status = MQTTV5_GetAckPacketSize( &ackInfo, &remainingLength, &packetSize, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + /* Make sure buffer has enough space */ + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); + /* Make sure test succeeds. */ + fixedBuffer.size = 200; + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + status = MQTTV5_SerializePubAckWithProperty( &ackInfo, remainingLength, &fixedBuffer, packetType, packetId ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + + /*With properties*/ + ackInfo.reasonStringLength = MQTT_TEST_UTF8_STRING_LENGTH; + ackInfo.pReasonString = MQTT_TEST_UTF8_STRING; + ackInfo.pUserProperty = &userProperties; + userProperties.count = 1; + userProperties.userProperty->keyLength = 1; + userProperties.userProperty->valueLength = 1; + userProperties.userProperty->pKey = "a"; + userProperties.userProperty->pValue = "a"; + status = MQTTV5_GetAckPacketSize( &ackInfo, &remainingLength, &packetSize, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + /* Make sure buffer has enough space */ + fixedBuffer.size = 400; + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + status = MQTTV5_SerializePubAckWithProperty( &ackInfo, remainingLength, &fixedBuffer, packetType, packetId ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); +} + +void test_MQTTV5_GetDisconnectPacketSize() +{ + MQTTAckInfo_t ackInfo; + size_t remainingLength; + size_t packetSize; + uint32_t maxPacketSize = 0U; + uint32_t sessionExpiry = 0U; + uint32_t prevSessionExpiry = 0U; + MQTTStatus_t status; + + memset( &ackInfo, 0x0, sizeof( ackInfo ) ); + + /*Invalid arguments*/ + status = MQTTV5_GetDisconnectPacketSize( NULL, &remainingLength, &packetSize, maxPacketSize, sessionExpiry, prevSessionExpiry ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + status = MQTTV5_GetDisconnectPacketSize( &ackInfo, NULL, &packetSize, maxPacketSize, sessionExpiry, prevSessionExpiry ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + status = MQTTV5_GetDisconnectPacketSize( &ackInfo, &remainingLength, NULL, maxPacketSize, sessionExpiry, prevSessionExpiry ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /*Max packet size cannot be 0.*/ + status = MQTTV5_GetDisconnectPacketSize( &ackInfo, &remainingLength, &packetSize, maxPacketSize, sessionExpiry, prevSessionExpiry ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /*Session expiry cannot overwrite zero.*/ + sessionExpiry = 10U; + maxPacketSize = 6U; + status = MQTTV5_GetDisconnectPacketSize( &ackInfo, &remainingLength, &packetSize, maxPacketSize, sessionExpiry, prevSessionExpiry ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + prevSessionExpiry = 5; + /*Invalid reason code*/ + ackInfo.reasonCode = 2; + status = MQTTV5_GetDisconnectPacketSize( &ackInfo, &remainingLength, &packetSize, maxPacketSize, sessionExpiry, prevSessionExpiry ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /*Reason string not initialized.*/ + ackInfo.reasonCode = 0; + ackInfo.reasonStringLength = MQTT_TEST_UTF8_STRING_LENGTH; + status = MQTTV5_GetDisconnectPacketSize( &ackInfo, &remainingLength, &packetSize, maxPacketSize, sessionExpiry, prevSessionExpiry ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + ackInfo.pReasonString = MQTT_TEST_UTF8_STRING; + /*Packet size greater than allowed.*/ + prevSessionExpiry = 10; + status = MQTTV5_GetDisconnectPacketSize( &ackInfo, &remainingLength, &packetSize, maxPacketSize, sessionExpiry, prevSessionExpiry ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /*Invalid Reason code*/ + maxPacketSize = 60U; + ackInfo.reasonCode = MQTT_REASON_SERVER_BUSY; + status = MQTTV5_GetDisconnectPacketSize( &ackInfo, &remainingLength, &packetSize, maxPacketSize, sessionExpiry, prevSessionExpiry ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /*Valid parameters*/ + ackInfo.reasonCode = MQTT_REASON_SEND_WILL; + status = MQTTV5_GetDisconnectPacketSize( &ackInfo, &remainingLength, &packetSize, maxPacketSize, sessionExpiry, prevSessionExpiry ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + + /*Valid parameters*/ + ackInfo.reasonCode = MQTT_REASON_PACKET_TOO_LARGE; + status = MQTTV5_GetDisconnectPacketSize( &ackInfo, &remainingLength, &packetSize, maxPacketSize, sessionExpiry, prevSessionExpiry ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); +} + +void test_MQTTV5_SerializeDisconnectWithProperty() +{ + MQTTAckInfo_t ackInfo; + size_t remainingLength = 0U; + uint32_t maxPacketSize = 200U; + uint32_t sessionExpiry = 0U; + MQTTStatus_t status = MQTTSuccess; + uint8_t buffer[ 200 + 2 * BUFFER_PADDING_LENGTH ]; + size_t bufferSize = sizeof( buffer ) - 2 * BUFFER_PADDING_LENGTH; + size_t packetSize = bufferSize; + MQTTFixedBuffer_t fixedBuffer = { 0 }; + + memset( &ackInfo, 0x0, sizeof( ackInfo ) ); + fixedBuffer.size = bufferSize; + + /*Invalid Parameters*/ + status = MQTTV5_SerializeDisconnectWithProperty( NULL, remainingLength, &fixedBuffer, sessionExpiry ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + status = MQTTV5_SerializeDisconnectWithProperty( &ackInfo, remainingLength, NULL, sessionExpiry ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + status = MQTTV5_SerializeDisconnectWithProperty( &ackInfo, remainingLength, &fixedBuffer, sessionExpiry ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + fixedBuffer.pBuffer = &buffer[ BUFFER_PADDING_LENGTH ]; + fixedBuffer.size = 2; + remainingLength = 20; + /*Buffer size not enough.*/ + status = MQTTV5_SerializeDisconnectWithProperty( &ackInfo, remainingLength, &fixedBuffer, sessionExpiry ); + TEST_ASSERT_EQUAL_INT( MQTTNoMemory, status ); + + fixedBuffer.size = bufferSize; + /* Calculate exact packet size and remaining length. */ + status = MQTTV5_GetDisconnectPacketSize( &ackInfo, &remainingLength, &packetSize, maxPacketSize, sessionExpiry, 10 ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + /* Make sure buffer has enough space */ + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); + + padAndResetBuffer( buffer, sizeof( buffer ) ); + status = MQTTV5_SerializeDisconnectWithProperty( &ackInfo, remainingLength, &fixedBuffer, sessionExpiry ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + checkBufferOverflow( buffer, sizeof( buffer ) ); + + ackInfo.pReasonString = MQTT_TEST_UTF8_STRING; + ackInfo.reasonStringLength = MQTT_TEST_UTF8_STRING_LENGTH; + ackInfo.pUserProperty = &userProperties; + userProperties.count = 1; + userProperties.userProperty[ 0 ].pKey = MQTT_TEST_UTF8_STRING; + userProperties.userProperty[ 0 ].pValue = MQTT_TEST_UTF8_STRING; + userProperties.userProperty[ 0 ].keyLength = MQTT_TEST_UTF8_STRING_LENGTH; + userProperties.userProperty[ 0 ].valueLength = MQTT_TEST_UTF8_STRING_LENGTH; + + sessionExpiry = 10; + fixedBuffer.pBuffer = &buffer[ BUFFER_PADDING_LENGTH ]; + /* Calculate exact packet size and remaining length. */ + status = MQTTV5_GetDisconnectPacketSize( &ackInfo, &remainingLength, &packetSize, maxPacketSize, sessionExpiry, 10 ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + /* Make sure buffer has enough space */ + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); + + padAndResetBuffer( buffer, sizeof( buffer ) ); + status = MQTTV5_SerializeDisconnectWithProperty( &ackInfo, remainingLength, &fixedBuffer, sessionExpiry ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + checkBufferOverflow( buffer, sizeof( buffer ) ); +} + +void test_MQTTV5_DeserializeDisconnect() +{ + MQTTAckInfo_t disconnectInfo; + const char * pServerRef; + uint16_t serverRefLength; + size_t dummy; + int32_t maxPacketSize = 0U; + uint8_t buffer[ 100 ] = { 0 }; + uint8_t * pIndex = buffer; + + memset( &disconnectInfo, 0x0, sizeof( disconnectInfo ) ); + + + /*Invalid parameters*/ + status = MQTTV5_DeserializeDisconnect( NULL, &disconnectInfo, &pServerRef, &serverRefLength, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /*Remaining data not initialized.*/ + status = MQTTV5_DeserializeDisconnect( &packetInfo, &disconnectInfo, &pServerRef, &serverRefLength, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + packetInfo.pRemainingData = buffer; + status = MQTTV5_DeserializeDisconnect( &packetInfo, NULL, &pServerRef, &serverRefLength, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + status = MQTTV5_DeserializeDisconnect( &packetInfo, &disconnectInfo, NULL, &serverRefLength, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + status = MQTTV5_DeserializeDisconnect( &packetInfo, &disconnectInfo, &pServerRef, NULL, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /*Max packet size cannot be 0.*/ + status = MQTTV5_DeserializeDisconnect( &packetInfo, &disconnectInfo, &pServerRef, &serverRefLength, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + maxPacketSize = 100; + + + /*Remaining length cannot be 0*/ + status = MQTTV5_DeserializeDisconnect( &packetInfo, &disconnectInfo, &pServerRef, &serverRefLength, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTMalformedPacket, status ); + + /*Remaining Length invalid. */ + packetInfo.remainingLength = 200; + status = MQTTV5_DeserializeDisconnect( &packetInfo, &disconnectInfo, &pServerRef, &serverRefLength, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTProtocolError, status ); + + /*User property not initialized.*/ + packetInfo.remainingLength = 1; + status = MQTTV5_DeserializeDisconnect( &packetInfo, &disconnectInfo, &pServerRef, &serverRefLength, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /*Invalid reason code.*/ + disconnectInfo.pUserProperty = &userProperties; + buffer[ 0 ] = MQTT_REASON_SEND_WILL; + status = MQTTV5_DeserializeDisconnect( &packetInfo, &disconnectInfo, &pServerRef, &serverRefLength, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTProtocolError, status ); + + packetInfo.remainingLength = 1; + buffer[ 0 ] = MQTT_REASON_NOT_AUTHORIZED; + status = MQTTV5_DeserializeDisconnect( &packetInfo, &disconnectInfo, &pServerRef, &serverRefLength, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + + /*Property length is 0.*/ + packetInfo.remainingLength = 2; + pIndex = &buffer[ 1 ]; + dummy = encodeRemainingLength( pIndex, 0 ); + status = MQTTV5_DeserializeDisconnect( &packetInfo, &disconnectInfo, &pServerRef, &serverRefLength, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + + /*With properties*/ + pIndex = &buffer[ 1 ]; + packetInfo.remainingLength = 29; + dummy = encodeRemainingLength( pIndex, 27 ); + pIndex++; + pIndex = serializeutf_8( pIndex, MQTT_REASON_STRING_ID ); + pIndex = serializeutf_8pair( pIndex ); + pIndex = serializeutf_8( pIndex, MQTT_SERVER_REF_ID ); + status = MQTTV5_DeserializeDisconnect( &packetInfo, &disconnectInfo, &pServerRef, &serverRefLength, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + + /*Invalid property id.*/ + pIndex = &buffer[ 1 ]; + packetInfo.remainingLength = 9; + dummy = encodeRemainingLength( pIndex, 7 ); + pIndex++; + pIndex = serializeutf_8( pIndex, MQTT_SESSION_EXPIRY_ID ); + status = MQTTV5_DeserializeDisconnect( &packetInfo, &disconnectInfo, &pServerRef, &serverRefLength, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTProtocolError, status ); + TEST_ASSERT_EQUAL_INT( 1, dummy ); + + /*Invalid property length.*/ + pIndex = &buffer[ 1 ]; + packetInfo.remainingLength = 9; + dummy = encodeRemainingLength( pIndex, 4 ); + status = MQTTV5_DeserializeDisconnect( &packetInfo, &disconnectInfo, &pServerRef, &serverRefLength, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTMalformedPacket, status ); + + buffer[ 1 ] = 0x81; + buffer[ 2 ] = 0x00; + status = MQTTV5_DeserializeDisconnect( &packetInfo, &disconnectInfo, &pServerRef, &serverRefLength, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status ); +} + +void test_MQTT_GetIncomingPacketTypeAndLength( void ) +{ + MQTTPacketInfo_t mqttPacket; + NetworkContext_t networkContext; + uint8_t buffer[ 10 ]; + uint8_t * bufPtr = buffer; + + /* Dummy network context - pointer to pointer to a buffer. */ + networkContext.buffer = &bufPtr; + /* Check when network receive fails. */ + memset( buffer, 0x00, 10 ); + /* Branch coverage for Disconnect. */ + bufPtr = buffer; + buffer[ 0 ] = MQTT_PACKET_TYPE_DISCONNECT; + status = MQTT_GetIncomingPacketTypeAndLength( mockReceive, &networkContext, &mqttPacket ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); +} + +void test_MQTTV5_InitConnect() +{ + status = MQTTV5_InitConnect( NULL ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + status = MQTTV5_InitConnect( &properties ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); +} diff --git a/test/unit-test/MQTTv5/core_mqttv5_state_utest.c b/test/unit-test/MQTTv5/core_mqttv5_state_utest.c new file mode 100644 index 000000000..d3bdc4278 --- /dev/null +++ b/test/unit-test/MQTTv5/core_mqttv5_state_utest.c @@ -0,0 +1,59 @@ +/* + * coreMQTT + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * SPDX-License-Identifier: MIT + * + * 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. + */ + +/** + * @file core_mqtt_state_utest.c + * @brief Unit tests for functions in core_mqtt_state.h. + */ +#include +#include "unity.h" + +#include "core_mqtt_state.h" +#include "mock_core_mqtt_serializer.h" + +#define MQTT_PACKET_ID_INVALID ( ( uint16_t ) 0U ) +#define MQTT_STATE_ARRAY_MAX_COUNT 10 + +/* ============================ UNITY FIXTURES ============================ */ +void setUp( void ) +{ +} + +/* called before each testcase */ +void tearDown( void ) +{ +} + +/* called at the beginning of the whole suite */ +void suiteSetUp() +{ +} + +/* called at the end of the whole suite */ +int suiteTearDown( int numFailures ) +{ + return numFailures; +} + +/* ========================================================================== */ diff --git a/test/unit-test/MQTTv5/core_mqttv5_utest.c b/test/unit-test/MQTTv5/core_mqttv5_utest.c new file mode 100644 index 000000000..bb8f4afd9 --- /dev/null +++ b/test/unit-test/MQTTv5/core_mqttv5_utest.c @@ -0,0 +1,1104 @@ +/* + * coreMQTT + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * SPDX-License-Identifier: MIT + * + * 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. + */ + +/** + * @file core_mqtt_utest.c + * @brief Unit tests for functions in core_mqtt.h. + */ +#include +#include +#include + +#include "unity.h" + +/* Include paths for public enums, structures, and macros. */ +#include "core_mqtt.h" + +/* #include "mock_core_mqttv5_serializer.h" */ +/* #include "mock_core_mqttv5_state.h" */ + +#include "core_mqtt_config_defaults.h" +#include "mock_core_mqtt_serializer.h" +#include "mock_core_mqtt_state.h" +/* Set network context to double pointer to buffer (uint8_t**). */ +struct NetworkContext +{ + uint8_t ** buffer; +}; + +/** + * @brief MQTT client identifier. + */ +#define MQTT_CLIENT_IDENTIFIER "testclient" + +/** + * @brief A valid starting packet ID per MQTT spec. Start from 1. + */ +#define MQTT_FIRST_VALID_PACKET_ID ( 1 ) + +/** + * @brief A PINGREQ packet is always 2 bytes in size, defined by MQTT 3.1.1 spec. + */ +#define MQTT_PACKET_PINGREQ_SIZE ( 2U ) + +/** + * @brief A packet type not handled by MQTT_ProcessLoop. + */ +#define MQTT_PACKET_TYPE_INVALID ( 0U ) + +/** + * @brief Number of milliseconds in a second. + */ +#define MQTT_ONE_SECOND_TO_MS ( 1000U ) + +/** + * @brief Length of the MQTT network buffer. + */ +#define MQTT_TEST_BUFFER_LENGTH ( 128 ) + +/** + * @brief Sample keep-alive interval that should be greater than 0. + */ +#define MQTT_SAMPLE_KEEPALIVE_INTERVAL_S ( 1U ) + +/** + * @brief Length of time spent for single test case with + * multiple iterations spent in the process loop for coverage. + */ +#define MQTT_SAMPLE_PROCESS_LOOP_TIMEOUT_MS ( 1U ) + +/** + * @brief Zero timeout in the process loop implies one iteration. + */ +#define MQTT_NO_TIMEOUT_MS ( 0U ) + +/** + * @brief Sample length of remaining serialized data. + */ +#define MQTT_SAMPLE_REMAINING_LENGTH ( 64 ) + +/** + * @brief Subtract this value from max value of global entry time + * for the timer overflow test. + */ +#define MQTT_OVERFLOW_OFFSET ( 3 ) + +/** + * @brief The number of times the "getTime()" function is called + * within a single iteration of the #MQTT_ProcessLoop. + * + * This constant is used for the timer overflow test which checks + * that the API can support normal behavior even if the timer + * overflows. + * + * @note Currently, there are 6 calls within a single iteration. + * This can change when the implementation changes which would be + * caught through unit test failure. + */ +#define MQTT_TIMER_CALLS_PER_ITERATION ( 6 ) + +/** + * @brief Timeout for the timer overflow test. + */ +#define MQTT_TIMER_OVERFLOW_TIMEOUT_MS ( 10 ) + +/** + * @brief A sample network context that we set to NULL. + */ +#define MQTT_SAMPLE_NETWORK_CONTEXT ( NULL ) + +/** + * @brief Sample topic filter to subscribe to. + */ +#define MQTT_SAMPLE_TOPIC_FILTER "iot" + +/** + * @brief Length of sample topic filter. + */ +#define MQTT_SAMPLE_TOPIC_FILTER_LENGTH ( sizeof( MQTT_SAMPLE_TOPIC_FILTER ) - 1 ) + +/** + * @brief Sample topic filter to subscribe to. + */ +#define MQTT_SAMPLE_TOPIC_FILTER1 "TopicFilter2" + +/** + * @brief Length of sample topic filter. + */ +#define MQTT_SAMPLE_TOPIC_FILTER_LENGTH1 ( sizeof( MQTT_SAMPLE_TOPIC_FILTER1 ) - 1 ) + +/** + * @brief Sample topic filter to subscribe to. + */ +#define MQTT_SAMPLE_TOPIC_FILTER2 "SomeTopic" + +/** + * @brief Length of sample topic filter. + */ +#define MQTT_SAMPLE_TOPIC_FILTER_LENGTH2 ( sizeof( MQTT_SAMPLE_TOPIC_FILTER2 ) - 1 ) + +/** + * @brief Sample topic filter to subscribe to. + */ +#define MQTT_SAMPLE_TOPIC_FILTER3 "iotTopicFilter" + +/** + * @brief Length of sample topic filter. + */ +#define MQTT_SAMPLE_TOPIC_FILTER_LENGTH3 ( sizeof( MQTT_SAMPLE_TOPIC_FILTER3 ) - 1 ) + + +#define TEST_TOPIC_ALIAS ( 2U ) +#define TEST_MSG_EXPIRY ( 100U ) +#define TEST_CONTENT_TYPE_LENGTH ( 2 ) +#define TEST_CONTENT_TYPE ( "ab" ) +#define TEST_RESPONSE_TOPIC_LENGTH ( 10 ) +#define TEST_RESPONSE_TOPIC ( "aaaaaaaaaa" ) +#define TEST_CORRELATION_DATA_LENGTH ( 5 ) +#define TEST_CORRELATION_DATA ( "abcde" ) + +/** + * @brief Return values of mocked calls in MQTT_ProcessLoop(). Used by + * `expectProcessLoopCalls` + */ +typedef struct ProcessLoopReturns +{ + MQTTStatus_t deserializeStatus; /**< @brief Status after deserializing incoming packet. */ + MQTTPublishState_t stateAfterDeserialize; /**< @brief Publish state after deserializing incoming packet. */ + MQTTStatus_t updateStateStatus; /**< @brief Status after updating publish state. */ + MQTTStatus_t serializeStatus; /**< @brief Status after serializing a publish ack to send. */ + MQTTPublishState_t stateAfterSerialize; /**< @brief Publish state after serializing an ack to send. */ + MQTTStatus_t processLoopStatus; /**< @brief Return value of the process loop. */ + bool incomingPublish; /**< @brief Whether the incoming packet is a publish. */ + MQTTPublishInfo_t * pPubInfo; /**< @brief Publish information to be returned by the deserializer. */ + uint32_t timeoutMs; /**< @brief The timeout value to call MQTT_ProcessLoop API with. */ +} ProcessLoopReturns_t; + + +/** + * @brief Time at the beginning of each test. Note that this is not updated with + * a real clock. Instead, we simply increment this variable. + */ +static uint32_t globalEntryTime = 0; + +/** + * @brief A static buffer used by the MQTT library for storing packet data. + */ +static uint8_t mqttBuffer[ MQTT_TEST_BUFFER_LENGTH ] = { 0 }; + +/** + * @brief A flag to indicate whether event callback is called from + * MQTT_ProcessLoop. + */ +static bool isEventCallbackInvoked = false; +static bool receiveOnce = false; +static MQTTStatus_t modifyIncomingPacketStatus = MQTTSuccess; + + +/* ============================ UNITY FIXTURES ============================ */ + +/* Called before each test method. */ +void setUp() +{ + memset( mqttBuffer, 0x0, sizeof( mqttBuffer ) ); + MQTT_State_strerror_IgnoreAndReturn( "DUMMY_MQTT_STATE" ); + + globalEntryTime = 0; +} + +/* Called after each test method. */ +void tearDown() +{ +} + +/* Called at the beginning of the whole suite. */ +void suiteSetUp() +{ + receiveOnce = 0; +} + +/* Called at the end of the whole suite. */ +int suiteTearDown( int numFailures ) +{ + return numFailures; +} + + +/* ========================================================================== */ + +/** + * @brief A mocked timer query function that increments on every call. This + * guarantees that only a single iteration runs in the ProcessLoop for ease + * of testing. + */ +static uint32_t getTime( void ) +{ + return globalEntryTime++; +} + +/** + * @brief Mocked failed transport read. + */ +static int32_t transportRecvFailure( NetworkContext_t * pNetworkContext, + void * pBuffer, + size_t bytesToRead ) +{ + ( void ) pNetworkContext; + ( void ) pBuffer; + ( void ) bytesToRead; + return -1; +} + +/** + * @brief Mocked MQTT event callback. + * + * @param[in] pContext MQTT context pointer. + * @param[in] pPacketInfo Packet Info pointer for the incoming packet. + * @param[in] pDeserializedInfo Deserialized information from the incoming packet. + */ +static void eventCallback( MQTTContext_t * pContext, + MQTTPacketInfo_t * pPacketInfo, + MQTTDeserializedInfo_t * pDeserializedInfo ) +{ + ( void ) pContext; + ( void ) pPacketInfo; + ( void ) pDeserializedInfo; + + /* Update the global state to indicate that event callback is invoked. */ + isEventCallbackInvoked = true; + pDeserializedInfo->pNextAckInfo = NULL; +} + +/** + * @brief Mocked MQTT event callback. + * + * @param[in] pContext MQTT context pointer. + * @param[in] pPacketInfo Packet Info pointer for the incoming packet. + * @param[in] pDeserializedInfo Deserialized information from the incoming packet. + */ +static void eventCallback1( MQTTContext_t * pContext, + MQTTPacketInfo_t * pPacketInfo, + MQTTDeserializedInfo_t * pDeserializedInfo ) +{ + ( void ) pContext; + ( void ) pPacketInfo; + ( void ) pDeserializedInfo; + MQTTAckInfo_t ackInfo; + MQTTUserProperties_t user; + memset( &user, 0x0, sizeof( user ) ); + memset( &ackInfo, 0x0, sizeof( ackInfo ) ); + /* Update the global state to indicate that event callback is invoked. */ + isEventCallbackInvoked = true; + pDeserializedInfo->pNextAckInfo = &ackInfo; + + if( pDeserializedInfo->packetIdentifier == 1 ) + { + ackInfo.reasonStringLength = 2; + ackInfo.pReasonString = "ab"; + ackInfo.pUserProperty = &user; + user.count = 0; + } +} + + +/** + * @brief Mocked transport send that always returns 0 bytes sent. + */ +static int32_t transportSendNoBytes( NetworkContext_t * pNetworkContext, + const void * pBuffer, + size_t bytesToWrite ) +{ + ( void ) pNetworkContext; + ( void ) pBuffer; + ( void ) bytesToWrite; + return 0; +} + +static uint8_t * MQTTV5_SerializeAckFixed_cb( uint8_t * pIndex, + uint8_t packetType, + uint16_t packetId, + size_t remainingLength, + size_t propertyLength, + int numcallbacks ) +{ + ( void ) packetType; + ( void ) packetId; + ( void ) remainingLength; + ( void ) propertyLength; + ( void ) numcallbacks; + + return pIndex; +} + +static uint8_t * MQTTV5_SerializeDisconnectFixed_cb( uint8_t * pIndex, + const MQTTAckInfo_t * pAckInfo, + size_t remainingLength, + uint32_t sessionExpiry, + int numcallbacks ) +{ + ( void ) pIndex; + ( void ) pAckInfo; + ( void ) remainingLength; + ( void ) sessionExpiry; + ( void ) numcallbacks; + + return pIndex; +} + + + +static uint8_t * MQTT_SerializeConnectFixedHeader_cb( uint8_t * pIndex, + const MQTTConnectInfo_t * pConnectInfo, + const MQTTPublishInfo_t * pWillInfo, + size_t remainingLength, + int numcallbacks ) +{ + ( void ) pConnectInfo; + ( void ) pWillInfo; + ( void ) remainingLength; + ( void ) numcallbacks; + + return pIndex; +} + +static uint8_t * MQTTV5_SerializeConnectProperties_cb( uint8_t * pIndex, + const MQTTConnectProperties_t * pConnectProperties + , + int numcallbacks ) +{ + ( void ) pConnectProperties; + ( void ) numcallbacks; + + return pIndex; +} + +static uint8_t * MQTT_SerializePublishProperties_cb( const MQTTPublishInfo_t * pPublishInfo, + uint8_t * pIndex + , + int numcallbacks ) +{ + ( void ) pPublishInfo; + ( void ) numcallbacks; + + return pIndex; +} + + +/** + * @brief Mocked successful transport writev. + * + * @param[in] tcpSocket TCP socket. + * @param[in] pMessage vectors to send + * @param[in] bytesToWrite number of vectors + * + * @return Number of bytes sent; negative value on error; + * 0 for timeout or 0 bytes sent. + */ +static int32_t transportWritevSuccess( NetworkContext_t * pNetworkContext, + TransportOutVector_t * pIoVectorIterator, + size_t vectorsToBeSent ) +{ + TEST_ASSERT_EQUAL( MQTT_SAMPLE_NETWORK_CONTEXT, pNetworkContext ); + int32_t bytesToWrite = 0; + size_t i; + + for( i = 0; i < vectorsToBeSent; ++i ) + { + bytesToWrite += pIoVectorIterator->iov_len; + pIoVectorIterator++; + } + + return bytesToWrite; +} + + +/** + * @brief Mocked successful transport send. + * + * @param[in] tcpSocket TCP socket. + * @param[in] pMessage Data to send. + * @param[in] bytesToWrite Length of data to send. + * + * @return Number of bytes sent; negative value on error; + * 0 for timeout or 0 bytes sent. + */ +static int32_t transportSendSuccess( NetworkContext_t * pNetworkContext, + const void * pBuffer, + size_t bytesToWrite ) +{ + TEST_ASSERT_EQUAL( MQTT_SAMPLE_NETWORK_CONTEXT, pNetworkContext ); + ( void ) pBuffer; + return bytesToWrite; +} + +/** + * @brief Mocked failed transport send. + */ +static int32_t transportSendFailure( NetworkContext_t * pNetworkContext, + const void * pBuffer, + size_t bytesToWrite ) +{ + ( void ) pNetworkContext; + ( void ) pBuffer; + ( void ) bytesToWrite; + return -1; +} + +/** + * @brief Mocked successful transport read. + * + * @param[in] tcpSocket TCP socket. + * @param[out] pBuffer Buffer for receiving data. + * @param[in] bytesToRead Size of pBuffer. + * + * @return Number of bytes received; negative value on error. + */ +static int32_t transportRecvSuccess( NetworkContext_t * pNetworkContext, + void * pBuffer, + size_t bytesToRead ) +{ + TEST_ASSERT_EQUAL( MQTT_SAMPLE_NETWORK_CONTEXT, pNetworkContext ); + ( void ) pBuffer; + return bytesToRead; +} + + +/** + * @brief Initialize the transport interface with the mocked functions for + * send and receive. + * + * @brief param[in] pTransport The transport interface to use with the context. + */ +static void setupTransportInterface( TransportInterface_t * pTransport ) +{ + pTransport->pNetworkContext = MQTT_SAMPLE_NETWORK_CONTEXT; + pTransport->send = transportSendSuccess; + pTransport->recv = transportRecvSuccess; + pTransport->writev = transportWritevSuccess; +} + +/** + * @brief Initialize pNetworkBuffer using static buffer. + * + * @param[in] pNetworkBuffer Network buffer provided for the context. + */ +static void setupNetworkBuffer( MQTTFixedBuffer_t * const pNetworkBuffer ) +{ + pNetworkBuffer->pBuffer = mqttBuffer; + pNetworkBuffer->size = MQTT_TEST_BUFFER_LENGTH; +} + +static void setupPublishProperties( MQTTPublishInfo_t * pPublishInfo ) +{ + pPublishInfo->payloadFormat = 1; + pPublishInfo->topicAlias = TEST_TOPIC_ALIAS; + pPublishInfo->msgExpiryInterval = TEST_MSG_EXPIRY; + pPublishInfo->msgExpiryPresent = 1; + pPublishInfo->contentTypeLength = TEST_CONTENT_TYPE_LENGTH; + pPublishInfo->pContentType = TEST_CONTENT_TYPE; + pPublishInfo->responseTopicLength = TEST_RESPONSE_TOPIC_LENGTH; + pPublishInfo->pResponseTopic = TEST_RESPONSE_TOPIC; + pPublishInfo->correlationLength = TEST_CORRELATION_DATA_LENGTH; + pPublishInfo->pCorrelationData = TEST_CORRELATION_DATA; +} + + +void test_MQTT_Connect() +{ + MQTTContext_t mqttContext = { 0 }; + MQTTConnectInfo_t connectInfo = { 0 }; + bool sessionPresent = false; + MQTTStatus_t status; + TransportInterface_t transport = { 0 }; + MQTTFixedBuffer_t networkBuffer = { 0 }; + + setupTransportInterface( &transport ); + setupNetworkBuffer( &networkBuffer ); + + memset( &mqttContext, 0x0, sizeof( mqttContext ) ); + MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer ); + +/* No connect Properties */ + MQTTV5_GetConnectPacketSize_ExpectAnyArgsAndReturn( MQTTBadParameter ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, 0U, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); +} + +void test_MQTT_Connect_error_path() +{ + MQTTContext_t mqttContext = { 0 }; + MQTTConnectInfo_t connectInfo = { 0 }; + MQTTPublishInfo_t willInfo = { 0 }; + uint32_t timeout = 2; + bool sessionPresent; + MQTTStatus_t status; + TransportInterface_t transport = { 0 }; + MQTTFixedBuffer_t networkBuffer = { 0 }; + MQTTConnectProperties_t properties; + MQTTAuthInfo_t authInfo; + MQTTAuthInfo_t authInfo2; + MQTTUserProperties_t userProperties; + + memset( &properties, 0x0, sizeof( properties ) ); + memset( &authInfo, 0x0, sizeof( authInfo ) ); + memset( &authInfo2, 0x0, sizeof( authInfo2 ) ); + memset( &userProperties, 0x0, sizeof( userProperties ) ); + userProperties.userProperty[ 0 ].pKey = "ab"; + userProperties.userProperty[ 0 ].pValue = "ab"; + userProperties.userProperty[ 0 ].keyLength = 2; + userProperties.userProperty[ 0 ].valueLength = 2; + userProperties.count = 1; + authInfo.pAuthMethod = "2"; + authInfo.authMethodLength = 1; + authInfo.pAuthData = "ab"; + authInfo.authDataLength = 2; + setupTransportInterface( &transport ); + setupNetworkBuffer( &networkBuffer ); + memset( &mqttContext, 0x0, sizeof( mqttContext ) ); + memset( &willInfo, 0x0, sizeof( MQTTPublishInfo_t ) ); + memset( &connectInfo, 0x0, sizeof( MQTTConnectInfo_t ) ); + MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer ); + + /* With non-NULL Will. */ + mqttContext.connectStatus = MQTTNotConnected; + mqttContext.keepAliveIntervalSec = 0; + mqttContext.pConnectProperties = &properties; + properties.sessionExpiry = 13; + properties.receiveMax = 12; + properties.maxPacketSize = 1000; + properties.topicAliasMax = 13; + properties.requestProblemInfo = 0; + properties.requestResponseInfo = 1; + properties.pOutgoingUserProperty = &userProperties; + properties.pOutgoingAuth = &authInfo; + properties.pIncomingAuth = &authInfo2; + + willInfo.pTopicName = "test"; + willInfo.topicNameLength = 4; + willInfo.pPayload = "Payload"; + willInfo.payloadLength = 7; + willInfo.payloadFormat = 1; + willInfo.msgExpiryPresent = 1; + willInfo.msgExpiryInterval = 10; + willInfo.msgExpiryPresent = 1; + willInfo.msgExpiryInterval = 10; + willInfo.contentTypeLength = 2; + willInfo.pContentType = "ab"; + willInfo.responseTopicLength = 2; + willInfo.pResponseTopic = "ab"; + willInfo.correlationLength = 2; + willInfo.pCorrelationData = "ab"; + willInfo.willDelay = 3; + willInfo.pUserProperty = &userProperties; + connectInfo.pUserName = "abcd"; + connectInfo.userNameLength = 3; + connectInfo.passwordLength = 4; + connectInfo.pPassword = "1234"; + mqttContext.transportInterface.send = transportSendSuccess; + MQTTV5_GetConnectPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_SerializeConnectFixedHeader_Stub( MQTT_SerializeConnectFixedHeader_cb ); + MQTTV5_SerializeConnectProperties_Stub( MQTTV5_SerializeConnectProperties_cb ); + MQTT_SerializePublishProperties_Stub( MQTT_SerializePublishProperties_cb ); + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTRecvFailed ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTRecvFailed, status ); + + properties.pOutgoingUserProperty = NULL; + MQTTV5_GetConnectPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_SerializeConnectFixedHeader_Stub( MQTT_SerializeConnectFixedHeader_cb ); + MQTTV5_SerializeConnectProperties_Stub( MQTTV5_SerializeConnectProperties_cb ); + MQTT_SerializePublishProperties_Stub( MQTT_SerializePublishProperties_cb ); + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTRecvFailed ); + status = MQTT_Connect( &mqttContext, &connectInfo, &willInfo, timeout, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTRecvFailed, status ); + properties.pOutgoingAuth->authDataLength = 0; + MQTTV5_GetConnectPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_SerializeConnectFixedHeader_Stub( MQTT_SerializeConnectFixedHeader_cb ); + MQTTV5_SerializeConnectProperties_Stub( MQTTV5_SerializeConnectProperties_cb ); + MQTT_SerializePublishProperties_Stub( MQTT_SerializePublishProperties_cb ); + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTRecvFailed ); + status = MQTT_Connect( &mqttContext, &connectInfo, &willInfo, timeout, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTRecvFailed, status ); + + properties.pOutgoingAuth = NULL; + willInfo.contentTypeLength = 0; + willInfo.responseTopicLength = 0; + willInfo.correlationLength = 0; + willInfo.pUserProperty = NULL; + MQTTV5_GetConnectPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_SerializeConnectFixedHeader_Stub( MQTT_SerializeConnectFixedHeader_cb ); + MQTTV5_SerializeConnectProperties_Stub( MQTTV5_SerializeConnectProperties_cb ); + MQTT_SerializePublishProperties_Stub( MQTT_SerializePublishProperties_cb ); + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTRecvFailed ); + status = MQTT_Connect( &mqttContext, &connectInfo, &willInfo, timeout, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTRecvFailed, status ); + + willInfo.pTopicName = NULL; + mqttContext.transportInterface.send = transportSendSuccess; + MQTTV5_GetConnectPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_SerializeConnectFixedHeader_Stub( MQTT_SerializeConnectFixedHeader_cb ); + MQTTV5_SerializeConnectProperties_Stub( MQTTV5_SerializeConnectProperties_cb ); + MQTT_SerializePublishProperties_Stub( MQTT_SerializePublishProperties_cb ); + status = MQTT_Connect( &mqttContext, &connectInfo, &willInfo, timeout, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); +} + +void test_MQTT_Connect_receiveConnack( void ) +{ + MQTTContext_t mqttContext = { 0 }; + MQTTConnectInfo_t connectInfo = { 0 }; + uint32_t timeout = 0; + bool sessionPresent; + MQTTStatus_t status; + TransportInterface_t transport = { 0 }; + MQTTFixedBuffer_t networkBuffer = { 0 }; + MQTTConnectProperties_t properties = { 0 }; + MQTTPacketInfo_t incomingPacket = { 0 }; + + setupTransportInterface( &transport ); + setupNetworkBuffer( &networkBuffer ); + transport.recv = transportRecvFailure; + + memset( &mqttContext, 0x0, sizeof( mqttContext ) ); + memset( &properties, 0x0, sizeof( properties ) ); + MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer ); + mqttContext.pConnectProperties = &properties; + /* Everything before receiving the CONNACK should succeed. */ + MQTTV5_SerializeConnect_IgnoreAndReturn( MQTTSuccess ); + MQTTV5_GetConnectPacketSize_IgnoreAndReturn( MQTTSuccess ); + + /* Nothing received from transport interface. Set timeout to 2 for branch coverage. */ + timeout = 2; + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTNoDataAvailable ); + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTNoDataAvailable ); + MQTT_SerializeConnectFixedHeader_Stub( MQTT_SerializeConnectFixedHeader_cb ); + MQTTV5_SerializeConnectProperties_Stub( MQTTV5_SerializeConnectProperties_cb ); + MQTT_SerializePublishProperties_Stub( MQTT_SerializePublishProperties_cb ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTNoDataAvailable, status ); + + /* Did not receive a CONNACK. */ + incomingPacket.type = MQTT_PACKET_TYPE_PINGRESP; + incomingPacket.remainingLength = 0; + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status ); + + /* Transport receive failure when receiving rest of packet. */ + incomingPacket.type = MQTT_PACKET_TYPE_CONNACK; + incomingPacket.remainingLength = 2; + timeout = 2; + mqttContext.transportInterface.recv = transportRecvFailure; + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTRecvFailed, status ); + + mqttContext.transportInterface.recv = transportRecvSuccess; + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + MQTTV5_DeserializeConnack_ExpectAnyArgsAndReturn( MQTTBadResponse ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status ); +} + +void test_MQTT_Publish( void ) +{ + MQTTContext_t mqttContext = { 0 }; + MQTTPublishInfo_t publishInfo = { 0 }; + MQTTConnectProperties_t properties = { 0 }; + TransportInterface_t transport = { 0 }; + MQTTFixedBuffer_t networkBuffer = { 0 }; + MQTTStatus_t status; + + const uint16_t PACKET_ID = 1; + + setupTransportInterface( &transport ); + setupNetworkBuffer( &networkBuffer ); + transport.send = transportSendFailure; + + memset( &mqttContext, 0x0, sizeof( mqttContext ) ); + memset( &publishInfo, 0x0, sizeof( publishInfo ) ); + MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer ); + + /*Connect properties not defined*/ + status = MQTT_Publish( &mqttContext, &publishInfo, PACKET_ID ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + mqttContext.pConnectProperties = &properties; + + properties.maxPacketSize = 10000; + MQTTV5_ValidatePublishParams_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTTV5_GetPublishPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_SerializePublishHeaderWithoutTopic_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_SerializePublishProperties_Stub( MQTT_SerializePublishProperties_cb ); + status = MQTT_Publish( &mqttContext, &publishInfo, PACKET_ID ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + + MQTTV5_ValidatePublishParams_ExpectAnyArgsAndReturn( MQTTBadParameter ); + status = MQTT_Publish( &mqttContext, &publishInfo, PACKET_ID ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /*Send With properties*/ + setupPublishProperties( &publishInfo ); + MQTTV5_ValidatePublishParams_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTTV5_GetPublishPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_SerializePublishHeaderWithoutTopic_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_SerializePublishProperties_Stub( MQTT_SerializePublishProperties_cb ); + status = MQTT_Publish( &mqttContext, &publishInfo, PACKET_ID ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); +} + +void test_MQTT_ProcessLoop_handleIncomingAck_Happy_Paths( void ) +{ + MQTTStatus_t status; + MQTTPublishState_t stateAfterDeserialize; + MQTTContext_t context = { 0 }; + TransportInterface_t transport = { 0 }; + MQTTFixedBuffer_t networkBuffer = { 0 }; + MQTTConnectProperties_t properties; + MQTTPacketInfo_t incomingPacket = { 0 }; + + setupTransportInterface( &transport ); + setupNetworkBuffer( &networkBuffer ); + /* Modify incoming packet depending on type to be tested. */ + incomingPacket.type = MQTT_PACKET_TYPE_PUBREC; + incomingPacket.remainingLength = MQTT_SAMPLE_REMAINING_LENGTH; + incomingPacket.headerLength = MQTT_SAMPLE_REMAINING_LENGTH; + status = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + properties.requestProblemInfo = 1; + context.pConnectProperties = &properties; + modifyIncomingPacketStatus = MQTTSuccess; + stateAfterDeserialize = MQTTPubRelSend; + MQTT_ProcessIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_ProcessIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + MQTTV5_DeserializeAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_SerializeAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_UpdateStateAck_ReturnThruPtr_pNewState( &stateAfterDeserialize ); + MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + status = MQTT_ProcessLoop( &context ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); +} + +void test_MQTT_ProcessLoop_handleIncomingAck_Error_Paths1( void ) +{ + MQTTStatus_t status; + MQTTPublishState_t stateAfterDeserialize; + MQTTPublishState_t stateAfterSerialize; + MQTTContext_t context = { 0 }; + TransportInterface_t transport = { 0 }; + MQTTFixedBuffer_t networkBuffer = { 0 }; + MQTTConnectProperties_t properties; + MQTTPacketInfo_t incomingPacket = { 0 }; + + /*Update state returns bad response.*/ + setupTransportInterface( &transport ); + setupNetworkBuffer( &networkBuffer ); + incomingPacket.type = MQTT_PACKET_TYPE_PUBREC; + incomingPacket.remainingLength = MQTT_SAMPLE_REMAINING_LENGTH; + incomingPacket.headerLength = MQTT_SAMPLE_REMAINING_LENGTH; + status = MQTT_Init( &context, &transport, getTime, eventCallback1, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + properties.requestProblemInfo = 1; + context.pConnectProperties = &properties; + modifyIncomingPacketStatus = MQTTSuccess; + stateAfterDeserialize = MQTTPubRelSend; + stateAfterSerialize = MQTTPubCompPending; + MQTT_ProcessIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_ProcessIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + MQTTV5_DeserializeAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_UpdateStateAck_ReturnThruPtr_pNewState( &stateAfterDeserialize ); + MQTTV5_GetAckPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTTV5_SerializeAckFixed_Stub( MQTTV5_SerializeAckFixed_cb ); + MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( MQTTBadResponse ); + MQTT_UpdateStateAck_ReturnThruPtr_pNewState( &stateAfterSerialize ); + status = MQTT_ProcessLoop( &context ); + TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status ); +} + +void test_MQTT_ProcessLoop_handleIncomingAck_Error_Paths2( void ) +{ + MQTTStatus_t status; + MQTTContext_t context = { 0 }; + TransportInterface_t transport = { 0 }; + MQTTFixedBuffer_t networkBuffer = { 0 }; + MQTTConnectProperties_t properties; + MQTTPacketInfo_t incomingPacket = { 0 }; + + /*Update state returns bad response.*/ + setupTransportInterface( &transport ); + setupNetworkBuffer( &networkBuffer ); + status = MQTT_Init( &context, &transport, getTime, eventCallback1, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + incomingPacket.type = MQTT_PACKET_TYPE_PUBREC; + incomingPacket.remainingLength = MQTT_SAMPLE_REMAINING_LENGTH; + incomingPacket.headerLength = MQTT_SAMPLE_REMAINING_LENGTH; + properties.requestProblemInfo = 1; + context.pConnectProperties = &properties; + modifyIncomingPacketStatus = MQTTSuccess; + MQTT_ProcessIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_ProcessIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + MQTTV5_DeserializeAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( MQTTBadParameter ); + status = MQTT_ProcessLoop( &context ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); +} + +void test_MQTT_ProcessLoop_handleIncomingAck_Error_Paths3( void ) +{ + MQTTStatus_t status; + MQTTPublishState_t stateAfterDeserialize; + MQTTContext_t context = { 0 }; + TransportInterface_t transport = { 0 }; + MQTTFixedBuffer_t networkBuffer = { 0 }; + MQTTConnectProperties_t properties; + uint16_t packetId = 2; + MQTTPacketInfo_t incomingPacket = { 0 }; + + /*Invalid packet parameters.*/ + setupTransportInterface( &transport ); + setupNetworkBuffer( &networkBuffer ); + incomingPacket.type = MQTT_PACKET_TYPE_PUBREC; + incomingPacket.remainingLength = MQTT_SAMPLE_REMAINING_LENGTH; + incomingPacket.headerLength = MQTT_SAMPLE_REMAINING_LENGTH; + status = MQTT_Init( &context, &transport, getTime, eventCallback1, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + properties.requestProblemInfo = 1; + context.pConnectProperties = &properties; + modifyIncomingPacketStatus = MQTTSuccess; + stateAfterDeserialize = MQTTPubRelSend; + MQTT_ProcessIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_ProcessIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + MQTTV5_DeserializeAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTTV5_DeserializeAck_ReturnThruPtr_pPacketId( &packetId ); + MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_UpdateStateAck_ReturnThruPtr_pNewState( &stateAfterDeserialize ); + MQTTV5_GetAckPacketSize_ExpectAnyArgsAndReturn( MQTTBadParameter ); + status = MQTT_ProcessLoop( &context ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); +} + +void test_MQTT_ProcessLoop_handleIncomingAck_Happy_Paths2( void ) +{ + MQTTStatus_t status; + MQTTPublishState_t stateAfterDeserialize; + MQTTPublishState_t stateAfterSerialize; + MQTTContext_t context = { 0 }; + TransportInterface_t transport = { 0 }; + MQTTFixedBuffer_t networkBuffer = { 0 }; + MQTTConnectProperties_t properties; + uint16_t packetId = 1; + MQTTPacketInfo_t incomingPacket = { 0 }; + + /*Using event call back to set reason string and user properties,*/ + setupTransportInterface( &transport ); + setupNetworkBuffer( &networkBuffer ); + incomingPacket.type = MQTT_PACKET_TYPE_PUBREC; + incomingPacket.remainingLength = MQTT_SAMPLE_REMAINING_LENGTH; + incomingPacket.headerLength = MQTT_SAMPLE_REMAINING_LENGTH; + status = MQTT_Init( &context, &transport, getTime, eventCallback1, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + properties.requestProblemInfo = 1; + context.pConnectProperties = &properties; + modifyIncomingPacketStatus = MQTTSuccess; + stateAfterDeserialize = MQTTPubRelSend; + stateAfterSerialize = MQTTPubCompPending; + MQTT_ProcessIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_ProcessIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + MQTTV5_DeserializeAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTTV5_DeserializeAck_ReturnThruPtr_pPacketId( &packetId ); + MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_UpdateStateAck_ReturnThruPtr_pNewState( &stateAfterDeserialize ); + MQTTV5_GetAckPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTTV5_SerializeAckFixed_Stub( MQTTV5_SerializeAckFixed_cb ); + MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_UpdateStateAck_ReturnThruPtr_pNewState( &stateAfterSerialize ); + status = MQTT_ProcessLoop( &context ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); +} + +void test_MQTT_ProcessLoop_handleIncomingAck_Error_Paths4( void ) +{ + MQTTStatus_t status; + MQTTPublishState_t stateAfterDeserialize; + MQTTContext_t context = { 0 }; + TransportInterface_t transport = { 0 }; + MQTTFixedBuffer_t networkBuffer = { 0 }; + MQTTConnectProperties_t properties; + MQTTPacketInfo_t incomingPacket = { 0 }; + + /*Invalid packet parameters.*/ + setupTransportInterface( &transport ); + setupNetworkBuffer( &networkBuffer ); + incomingPacket.type = MQTT_PACKET_TYPE_PUBREC; + incomingPacket.remainingLength = MQTT_SAMPLE_REMAINING_LENGTH; + incomingPacket.headerLength = MQTT_SAMPLE_REMAINING_LENGTH; + status = MQTT_Init( &context, &transport, getTime, eventCallback1, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + properties.requestProblemInfo = 1; + context.pConnectProperties = &properties; + modifyIncomingPacketStatus = MQTTSuccess; + stateAfterDeserialize = MQTTPubRelPending; + MQTT_ProcessIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_ProcessIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + MQTTV5_DeserializeAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_UpdateStateAck_ReturnThruPtr_pNewState( &stateAfterDeserialize ); + MQTTV5_GetAckPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); + status = MQTT_ProcessLoop( &context ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); +} + +void test_MQTT_ProcessLoop_handleIncomingAck_Error_Paths5( void ) +{ + MQTTStatus_t status; + MQTTPublishState_t stateAfterDeserialize; + MQTTContext_t context = { 0 }; + TransportInterface_t transport = { 0 }; + MQTTFixedBuffer_t networkBuffer = { 0 }; + MQTTConnectProperties_t properties; + MQTTPacketInfo_t incomingPacket = { 0 }; + uint16_t packetId = 1; + + /*Unable to send the packet using transport interface.*/ + setupTransportInterface( &transport ); + setupNetworkBuffer( &networkBuffer ); + incomingPacket.type = MQTT_PACKET_TYPE_PUBREC; + incomingPacket.remainingLength = MQTT_SAMPLE_REMAINING_LENGTH; + incomingPacket.headerLength = MQTT_SAMPLE_REMAINING_LENGTH; + status = MQTT_Init( &context, &transport, getTime, eventCallback1, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + context.transportInterface.send = transportSendNoBytes; + context.transportInterface.writev = NULL; + properties.requestProblemInfo = 1; + context.pConnectProperties = &properties; + modifyIncomingPacketStatus = MQTTSuccess; + stateAfterDeserialize = MQTTPubRelSend; + MQTT_ProcessIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_ProcessIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + MQTTV5_DeserializeAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTTV5_DeserializeAck_ReturnThruPtr_pPacketId( &packetId ); + MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_UpdateStateAck_ReturnThruPtr_pNewState( &stateAfterDeserialize ); + MQTTV5_GetAckPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTTV5_SerializeAckFixed_Stub( MQTTV5_SerializeAckFixed_cb ); + status = MQTT_ProcessLoop( &context ); + TEST_ASSERT_EQUAL_INT( MQTTSendFailed, status ); +} + +void test_MQTTV5_Disconnect() +{ + uint32_t sessionExpiry = 0U; + MQTTStatus_t status; + MQTTConnectProperties_t properties = { 0 }; + MQTTAckInfo_t ackInfo = { 0 }; + MQTTContext_t context = { 0 }; + TransportInterface_t transport = { 0 }; + MQTTFixedBuffer_t networkBuffer = { 0 }; + MQTTUserProperties_t userProperties = { 0 }; + + setupTransportInterface( &transport ); + setupNetworkBuffer( &networkBuffer ); + status = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + + /*Invalid parameters*/ + status = MQTTV5_Disconnect( NULL, &ackInfo, sessionExpiry ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + status = MQTTV5_Disconnect( &context, NULL, sessionExpiry ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /*Connect properties not initialized*/ + status = MQTTV5_Disconnect( &context, &ackInfo, sessionExpiry ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /*Bad Parameters*/ + context.pConnectProperties = &properties; + MQTTV5_GetDisconnectPacketSize_ExpectAnyArgsAndReturn( MQTTBadParameter ); + status = MQTTV5_Disconnect( &context, &ackInfo, sessionExpiry ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /*Valid parameters*/ + properties.maxPacketSize = 100U; + properties.sessionExpiry = 10U; + MQTTV5_GetDisconnectPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTTV5_SerializeDisconnectFixed_Stub( MQTTV5_SerializeDisconnectFixed_cb ); + status = MQTTV5_Disconnect( &context, &ackInfo, sessionExpiry ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + + /*With reason string and user property*/ + ackInfo.pUserProperty = &userProperties; + ackInfo.pReasonString = "test"; + ackInfo.reasonStringLength = 4; + MQTTV5_GetDisconnectPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTTV5_SerializeDisconnectFixed_Stub( MQTTV5_SerializeDisconnectFixed_cb ); + status = MQTTV5_Disconnect( &context, &ackInfo, sessionExpiry ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + + /*Send failed*/ + context.transportInterface.send = transportSendFailure; + context.transportInterface.writev = NULL; + MQTTV5_GetDisconnectPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTTV5_SerializeDisconnectFixed_Stub( MQTTV5_SerializeDisconnectFixed_cb ); + status = MQTTV5_Disconnect( &context, &ackInfo, sessionExpiry ); + TEST_ASSERT_EQUAL_INT( MQTTSendFailed, status ); +} + +void test_MQTT_ProcessLoop_handleIncomingDisconnect( void ) +{ + MQTTStatus_t status; + MQTTContext_t context = { 0 }; + TransportInterface_t transport = { 0 }; + MQTTFixedBuffer_t networkBuffer = { 0 }; + MQTTConnectProperties_t properties; + MQTTAckInfo_t disconnectInfo; + MQTTPacketInfo_t incomingPacket = { 0 }; + + memset( &disconnectInfo, 0x0, sizeof( disconnectInfo ) ); + setupTransportInterface( &transport ); + setupNetworkBuffer( &networkBuffer ); + status = MQTT_Init( &context, &transport, getTime, eventCallback1, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + context.pDisconnectInfo = &disconnectInfo; + context.pConnectProperties = &properties; + incomingPacket.type = MQTT_PACKET_TYPE_DISCONNECT; + incomingPacket.remainingLength = MQTT_SAMPLE_REMAINING_LENGTH; + incomingPacket.headerLength = MQTT_SAMPLE_REMAINING_LENGTH; + MQTT_ProcessIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_ProcessIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + MQTTV5_DeserializeDisconnect_IgnoreAndReturn( MQTTSuccess ); + status = MQTT_ProcessLoop( &context ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + + /*Invalid packet parameters.*/ + MQTT_ProcessIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_ProcessIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + MQTTV5_DeserializeDisconnect_IgnoreAndReturn( MQTTProtocolError ); + status = MQTT_ProcessLoop( &context ); + TEST_ASSERT_EQUAL_INT( MQTTProtocolError, status ); +} diff --git a/test/unit-test/WithoutUP/CMakeLists.txt b/test/unit-test/WithoutUP/CMakeLists.txt new file mode 100644 index 000000000..db4a66df3 --- /dev/null +++ b/test/unit-test/WithoutUP/CMakeLists.txt @@ -0,0 +1,95 @@ +# Include filepaths for source and include. +include( ${MODULE_ROOT_DIR}/mqttFilePaths.cmake ) + +# ==================== Define your project name (edit) ======================== +set(project_name "core_mqttv5WUP") + + +# ===================== Create your mock here (edit) ======================== + +# list the files to mock here +list(APPEND mock_list + "${MODULE_ROOT_DIR}/source/include/core_mqtt_serializer.h" + "${MODULE_ROOT_DIR}/source/include/core_mqtt_state.h" + ) +# list the directories your mocks need +list(APPEND mock_include_list + . + ${CMAKE_CURRENT_LIST_DIR}/logging + ${MQTT_INCLUDE_PUBLIC_DIRS} + ) +#list the definitions of your mocks to control what to be included +list(APPEND mock_define_list + "" + ) + +# ================= Create the library under test here (edit) ================== + +# list the files you would like to test here +list(APPEND real_source_files + ${MQTT_SOURCES} + ${MQTT_SERIALIZER_SOURCES} + ) +# list the directories the module under test includes +list(APPEND real_include_directories + . + ${CMAKE_CURRENT_LIST_DIR}/logging + ${MQTT_INCLUDE_PUBLIC_DIRS} + ) + +# ===================== Create UnitTest Code here (edit) ===================== + +# list the directories your test needs to include +list(APPEND test_include_directories + . + ${MQTT_INCLUDE_PUBLIC_DIRS} + ) + +# ============================= (end edit) =================================== + +set(mock_name "${project_name}_mock") +set(real_name "${project_name}_real") + +create_mock_list(${mock_name} + "${mock_list}" + "${MODULE_ROOT_DIR}/tools/cmock/project.yml" + "${mock_include_list}" + "${mock_define_list}" + ) + +create_real_library(${real_name} + "${real_source_files}" + "${real_include_directories}" + "${mock_name}" + ) + +list(APPEND utest_link_list + -l${mock_name} + lib${real_name}.a + ) + +list(APPEND utest_dep_list + ${real_name} + ) + +# need to redefine because the tests below don't use any mocks +set(utest_link_list "") +list(APPEND utest_link_list + lib${real_name}.a + ) + +# mqtt_serializer_utest +set(utest_name "${project_name}_serializer_utest") +set(utest_source "${project_name}_serializer_utest.c") + +set(utest_link_list "") +list(APPEND utest_link_list + lib${real_name}.a + ) + +create_test(${utest_name} + ${utest_source} + "${utest_link_list}" + "${utest_dep_list}" + "${test_include_directories}" + ) \ No newline at end of file diff --git a/test/unit-test/WithoutUP/cmock_opaque_types.h b/test/unit-test/WithoutUP/cmock_opaque_types.h new file mode 100644 index 000000000..d29ef7b9b --- /dev/null +++ b/test/unit-test/WithoutUP/cmock_opaque_types.h @@ -0,0 +1,36 @@ +/* + * coreMQTT + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * SPDX-License-Identifier: MIT + * + * 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. + */ + +#ifndef CMOCK_OPAQUE_TYPES_H_ +#define CMOCK_OPAQUE_TYPES_H_ + +/* CMock does not support opaque types so needs concrete definitions for them. + * This file is included in CMock .c files. */ + +struct NetworkContext +{ + int a; +}; + +#endif /* ifndef CMOCK_OPAQUE_TYPES_H_ */ diff --git a/test/unit-test/WithoutUP/core_mqtt_config.h b/test/unit-test/WithoutUP/core_mqtt_config.h new file mode 100644 index 000000000..636473e4d --- /dev/null +++ b/test/unit-test/WithoutUP/core_mqtt_config.h @@ -0,0 +1,79 @@ +/* + * coreMQTT + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * SPDX-License-Identifier: MIT + * + * 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. + */ + +/** + * @file core_mqtt_config.h + * @brief This header sets configuration macros for the MQTT library. + */ +#ifndef CORE_MQTT_CONFIG_H_ +#define CORE_MQTT_CONFIG_H_ + +/* Standard include. */ +#include + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Include logging header files and define logging macros in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define the LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL macros depending on + * the logging configuration for MQTT. + * 3. Include the header file "logging_stack.h", if logging is enabled for MQTT. + */ + +#include "../logging/logging_levels.h" + +/* Logging configuration for the MQTT library. */ +#ifndef LIBRARY_LOG_NAME + #define LIBRARY_LOG_NAME "MQTT" +#endif + +#ifndef LIBRARY_LOG_LEVEL + #define LIBRARY_LOG_LEVEL LOG_NONE +#endif + +#include "../logging/logging_stack.h" + +/************ End of logging configuration ****************/ + +/** + * @brief Retry count for reading CONNACK from network. + * + * #MQTT_Connect() can be using retries. If timeout passed as 0 to MQTT_Connect(), + * retries are used to attempt to read from network. The maximum retry count is + * specified by this config. + * + * These unit tests expect retrying only twice. + */ +#define MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT ( 2U ) + +#define MQTT_SUB_UNSUB_MAX_VECTORS ( 6U ) + +#define MQTT_SEND_TIMEOUT_MS ( 200U ) +#define MQTT_VERSION_5_ENABLED ( true ) +#define MAX_USER_PROPERTY ( 0U ) +#define MQTT_USER_PROPERTY_ENABLED ( false ) + +#endif /* ifndef CORE_MQTT_CONFIG_H_ */ diff --git a/test/unit-test/WithoutUP/core_mqttv5WUP_serializer_utest.c b/test/unit-test/WithoutUP/core_mqttv5WUP_serializer_utest.c new file mode 100644 index 000000000..2e19d9285 --- /dev/null +++ b/test/unit-test/WithoutUP/core_mqttv5WUP_serializer_utest.c @@ -0,0 +1,322 @@ +/* + * coreMQTT + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * SPDX-License-Identifier: MIT + * + * 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. + */ + +/** + * @file core_mqtt_serializer_utest.c + * @brief Unit tests for functions in core_mqtt_serializer.h. + */ +#include +#include +#include +#include "unity.h" + +/* Include paths for public enums, structures, and macros. */ +#include "core_mqtt_serializer.h" + +/* Set network context to double pointer to buffer (uint8_t**). */ +struct NetworkContext +{ + uint8_t ** buffer; +}; + + +#define MQTT_TEST_UTF8_STRING ( "test" ) +#define MQTT_TEST_UTF8_STRING_LENGTH ( sizeof( MQTT_TEST_UTF8_STRING ) - 1 ) + +/** + * @brief Set a bit in an 8-bit unsigned integer. + */ +#define UINT8_SET_BIT( x, position ) ( ( x ) = ( uint8_t ) ( ( x ) | ( 0x01U << ( 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 ) & ( 0x01U << ( position ) ) ) == ( 0x01U << ( position ) ) ) + +/** + * @brief Get the high byte of a 16-bit unsigned integer. + */ +#define UINT16_HIGH_BYTE( x ) ( ( uint8_t ) ( ( x ) >> 8 ) ) + +/** + * @brief Get the low byte of a 16-bit unsigned integer. + */ +#define UINT16_LOW_BYTE( x ) ( ( uint8_t ) ( ( x ) & 0x00ffU ) ) + +#define MQTT_USER_PROPERTY_ID ( 0x26 ) +#define MQTT_REASON_STRING_ID ( 0x1F ) +#define MQTT_SERVER_REF_ID ( 0x1C ) + + +#define CORE_MQTT_ID_SIZE ( 1U ) +#define MQTT_REMAINING_LENGTH_INVALID ( ( size_t ) 268435456 ) + +/* Variables common to testcases */ +MQTTConnectProperties_t properties; +MQTTConnectInfo_t connectInfo; +MQTTPacketInfo_t packetInfo; +MQTTStatus_t status; + +/* ============================ UNITY FIXTURES ============================ */ + +/* Called before each test method. */ +void setUp( void ) +{ + memset( &properties, 0x0, sizeof( properties ) ); + memset( &connectInfo, 0x0, sizeof( connectInfo ) ); + memset( &packetInfo, 0x0, sizeof( packetInfo ) ); +} + +/* Called after each test method. */ +void tearDown( void ) +{ +} + +/* Called at the beginning of the whole suite. */ +void suiteSetUp() +{ +} + +/* Called at the end of the whole suite. */ +int suiteTearDown( int numFailures ) +{ + return numFailures; +} + +/* ========================================================================== */ + +/** + * @brief Encode remaining length into pDestination for packet serialization + * using MQTT v3.1.1 spec. + * + * @param[in] pDestination Buffer to write encoded remaining length. + * @param[in] length Actual remaining length. + */ +static size_t encodeRemainingLength( uint8_t * pDestination, + size_t length ) +{ + uint8_t lengthByte; + uint8_t * pLengthEnd = NULL; + size_t remainingLength = length; + + TEST_ASSERT_NOT_NULL( pDestination ); + + pLengthEnd = pDestination; + + /* This algorithm is copied from the MQTT v3.1.1 spec. */ + do + { + lengthByte = ( uint8_t ) ( remainingLength % 128U ); + remainingLength = remainingLength / 128U; + + /* Set the high bit of this byte, indicating that there's more data. */ + if( remainingLength > 0U ) + { + UINT8_SET_BIT( lengthByte, 7 ); + } + + /* Output a single encoded byte. */ + *pLengthEnd = lengthByte; + pLengthEnd++; + } while( remainingLength > 0U ); + + return ( size_t ) ( pLengthEnd - pDestination ); +} + +/** + * @brief Encode UTF-8 string and its length into pDestination for + * packet serialization. + * + * @param[in] pDestination Buffer to write encoded string. + * @param[in] source String to encode. + * @param[in] sourceLength Length of the string to encode. + */ +static size_t encodeString( uint8_t * pDestination, + const char * source, + uint16_t sourceLength ) +{ + uint8_t * pBuffer = NULL; + + /* Typecast const char * typed source buffer to const uint8_t *. + * This is to use same type buffers in memcpy. */ + const uint8_t * pSourceBuffer = ( const uint8_t * ) source; + + TEST_ASSERT_NOT_NULL( pSourceBuffer ); + TEST_ASSERT_NOT_NULL( pDestination ); + + pBuffer = pDestination; + + /* The first byte of a UTF-8 string is the high byte of the string length. */ + *pBuffer = UINT16_HIGH_BYTE( sourceLength ); + pBuffer++; + + /* The second byte of a UTF-8 string is the low byte of the string length. */ + *pBuffer = UINT16_LOW_BYTE( sourceLength ); + pBuffer++; + + /* Copy the string into pBuffer. */ + ( void ) memcpy( pBuffer, pSourceBuffer, sourceLength ); + + /* Return the pointer to the end of the encoded string. */ + pBuffer += sourceLength; + + return ( size_t ) ( pBuffer - pDestination ); +} + +static uint8_t * serializeutf_8( uint8_t * pIndex, + uint8_t propertyId ) +{ + uint8_t * pIndexLocal = pIndex; + + *pIndexLocal = propertyId; + pIndexLocal++; + size_t dummy = encodeString( pIndexLocal, MQTT_TEST_UTF8_STRING, MQTT_TEST_UTF8_STRING_LENGTH ); + pIndexLocal = &pIndexLocal[ dummy ]; + return pIndexLocal; +} + +static uint8_t * serializeutf_8pair( uint8_t * pIndex ) +{ + uint8_t * pIndexLocal = pIndex; + + *pIndexLocal = MQTT_USER_PROPERTY_ID; + pIndexLocal++; + size_t dummy = encodeString( pIndexLocal, MQTT_TEST_UTF8_STRING, MQTT_TEST_UTF8_STRING_LENGTH ); + pIndexLocal = &pIndexLocal[ dummy ]; + dummy = encodeString( pIndexLocal, MQTT_TEST_UTF8_STRING, MQTT_TEST_UTF8_STRING_LENGTH ); + pIndexLocal = &pIndexLocal[ dummy ]; + return pIndexLocal; +} + +void test_MQTTV5_DeserializeConnackOnlyUserProperty( void ) +{ + uint8_t buffer[ 100 ] = { 0 }; + uint8_t * pIndexLocal = buffer; + + buffer[ 0 ] = 0x01; + buffer[ 1 ] = 0x00; + + bool session = false; + properties.maxPacketSize = 100U; + packetInfo.pRemainingData = buffer; + packetInfo.type = MQTT_PACKET_TYPE_CONNACK; + packetInfo.remainingLength = 16; + pIndexLocal = &buffer[ 2 ]; + size_t propertyLength = encodeRemainingLength( pIndexLocal, 13 ); + pIndexLocal++; + pIndexLocal = serializeutf_8pair( pIndexLocal ); + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &session ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + + /*Invalid property length*/ + packetInfo.remainingLength = 5; + pIndexLocal = &buffer[ 2 ]; + propertyLength = encodeRemainingLength( pIndexLocal, 2 ); + pIndexLocal++; + pIndexLocal = serializeutf_8pair( pIndexLocal ); + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &session ); + TEST_ASSERT_EQUAL_INT( MQTTMalformedPacket, status ); + + /*Invalid property length*/ + packetInfo.remainingLength = 6; + pIndexLocal = &buffer[ 2 ]; + propertyLength = encodeRemainingLength( pIndexLocal, 3 ); + pIndexLocal++; + pIndexLocal = serializeutf_8pair( pIndexLocal ); + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &session ); + TEST_ASSERT_EQUAL_INT( MQTTMalformedPacket, status ); + + /*Invalid property length*/ + pIndexLocal = &buffer[ 2 ]; + propertyLength = encodeRemainingLength( pIndexLocal, 8 ); + packetInfo.remainingLength = propertyLength + 10; + pIndexLocal++; + pIndexLocal = serializeutf_8pair( pIndexLocal ); + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &session ); + TEST_ASSERT_EQUAL_INT( MQTTMalformedPacket, status ); + + /*Invalid property length*/ + packetInfo.remainingLength = 15; + pIndexLocal = &buffer[ 2 ]; + propertyLength = encodeRemainingLength( pIndexLocal, 12 ); + pIndexLocal++; + pIndexLocal = serializeutf_8pair( pIndexLocal ); + status = MQTTV5_DeserializeConnack( &properties, &packetInfo, &session ); + TEST_ASSERT_EQUAL_INT( MQTTMalformedPacket, status ); +} +void test_MQTTV5_DeserializeAck( void ) +{ + MQTTAckInfo_t ackInfo; + uint16_t packetIdentifier; + uint32_t maxPacketSize = 200U; + bool requestProblem = true; + MQTTStatus_t status = MQTTSuccess; + uint8_t buffer[ 100 ] = { 0 }; + uint8_t * pIndex = buffer; + size_t dummy; + + /* Verify parameters */ + memset( &ackInfo, 0x00, sizeof( ackInfo ) ); + packetInfo.pRemainingData = buffer; + buffer[ 0 ] = 0; + buffer[ 1 ] = 1; + buffer[ 2 ] = 0x00; + pIndex = &buffer[ 3 ]; + packetInfo.type = MQTT_PACKET_TYPE_PUBACK; + dummy = encodeRemainingLength( pIndex, 20 ); + packetInfo.remainingLength = dummy + 23; + pIndex++; + pIndex = serializeutf_8( pIndex, MQTT_REASON_STRING_ID ); + pIndex = serializeutf_8pair( pIndex ); + status = MQTTV5_DeserializeAck( &packetInfo, &packetIdentifier, &ackInfo, requestProblem, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); +} + + +void test_MQTTV5_DeserializeDisconnect() +{ + MQTTAckInfo_t disconnectInfo; + const char * pServerRef; + uint16_t serverRefLength; + int32_t maxPacketSize = 100U; + uint8_t buffer[ 100 ] = { 0 }; + uint8_t * pIndex = buffer; + size_t dummy; + + memset( &disconnectInfo, 0x0, sizeof( disconnectInfo ) ); + /*With properties*/ + pIndex = &buffer[ 1 ]; + packetInfo.pRemainingData = buffer; + dummy = encodeRemainingLength( pIndex, 27 ); + packetInfo.remainingLength = 28 + dummy; + pIndex++; + pIndex = serializeutf_8( pIndex, MQTT_REASON_STRING_ID ); + pIndex = serializeutf_8pair( pIndex ); + pIndex = serializeutf_8( pIndex, MQTT_SERVER_REF_ID ); + status = MQTTV5_DeserializeDisconnect( &packetInfo, &disconnectInfo, &pServerRef, &serverRefLength, maxPacketSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); +}