diff --git a/source/core_mqtt.c b/source/core_mqtt.c index 2a354192..41963713 100644 --- a/source/core_mqtt.c +++ b/source/core_mqtt.c @@ -30,6 +30,7 @@ #include #include #include +#include #include "core_mqtt.h" #include "core_mqtt_serializer.h" @@ -464,28 +465,31 @@ static MQTTStatus_t handleCleanSession( MQTTContext_t * pContext ); * @brief Send the publish packet without copying the topic string and payload in * the buffer. * - * @brief param[in] pContext Initialized MQTT context. - * @brief param[in] pPublishInfo MQTT PUBLISH packet parameters. - * @brief param[in] pMqttHeader the serialized MQTT header with the header byte; + * @param[in] pContext Initialized MQTT context. + * @param[in] pPublishInfo MQTT PUBLISH packet parameters. + * @param[in] pMqttHeader the serialized MQTT header with the header byte; * the encoded length of the packet; and the encoded length of the topic string. - * @brief param[in] headerSize Size of the serialized PUBLISH header. - * @brief param[in] packetId Packet Id of the publish packet. + * @param[in] headerSize Size of the serialized PUBLISH header. + * @param[in] packetId Packet Id of the publish packet. + * @param[in] pPropertyBuilder MQTT Publish property builder. * * @return #MQTTSendFailed if transport send during resend failed; + * #MQTTPublishStoreFailed if storing the outgoing publish failed in the case of QoS 1/2 * #MQTTSuccess otherwise. */ static MQTTStatus_t sendPublishWithoutCopy( MQTTContext_t * pContext, const MQTTPublishInfo_t * pPublishInfo, uint8_t * pMqttHeader, size_t headerSize, - uint16_t packetId ); + uint16_t packetId, + const MQTTPropBuilder_t * pPropertyBuilder ); /** * @brief Function to validate #MQTT_Publish parameters. * - * @brief param[in] pContext Initialized MQTT context. - * @brief param[in] pPublishInfo MQTT PUBLISH packet parameters. - * @brief param[in] packetId Packet Id for the MQTT PUBLISH packet. + * @param[in] pContext Initialized MQTT context. + * @param[in] pPublishInfo MQTT PUBLISH packet parameters. + * @param[in] packetId Packet Id for the MQTT PUBLISH packet. * * @return #MQTTBadParameter if invalid parameters are passed; * #MQTTSuccess otherwise. @@ -631,6 +635,48 @@ static void addSubscriptionOptions( const MQTTSubscribeInfo_t subscriptionInfo, */ static MQTTStatus_t handleSuback( MQTTContext_t * pContext, MQTTPacketInfo_t * pIncomingPacket ); + +/** + * @brief Send acks for received QoS 1/2 publishes. This function is used to send + * Publish Acks without any properties or reason codes. + * + * @param[in] pContext MQTT Connection context. + * @param[in] packetId packet ID of original PUBLISH. + * @param[in] publishState Current publish state in record. + * + * @return MQTTSuccess, MQTTBadParamater, MQTTBadResponse, MQTTIllegalState, MQTTSendFailed, MQTTStatusNotConnected, MQTTStatusDisconnectPending or MQTTNoMemory. + */ +static MQTTStatus_t sendPublishAcksWithoutProperty( MQTTContext_t * pContext, + uint16_t packetId, + MQTTPublishState_t publishState ); + +/** + * @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] reasonCode Reason code to be sent in the Publish Ack. + * + * @return #MQTTSuccess, #MQTTBadParameter, #MQTTIllegalState, #MQTTSendFailed, #MQTTStatusNotConnected, #MQTTStatusDisconnectPending or #MQTTBadResponse. + */ +static MQTTStatus_t sendPublishAcksWithProperty( MQTTContext_t * pContext, + uint16_t packetId, + MQTTPublishState_t publishState, + MQTTSuccessFailReasonCode_t reasonCode ); + +/** + * @brief Validate Publish Ack Reason Code + * + * @param[in] reasonCode Reason Code to validate + * @param[in] packetType Packet Type byte of the publish ack packet. (PUBACK, PUBREC, PUBREL, PUBCOMP) + * + * @return #MQTTBadParameter if invalid parameters are passed; + * #MQTTSuccess otherwise + */ +static MQTTStatus_t validatePublishAckReasonCode( MQTTSuccessFailReasonCode_t reasonCode, + uint8_t packetType ); + /*-----------------------------------------------------------*/ static bool matchEndWildcardsSpecialCases( const char * pTopicFilter, @@ -1513,21 +1559,364 @@ static MQTTStatus_t handleKeepAlive( MQTTContext_t * pContext ) /*-----------------------------------------------------------*/ +static MQTTStatus_t sendPublishAcksWithoutProperty( MQTTContext_t * pContext, + uint16_t packetId, + MQTTPublishState_t publishState ) +{ + MQTTStatus_t status = MQTTSuccess; + MQTTPublishState_t newState = MQTTStateNull; + int32_t sendResult = 0; + uint8_t packetTypeByte = 0U; + MQTTPubAckType_t packetType; + MQTTFixedBuffer_t localBuffer; + MQTTConnectionStatus_t connectStatus; + uint8_t pubAckPacket[ MQTT_PUBLISH_ACK_PACKET_SIZE ]; + + localBuffer.pBuffer = pubAckPacket; + localBuffer.size = MQTT_PUBLISH_ACK_PACKET_SIZE; + + assert( pContext != NULL ); + + packetTypeByte = getAckTypeToSend( publishState ); + + LogDebug( ( "Got ACK packet type 0x%02x [pkt ID: %hu] for the publish state 0x%02x", + packetTypeByte, + packetId, + publishState ) ); + + if( packetTypeByte != 0U ) + { + packetType = getAckFromPacketType( packetTypeByte ); + + status = MQTT_SerializeAck( &localBuffer, + packetTypeByte, + packetId ); + + if( MQTT_PUBLISH_ACK_PACKET_SIZE > pContext->connectionProperties.serverMaxPacketSize ) + { + LogError( ( "Packet size is greater than the allowed maximum packet size." ) ); + status = MQTTBadParameter; + } + + if( status == MQTTSuccess ) + { + MQTT_PRE_STATE_UPDATE_HOOK( pContext ); + { + connectStatus = pContext->connectStatus; + + if( connectStatus != MQTTConnected ) + { + status = ( connectStatus == MQTTNotConnected ) ? MQTTStatusNotConnected : MQTTStatusDisconnectPending; + } + + if( status == MQTTSuccess ) + { + LogDebug( ( "Sending ACK packet: PacketType=%02x, PacketID=%hu.", + ( unsigned int ) packetTypeByte, ( unsigned short ) packetId ) ); + + /* Here, we are not using the vector approach for efficiency. There is just one buffer + * to be sent which can be achieved with a normal send call. */ + sendResult = sendBuffer( pContext, + localBuffer.pBuffer, + MQTT_PUBLISH_ACK_PACKET_SIZE ); + + if( sendResult < ( int32_t ) MQTT_PUBLISH_ACK_PACKET_SIZE ) + { + status = MQTTSendFailed; + } + } + } + MQTT_POST_STATE_UPDATE_HOOK( pContext ); + } + + 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 + { + LogError( ( "Failed to send ACK packet: PacketType=%02x, SentBytes=%ld, " + "PacketSize=%lu.", + ( unsigned int ) packetTypeByte, ( long int ) sendResult, + MQTT_PUBLISH_ACK_PACKET_SIZE ) ); + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static MQTTStatus_t sendPublishAcksWithProperty( MQTTContext_t * pContext, + uint16_t packetId, + MQTTPublishState_t publishState, + MQTTSuccessFailReasonCode_t reasonCode ) +{ + int32_t bytesSentOrError; + size_t ioVectorLength = 0U; + size_t totalMessageLength = 0U; + MQTTStatus_t status = MQTTSuccess; + MQTTPublishState_t newState = MQTTStateNull; + uint8_t packetTypeByte = 0U; + MQTTPubAckType_t packetType; + size_t ackPropertyLength = 0U; + + /** + * Maximum number of bytes to send the Property Length. + * Property Length 0 + 4 = 4 + */ + uint8_t propertyLength[ 4U ]; + + /* 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 + */ + uint8_t pubAckHeader[ 8U ]; + size_t remainingLength = 0U; + size_t packetSize = 0U; + + /* The maximum vectors required to encode and send a publish ack. + * Ack Header 0 + 1 = 1 + * Property Length + 1 = 2 + * Properties + 1 = 3 + */ + + TransportOutVector_t pIoVector[ 3U ]; + + uint8_t * pIndex = pubAckHeader; + TransportOutVector_t * iterator = pIoVector; + + assert( pContext != NULL ); + + if( pContext->ackPropsBuffer.pBuffer != NULL ) + { + ackPropertyLength = pContext->ackPropsBuffer.currentIndex; + } + + packetTypeByte = getAckTypeToSend( publishState ); + + LogDebug( ( "Got ACK packet type 0x%02x [pkt ID: %hu] for the publish state 0x%02x", + packetTypeByte, + packetId, + publishState ) ); + + if( packetTypeByte != 0U ) + { + status = MQTT_ValidatePublishAckProperties( &pContext->ackPropsBuffer ); + + if( status == MQTTSuccess ) + { + status = validatePublishAckReasonCode( reasonCode, packetTypeByte ); + } + + if( status == MQTTSuccess ) + { + status = MQTT_GetAckPacketSize( &remainingLength, + &packetSize, + pContext->connectionProperties.serverMaxPacketSize, + ackPropertyLength ); + } + } + + if( pContext->connectStatus != MQTTConnected ) + { + status = ( pContext->connectStatus == MQTTNotConnected ) ? MQTTStatusNotConnected : MQTTStatusDisconnectPending; + } + + if( ( packetTypeByte != 0U ) && ( status == MQTTSuccess ) ) + { + packetType = getAckFromPacketType( packetTypeByte ); + + /* Only for fixed size fields. */ + pIndex = serializeAckFixed( pIndex, + packetTypeByte, + packetId, + remainingLength, + reasonCode ); + 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++; + + if( ( pContext->ackPropsBuffer.pBuffer != NULL ) && ( ackPropertyLength != 0U ) ) + { + /* Encode the property length. */ + pIndex = propertyLength; + pIndex = encodeVariableLength( propertyLength, ackPropertyLength ); + iterator->iov_base = propertyLength; + /* 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 - propertyLength ); + totalMessageLength += iterator->iov_len; + iterator++; + ioVectorLength++; + + /* Encode the properties. */ + iterator->iov_base = pContext->ackPropsBuffer.pBuffer; + iterator->iov_len = pContext->ackPropsBuffer.currentIndex; + totalMessageLength += iterator->iov_len; + iterator++; + ioVectorLength++; + + /* + * Resetting buffer after sending the message. + */ + + pContext->ackPropsBuffer.currentIndex = 0; + pContext->ackPropsBuffer.fieldSet = 0; + } + + MQTT_PRE_STATE_UPDATE_HOOK( pContext ); + { + LogDebug( ( "Sending ACK packet: PacketType=%02x, PacketID=%hu.", + ( unsigned int ) packetTypeByte, ( unsigned short ) packetId ) ); + + bytesSentOrError = sendMessageVector( pContext, pIoVector, ioVectorLength ); + } + MQTT_POST_STATE_UPDATE_HOOK( pContext ); + + 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 ) ); + } + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static MQTTStatus_t validatePublishAckReasonCode( MQTTSuccessFailReasonCode_t reasonCode, + uint8_t packetType ) +{ + MQTTStatus_t status = MQTTSuccess; + + switch( reasonCode ) + { + case MQTT_REASON_PUBACK_SUCCESS: + status = MQTTSuccess; + break; + + case MQTT_REASON_PUBACK_NO_MATCHING_SUBSCRIBERS: + case MQTT_REASON_PUBACK_UNSPECIFIED_ERROR: + case MQTT_REASON_PUBACK_IMPLEMENTATION_SPECIFIC_ERROR: + case MQTT_REASON_PUBACK_NOT_AUTHORIZED: + case MQTT_REASON_PUBACK_TOPIC_NAME_INVALID: + case MQTT_REASON_PUBACK_PACKET_IDENTIFIER_IN_USE: + case MQTT_REASON_PUBACK_QUOTA_EXCEEDED: + case MQTT_REASON_PUBACK_PAYLOAD_FORMAT_INVALID: + + if( ( packetType == MQTT_PACKET_TYPE_PUBACK ) || ( packetType == MQTT_PACKET_TYPE_PUBREC ) ) + { + status = MQTTSuccess; + } + else + { + status = MQTTBadParameter; + LogError( ( "Invalid Reason Code for PUBREL or PUBCOMP packet." ) ); + } + + break; + + case MQTT_REASON_PUBREL_PACKET_IDENTIFIER_NOT_FOUND: + + if( ( packetType == MQTT_PACKET_TYPE_PUBREL ) || ( packetType == MQTT_PACKET_TYPE_PUBCOMP ) ) + { + status = MQTTSuccess; + } + else + { + status = MQTTBadParameter; + LogError( ( "Invalid Reason Code for PUBREC or PUBACK packet." ) ); + } + + break; + + default: + status = MQTTBadParameter; + LogError( ( "Invalid Reason Code." ) ); + break; + } + + return status; +} + +/*-----------------------------------------------------------*/ + static MQTTStatus_t handleIncomingPublish( MQTTContext_t * pContext, MQTTPacketInfo_t * pIncomingPacket ) { MQTTStatus_t status; MQTTPublishState_t publishRecordState = MQTTStateNull; uint16_t packetIdentifier = 0U; - MQTTPublishInfo_t publishInfo; + MQTTPublishInfo_t publishInfo = { 0 }; MQTTDeserializedInfo_t deserializedInfo; bool duplicatePublish = false; + MQTTPropBuilder_t propBuffer = { 0 }; + MQTTSuccessFailReasonCode_t reasonCode; + bool ackPropsAdded; assert( pContext != NULL ); assert( pIncomingPacket != NULL ); assert( pContext->appCallback != NULL ); - status = MQTT_DeserializePublish( pIncomingPacket, &packetIdentifier, &publishInfo ); + status = MQTT_DeserializePublish( pIncomingPacket, + &packetIdentifier, + &publishInfo, + &propBuffer, + pContext->connectionProperties.maxPacketSize, + pContext->connectionProperties.topicAliasMax ); + LogInfo( ( "De-serialized incoming PUBLISH packet: DeserializerResult=%s.", MQTT_Status_strerror( status ) ) ); @@ -1615,30 +2004,45 @@ static MQTTStatus_t handleIncomingPublish( MQTTContext_t * pContext, if( status == MQTTSuccess ) { - /* Set fields of deserialized struct. */ deserializedInfo.packetIdentifier = packetIdentifier; deserializedInfo.pPublishInfo = &publishInfo; deserializedInfo.deserializationResult = status; /* Invoke application callback to hand the buffer over to application - * before sending acks. - * Application callback will be invoked for all publishes, except for - * duplicate incoming publishes. */ + * before sending acks. */ + reasonCode = MQTT_INVALID_REASON_CODE; + if( duplicatePublish == false ) { - /* TODO: Fix the callback params - will be handled with receive path. */ - pContext->appCallback( pContext, - pIncomingPacket, - &deserializedInfo, - NULL, - NULL, - NULL ); + if( pContext->appCallback( pContext, pIncomingPacket, &deserializedInfo, + &reasonCode, &pContext->ackPropsBuffer, &propBuffer ) == false ) + { + /* TODO: Figure out whether this should block the library + * from processing any more packets. */ + status = MQTTEventCallbackFailed; + } } - /* Send PUBACK or PUBREC if necessary. */ - status = sendPublishAcks( pContext, - packetIdentifier, - publishRecordState ); + /* Send PUBREC or PUBCOMP if necessary. */ + ackPropsAdded = ( pContext->ackPropsBuffer.pBuffer != NULL ) && + ( pContext->ackPropsBuffer.currentIndex > 0U ); + + if( status == MQTTSuccess ) + { + if( ( ackPropsAdded == false ) && ( reasonCode == MQTT_INVALID_REASON_CODE ) ) + { + status = sendPublishAcksWithoutProperty( pContext, + packetIdentifier, + publishRecordState ); + } + else + { + status = sendPublishAcksWithProperty( pContext, + packetIdentifier, + publishRecordState, + reasonCode ); + } + } } return status; @@ -1655,6 +2059,11 @@ static MQTTStatus_t handlePublishAcks( MQTTContext_t * pContext, MQTTPubAckType_t ackType; MQTTEventCallback_t appCallback; MQTTDeserializedInfo_t deserializedInfo; + MQTTPropBuilder_t propBuffer = { 0 }; + MQTTSuccessFailReasonCode_t reasonCode; + bool ackPropsAdded; + + MQTTReasonCodeInfo_t incomingReasonCode = { 0 }; assert( pContext != NULL ); assert( pIncomingPacket != NULL ); @@ -1664,21 +2073,27 @@ static MQTTStatus_t handlePublishAcks( MQTTContext_t * pContext, ackType = getAckFromPacketType( pIncomingPacket->type ); - /* TODO: Fix the call here. */ - status = MQTT_DeserializeAck( pIncomingPacket, &packetIdentifier, NULL, NULL, NULL ); - LogInfo( ( "Ack packet deserialized with result: %s.", - MQTT_Status_strerror( status ) ) ); + status = MQTT_DeserializeAck( pIncomingPacket, + &packetIdentifier, + &incomingReasonCode, + &propBuffer, + &pContext->connectionProperties ); + + LogDebug( ( "Ack packet of type %s (packet ID: %" PRIu16 ") deserialized with result: %s.", + MQTT_GetPacketTypeString( pIncomingPacket->type ), + packetIdentifier, + MQTT_Status_strerror( status ) ) ); if( status == MQTTSuccess ) { MQTT_PRE_STATE_UPDATE_HOOK( pContext ); - - status = MQTT_UpdateStateAck( pContext, - packetIdentifier, - ackType, - MQTT_RECEIVE, - &publishRecordState ); - + { + status = MQTT_UpdateStateAck( pContext, + packetIdentifier, + ackType, + MQTT_RECEIVE, + &publishRecordState ); + } MQTT_POST_STATE_UPDATE_HOOK( pContext ); if( status == MQTTSuccess ) @@ -1706,20 +2121,46 @@ static MQTTStatus_t handlePublishAcks( MQTTContext_t * pContext, if( status == MQTTSuccess ) { - /* Set fields of deserialized struct. */ deserializedInfo.packetIdentifier = packetIdentifier; deserializedInfo.deserializationResult = status; deserializedInfo.pPublishInfo = NULL; + deserializedInfo.pReasonCode = &incomingReasonCode; + - /* TODO: Fix the callback params - will be handled with receive path. */ /* Invoke application callback to hand the buffer over to application * before sending acks. */ - appCallback( pContext, pIncomingPacket, &deserializedInfo, NULL, NULL, NULL ); - /* Send PUBREL or PUBCOMP if necessary. */ - status = sendPublishAcks( pContext, - packetIdentifier, - publishRecordState ); + reasonCode = MQTT_INVALID_REASON_CODE; + + if( appCallback( pContext, pIncomingPacket, &deserializedInfo, &reasonCode, + &pContext->ackPropsBuffer, &propBuffer ) == false ) + { + /* TODO: verify whether this should block the recv thread? */ + status = MQTTEventCallbackFailed; + } + + if( status == MQTTSuccess ) + { + /* Send PUBREL or PUBCOMP if necessary. */ + ackPropsAdded = ( ( pContext->ackPropsBuffer.pBuffer != NULL ) && + ( pContext->ackPropsBuffer.currentIndex > 0U ) ); + + if( ( ackPropsAdded == false ) && ( reasonCode == MQTT_INVALID_REASON_CODE ) ) + { + LogTrace( ( "No reason code provided by application. Sending default reason code." ) ); + status = sendPublishAcksWithoutProperty( pContext, + packetIdentifier, + publishRecordState ); + } + else + { + LogTrace( ( "Reason code provided by application. Sending reason code." ) ); + status = sendPublishAcksWithProperty( pContext, + packetIdentifier, + publishRecordState, + reasonCode ); + } + } } return status; @@ -1749,8 +2190,8 @@ static MQTTStatus_t handleIncomingAck( MQTTContext_t * pContext, appCallback = pContext->appCallback; - LogDebug( ( "Received packet of type %02x.", - ( unsigned int ) pIncomingPacket->type ) ); + LogTrace( ( "Received packet of type %s.", + MQTT_GetPacketTypeString( pIncomingPacket->type ) ) ); switch( pIncomingPacket->type ) { @@ -1765,8 +2206,12 @@ static MQTTStatus_t handleIncomingAck( MQTTContext_t * pContext, break; case MQTT_PACKET_TYPE_PINGRESP: - /* TODO: Fix the call here. */ - status = MQTT_DeserializeAck( pIncomingPacket, &packetIdentifier, NULL, NULL, NULL ); + /* TODO: Fix the call here. Should be done with PING. */ + status = MQTT_DeserializeAck( pIncomingPacket, + &packetIdentifier, + NULL, + NULL, + &pContext->connectionProperties ); invokeAppCallback = ( status == MQTTSuccess ) && !manageKeepAlive; if( ( status == MQTTSuccess ) && ( manageKeepAlive == true ) ) @@ -1825,128 +2270,145 @@ static MQTTStatus_t receiveSingleIteration( MQTTContext_t * pContext, &( pContext->networkBuffer.pBuffer[ pContext->index ] ), pContext->networkBuffer.size - pContext->index ); - if( recvBytes < 0 ) - { - /* The receive function has failed. Bubble up the error up to the user. */ - status = MQTTRecvFailed; + LogDebug( ( "Received %ld bytes from network.", + ( long int ) recvBytes ) ); + LogTrace( ( "Index is at location: %ld", + ( long int ) pContext->index ) ); + LogTrace( ( "Remaining buffer capacity: %ld", + ( long int ) ( pContext->networkBuffer.size - pContext->index ) ) ); - MQTT_PRE_STATE_UPDATE_HOOK( pContext ); + do{ + if( recvBytes < 0 ) + { + /* The receive function has failed. Bubble up the error up to the user. */ + status = MQTTRecvFailed; - if( pContext->connectStatus == MQTTConnected ) + MQTT_PRE_STATE_UPDATE_HOOK( pContext ); + + if( pContext->connectStatus == MQTTConnected ) + { + pContext->connectStatus = MQTTDisconnectPending; + } + + MQTT_POST_STATE_UPDATE_HOOK( pContext ); + + LogTrace( ( "Recv failed with error: %s", strerror( errno ) ) ); + } + else if( ( recvBytes == 0 ) && ( pContext->index == 0U ) ) { - pContext->connectStatus = MQTTDisconnectPending; + LogDebug( ( "No data available from the network." ) ); + + /* No more bytes available since the last read and neither is anything in + * the buffer. */ + status = MQTTNoDataAvailable; } - MQTT_POST_STATE_UPDATE_HOOK( pContext ); - } - else if( ( recvBytes == 0 ) && ( pContext->index == 0U ) ) - { - /* No more bytes available since the last read and neither is anything in - * the buffer. */ - status = MQTTNoDataAvailable; - } + /* Either something was received, or there is still data to be processed in the + * buffer, or both. */ + else + { + /* Update the number of bytes in the MQTT fixed buffer. */ + pContext->index += ( size_t ) recvBytes; - /* Either something was received, or there is still data to be processed in the - * buffer, or both. */ - else - { - /* Update the number of bytes in the MQTT fixed buffer. */ - pContext->index += ( size_t ) recvBytes; + /* Reset this value so that it will not be counted in the next iteration. */ + recvBytes = 0; - status = MQTT_ProcessIncomingPacketTypeAndLength( pContext->networkBuffer.pBuffer, - &( pContext->index ), - &incomingPacket ); + status = MQTT_ProcessIncomingPacketTypeAndLength( pContext->networkBuffer.pBuffer, + &( pContext->index ), + &incomingPacket ); - totalMQTTPacketLength = incomingPacket.remainingLength + incomingPacket.headerLength; - } + totalMQTTPacketLength = incomingPacket.remainingLength + incomingPacket.headerLength; + } - /* No data was received, check for keep alive timeout. */ - if( recvBytes == 0 ) - { - if( manageKeepAlive == true ) + /* No data was received, check for keep alive timeout. */ + if( recvBytes == 0 ) { - /* Keep the copy of the status to be reset later. */ - MQTTStatus_t statusCopy = status; + if( manageKeepAlive == true ) + { + /* Keep the copy of the status to be reset later. */ + MQTTStatus_t statusCopy = status; - /* Assign status so an error can be bubbled up to application, - * but reset it on success. */ - status = handleKeepAlive( pContext ); + /* Assign status so an error can be bubbled up to application, + * but reset it on success. */ + status = handleKeepAlive( pContext ); - if( status == MQTTSuccess ) - { - /* Reset the status. */ - status = statusCopy; - } - else - { - LogError( ( "Handling of keep alive failed. Status=%s", - MQTT_Status_strerror( status ) ) ); + if( status == MQTTSuccess ) + { + /* Reset the status. */ + status = statusCopy; + } + else + { + LogError( ( "Handling of keep alive failed. Status=%s", + MQTT_Status_strerror( status ) ) ); + } } } - } - - /* Check whether there is data available before processing the packet further. */ - if( ( status == MQTTNeedMoreBytes ) || ( status == MQTTNoDataAvailable ) ) - { - /* Do nothing as there is nothing to be processed right now. The proper - * error code will be bubbled up to the user. */ - } - /* Any other error code. */ - else if( status != MQTTSuccess ) - { - LogError( ( "Call to receiveSingleIteration failed. Status=%s", - MQTT_Status_strerror( status ) ) ); - } - /* If the MQTT Packet size is bigger than the buffer itself. */ - else if( totalMQTTPacketLength > pContext->networkBuffer.size ) - { - /* Discard the packet from the receive buffer and drain the pending - * data from the socket buffer. */ - status = discardStoredPacket( pContext, - &incomingPacket ); - } - /* If the total packet is of more length than the bytes we have available. */ - else if( totalMQTTPacketLength > pContext->index ) - { - status = MQTTNeedMoreBytes; - } - else - { - /* MISRA else. */ - } - - /* Handle received packet. If incomplete data was read then this will not execute. */ - if( status == MQTTSuccess ) - { - incomingPacket.pRemainingData = &pContext->networkBuffer.pBuffer[ incomingPacket.headerLength ]; - /* PUBLISH packets allow flags in the lower four bits. For other - * packet types, they are reserved. */ - if( ( incomingPacket.type & 0xF0U ) == MQTT_PACKET_TYPE_PUBLISH ) + /* Check whether there is data available before processing the packet further. */ + if( ( status == MQTTNeedMoreBytes ) || ( status == MQTTNoDataAvailable ) ) + { + /* Do nothing as there is nothing to be processed right now. The proper + * error code will be bubbled up to the user. */ + } + /* Any other error code. */ + else if( status != MQTTSuccess ) + { + LogError( ( "Call to receiveSingleIteration failed. Status=%s", + MQTT_Status_strerror( status ) ) ); + } + /* If the MQTT Packet size is bigger than the buffer itself. */ + else if( totalMQTTPacketLength > pContext->networkBuffer.size ) + { + LogWarn( ( "Incoming packet size is bigger than MQTT buffer size. Discarding!" ) ); + /* Discard the packet from the receive buffer and drain the pending + * data from the socket buffer. */ + status = discardStoredPacket( pContext, + &incomingPacket ); + } + /* If the total packet is of more length than the bytes we have available. */ + else if( totalMQTTPacketLength > pContext->index ) { - status = handleIncomingPublish( pContext, &incomingPacket ); + status = MQTTNeedMoreBytes; } else { - status = handleIncomingAck( pContext, &incomingPacket, manageKeepAlive ); - - /* TODO: decide what to do when the app callback has failed. - * Should the packet be re-sent to the app? */ + /* MISRA else. */ } - /* 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 ); - + /* Handle received packet. If incomplete data was read then this will not execute. */ if( status == MQTTSuccess ) { - pContext->lastPacketRxTime = pContext->getTime(); + incomingPacket.pRemainingData = &pContext->networkBuffer.pBuffer[ incomingPacket.headerLength ]; + + /* PUBLISH packets allow flags in the lower four bits. For other + * packet types, they are reserved. */ + if( ( incomingPacket.type & 0xF0U ) == MQTT_PACKET_TYPE_PUBLISH ) + { + status = handleIncomingPublish( pContext, &incomingPacket ); + } + else + { + status = handleIncomingAck( pContext, &incomingPacket, manageKeepAlive ); + + /* TODO: decide what to do when the app callback has failed. + * Should the packet be re-sent to the app? */ + } + + /* 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 ); + + if( status == MQTTSuccess ) + { + pContext->lastPacketRxTime = pContext->getTime(); + } } - } + } while( ( pContext->index > 0 ) && ( status == MQTTSuccess ) ); if( status == MQTTNoDataAvailable ) { @@ -2297,24 +2759,37 @@ static MQTTStatus_t sendPublishWithoutCopy( MQTTContext_t * pContext, const MQTTPublishInfo_t * pPublishInfo, uint8_t * pMqttHeader, size_t headerSize, - uint16_t packetId ) + uint16_t packetId, + const MQTTPropBuilder_t * pPropertyBuilder ) { MQTTStatus_t status = MQTTSuccess; size_t ioVectorLength; size_t totalMessageLength; + size_t publishPropLength = 0U; bool dupFlagChanged = false; /* Bytes required to encode the packet ID in an MQTT header according to * the MQTT specification. */ uint8_t serializedPacketID[ 2U ]; + /** + * Maximum number of bytes to send the Property Length. + * Property Length 0 + 4 = 4 + */ + uint8_t propertyLength[ 4U ]; + /* Maximum number of vectors required to encode and send a publish * packet. The breakdown is shown below. * Fixed header (including topic string length) 0 + 1 = 1 * Topic string + 1 = 2 * Packet ID (only when QoS > QoS0) + 1 = 3 - * Payload + 1 = 4 */ - TransportOutVector_t pIoVector[ 4U ]; + * Property Length + 1 = 4 + * Optional Properties + 1 = 5 + * Payload + 1 = 6 */ + + TransportOutVector_t pIoVector[ 6U ]; + uint8_t * pIndex; + TransportOutVector_t * iterator; /* The header is sent first. */ pIoVector[ 0U ].iov_base = pMqttHeader; @@ -2343,6 +2818,35 @@ static MQTTStatus_t sendPublishWithoutCopy( MQTTContext_t * pContext, totalMessageLength += sizeof( serializedPacketID ); } + if( ( pPropertyBuilder != NULL ) && ( pPropertyBuilder->pBuffer != NULL ) ) + { + publishPropLength = pPropertyBuilder->currentIndex; + } + + iterator = &pIoVector[ ioVectorLength ]; + pIndex = propertyLength; + pIndex = encodeVariableLength( pIndex, publishPropLength ); + iterator->iov_base = propertyLength; + /* 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 - propertyLength ); + totalMessageLength += iterator->iov_len; + iterator++; + ioVectorLength++; + + /* Serialize the publish properties, if provided.*/ + + if( publishPropLength > 0U ) + { + iterator->iov_base = pPropertyBuilder->pBuffer; + iterator->iov_len = pPropertyBuilder->currentIndex; + totalMessageLength += iterator->iov_len; + iterator++; + ioVectorLength++; + } + /* Publish packets are allowed to contain no payload. */ if( pPublishInfo->payloadLength > 0U ) { @@ -2827,6 +3331,8 @@ static MQTTStatus_t handleUncleanSessionResumption( MQTTContext_t * pContext ) return status; } +/*-----------------------------------------------------------*/ + static MQTTStatus_t handleCleanSession( MQTTContext_t * pContext ) { MQTTStatus_t status = MQTTSuccess; @@ -2874,6 +3380,8 @@ static MQTTStatus_t handleCleanSession( MQTTContext_t * pContext ) return status; } +/*-----------------------------------------------------------*/ + static MQTTStatus_t validatePublishParams( const MQTTContext_t * pContext, const MQTTPublishInfo_t * pPublishInfo, uint16_t packetId ) @@ -3642,13 +4150,15 @@ MQTTStatus_t MQTT_Subscribe( MQTTContext_t * pContext, MQTTStatus_t MQTT_Publish( MQTTContext_t * pContext, const MQTTPublishInfo_t * pPublishInfo, - uint16_t packetId ) + uint16_t packetId, + const MQTTPropBuilder_t * pPropertyBuilder ) { size_t headerSize = 0UL; size_t remainingLength = 0UL; size_t packetSize = 0UL; MQTTPublishState_t publishStatus = MQTTStateNull; MQTTConnectionStatus_t connectStatus; + uint16_t topicAlias = 0U; /* Maximum number of bytes required by the 'fixed' part of the PUBLISH * packet header according to the MQTT specifications. @@ -3662,16 +4172,36 @@ MQTTStatus_t MQTT_Publish( MQTTContext_t * pContext, * an extra call to 'send' (in case writev is not defined) to send the * topic length. */ uint8_t mqttHeader[ 7U ]; + MQTTStatus_t status = MQTTSuccess; /* Validate arguments. */ - MQTTStatus_t status = validatePublishParams( pContext, pPublishInfo, packetId ); + status = validatePublishParams( pContext, pPublishInfo, packetId ); + + /* Validate Publish Properties and extract Topic Alias from the properties. */ + if( ( status == MQTTSuccess ) && ( pPropertyBuilder != NULL ) && ( pPropertyBuilder->pBuffer != NULL ) ) + { + status = MQTT_ValidatePublishProperties( pContext->connectionProperties.serverTopicAliasMax, + pPropertyBuilder, &topicAlias ); + } + + if( status == MQTTSuccess ) + { + /* Validate Publish Properties with the persistent Connection Properties. */ + status = MQTT_ValidatePublishParams( pPublishInfo, + pContext->connectionProperties.retainAvailable, + pContext->connectionProperties.serverMaxQos, + topicAlias, + pContext->connectionProperties.serverMaxPacketSize ); + } if( status == MQTTSuccess ) { /* Get the remaining length and packet size.*/ status = MQTT_GetPublishPacketSize( pPublishInfo, + pPropertyBuilder, &remainingLength, - &packetSize ); + &packetSize, + pContext->connectionProperties.serverMaxPacketSize ); } if( status == MQTTSuccess ) @@ -3718,7 +4248,7 @@ MQTTStatus_t MQTT_Publish( MQTTContext_t * pContext, pPublishInfo, mqttHeader, headerSize, - packetId ); + packetId, pPropertyBuilder ); } if( ( status == MQTTSuccess ) && @@ -4317,3 +4847,65 @@ void MQTT_SerializeMQTTVec( uint8_t * pAllocatedMem, } /*-----------------------------------------------------------*/ + +const char * MQTT_GetPacketTypeString( uint8_t packetType ) +{ + char * retVal; + if( ( packetType & 0xF0U ) == MQTT_PACKET_TYPE_PUBLISH ) + { + retVal = "PUBLISH"; + } + else + { + switch( packetType ) + { + case MQTT_PACKET_TYPE_CONNECT: + retVal = "CONNECT"; + break; + case MQTT_PACKET_TYPE_CONNACK: + retVal = "CONNACK"; + break; + case MQTT_PACKET_TYPE_PUBACK: + retVal = "PUBACK"; + break; + case MQTT_PACKET_TYPE_PUBREC: + retVal = "PUBREC"; + break; + case MQTT_PACKET_TYPE_PUBREL: + retVal = "PUBREL"; + break; + case MQTT_PACKET_TYPE_PUBCOMP: + retVal = "PUBCOMP"; + break; + case MQTT_PACKET_TYPE_SUBSCRIBE: + retVal = "SUBSCRIBE"; + break; + case MQTT_PACKET_TYPE_SUBACK: + retVal = "SUBACK"; + break; + case MQTT_PACKET_TYPE_UNSUBSCRIBE: + retVal = "UNSUBSCRIBE"; + break; + case MQTT_PACKET_TYPE_UNSUBACK: + retVal = "UNSUBACK"; + break; + case MQTT_PACKET_TYPE_PINGREQ: + retVal = "PINGREQ"; + break; + case MQTT_PACKET_TYPE_PINGRESP: + retVal = "PINGRESP"; + break; + case MQTT_PACKET_TYPE_DISCONNECT: + retVal = "DISCONNECT"; + break; + case MQTT_PACKET_TYPE_AUTH: + retVal = "AUTH"; + break; + default: + retVal = "UNKNOWN"; + break; + } + } + + return retVal; +} diff --git a/source/core_mqtt_prop_deserializer.c b/source/core_mqtt_prop_deserializer.c index 050c3ea3..c9dce9d1 100644 --- a/source/core_mqtt_prop_deserializer.c +++ b/source/core_mqtt_prop_deserializer.c @@ -278,7 +278,7 @@ MQTTStatus_t MQTT_GetNextPropertyType( MQTTPropBuilder_t * pPropertyBuilder, if( status != MQTTSuccess ) { - LogError( ( "Property type is invalid." ) ); + /* Do nothing. checkPropBuilderParams will log the warning/error. */ } else if( property == NULL ) { diff --git a/source/core_mqtt_prop_serializer.c b/source/core_mqtt_prop_serializer.c index 354234a1..88c9972f 100644 --- a/source/core_mqtt_prop_serializer.c +++ b/source/core_mqtt_prop_serializer.c @@ -32,7 +32,6 @@ #include #include "core_mqtt_serializer.h" -#include "transport_interface.h" #include "private/core_mqtt_serializer_private.h" diff --git a/source/core_mqtt_serializer.c b/source/core_mqtt_serializer.c index a06b7b61..f61ce252 100644 --- a/source/core_mqtt_serializer.c +++ b/source/core_mqtt_serializer.c @@ -31,6 +31,7 @@ #include #include +#include "core_mqtt.h" #include "core_mqtt_serializer.h" #include "transport_interface.h" #include "private/core_mqtt_serializer_private.h" @@ -183,13 +184,18 @@ static void serializePublishCommon( const MQTTPublishInfo_t * pPublishInfo, * @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. + * @param[in] publishPropertyLength Length of the optional properties in MQTT_PUBLISH * - * @return false if the packet would exceed the size allowed by the - * MQTT spec; true otherwise. + * + * @return MQTTBadParameter if the packet would exceed the size allowed by the + * MQTT spec; MQTTSuccess otherwise. */ -static bool calculatePublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, - size_t * pRemainingLength, - size_t * pPacketSize ); +static MQTTStatus_t calculatePublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, + size_t * pRemainingLength, + size_t * pPacketSize, + uint32_t maxPacketSize, + size_t publishPropertyLength ); /** * @brief Calculates the packet size and remaining length of an MQTT @@ -404,13 +410,17 @@ static MQTTStatus_t deserializeSuback( const MQTTPacketInfo_t * incomingPacket, * @param[out] pPacketId Packet identifier of the PUBLISH. * @param[out] pPublishInfo Pointer to #MQTTPublishInfo_t where output is * written. + * @param[out] pPropBuffer Pointer to the property buffer. + * @param[in] topicAliasMax Maximum allowed Topic Alias. * - * @return #MQTTSuccess if PUBLISH is valid; #MQTTBadResponse - * if the PUBLISH packet doesn't follow MQTT spec. + * @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 ); + MQTTPublishInfo_t * pPublishInfo, + MQTTPropBuilder_t * pPropBuffer, + uint16_t topicAliasMax ); /** * @brief Deserialize a PINGRESP packet. @@ -475,6 +485,61 @@ static MQTTStatus_t deserializeSubackProperties( MQTTPropBuilder_t * pPropBuffer size_t * pSubackPropertyLength, 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] pReasonCode Structure to store reason code of the ack type packet. + * @param[in] requestProblem To validate the packet. + * @param[out] pPropBuffer Pointer to the property buffer. + * + * @return #MQTTSuccess, #MQTTBadResponse, #MQTTBadParameter. + */ +static MQTTStatus_t deserializePubAcks( const MQTTPacketInfo_t * pAck, + uint16_t * pPacketIdentifier, + MQTTReasonCodeInfo_t * pReasonCode, + bool requestProblem, + MQTTPropBuilder_t * pPropBuffer ); +/** + * @brief Validate the length and decode the publish ack properties. + * + * @param[out] pPropBuffer To store the decoded property. + * @param[in] pIndex Pointer to the current index of the buffer. + * @param[in] remainingLength Remaining length of properties in the incoming packet. + * + * + * @return #MQTTSuccess, #MQTTBadResponse. + **/ +static MQTTStatus_t decodePubAckProperties( MQTTPropBuilder_t * pPropBuffer, + uint8_t * pIndex, + size_t remainingLength ); + +/** + * @brief Deserialize properties in the PUBLISH packet received from the server. + * + * Converts the packet from a stream of bytes to an #MQTTPublishInfo_t and + * extracts properties. + * + * @param[out] pPublishInfo Pointer to #MQTTPublishInfo_t where output is + * written. + * @param[out] pPropBuffer Pointer to the property buffer. + * @param[in] pIndex Pointer to the start of the properties. + * @param[in] topicAliasMax Maximum allowed Topic Alias. + * @param[in] remainingLength Remaining length of the incoming packet. + * + * @return #MQTTSuccess if PUBLISH is valid; #MQTTBadResponse + * if the PUBLISH packet doesn't follow MQTT spec. + */ +static MQTTStatus_t deserializePublishProperties( MQTTPublishInfo_t * pPublishInfo, + MQTTPropBuilder_t * pPropBuffer, + uint8_t * pIndex, + uint16_t topicAliasMax, + size_t remainingLength ); + /*-----------------------------------------------------------*/ static size_t remainingLengthEncodedSize( size_t length ) @@ -547,12 +612,15 @@ static uint8_t * encodeRemainingLength( uint8_t * pDestination, /*-----------------------------------------------------------*/ -static bool calculatePublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, - size_t * pRemainingLength, - size_t * pPacketSize ) +static MQTTStatus_t calculatePublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, + size_t * pRemainingLength, + size_t * pPacketSize, + uint32_t maxPacketSize, + size_t publishPropertyLength ) { - bool status = true; - size_t packetSize = 0, payloadLimit = 0; + MQTTStatus_t status = MQTTSuccess; + size_t packetSize = 0, propertyAndPayloadLimit = 0; + assert( pPublishInfo != NULL ); assert( pRemainingLength != NULL ); @@ -570,50 +638,61 @@ static bool calculatePublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, 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; + packetSize += variableLengthEncodedSize( publishPropertyLength ); - /* Ensure that the given payload fits within the calculated limit. */ - if( pPublishInfo->payloadLength > payloadLimit ) + /* Calculate the maximum allowed size of the properties and payload combined for + * the given parameters. */ + propertyAndPayloadLimit = MQTT_MAX_REMAINING_LENGTH - packetSize; + + if( publishPropertyLength > propertyAndPayloadLimit ) { - LogError( ( "PUBLISH payload length of %lu cannot exceed " + LogError( ( "PUBLISH properties 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, + "remaining length of MQTT 5.0 packet( %lu ).", + ( unsigned long ) publishPropertyLength, + ( unsigned long ) propertyAndPayloadLimit, MQTT_MAX_REMAINING_LENGTH ) ); - status = false; + status = MQTTBadParameter; } 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 ); + packetSize += publishPropertyLength; + propertyAndPayloadLimit -= publishPropertyLength; + } - /* Check that the given payload fits within the size allowed by MQTT spec. */ - if( pPublishInfo->payloadLength > payloadLimit ) + if( status == MQTTSuccess ) + { + if( pPublishInfo->payloadLength > propertyAndPayloadLimit ) { - LogError( ( "PUBLISH payload length of %lu cannot exceed " + LogError( ( "PUBLISH properties and payload combined 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, + "remaining length of MQTT 5.0 packet( %lu ).", + ( unsigned long ) ( pPublishInfo->payloadLength + publishPropertyLength ), + ( unsigned long ) ( propertyAndPayloadLimit + publishPropertyLength ), MQTT_MAX_REMAINING_LENGTH ) ); - status = false; + status = MQTTBadParameter; } else { - /* Set the "Remaining length" output parameter and calculate the full - * size of the PUBLISH packet. */ - *pRemainingLength = packetSize; + packetSize += pPublishInfo->payloadLength; + } + } + + if( status == MQTTSuccess ) + { + /* Set the "Remaining length" output parameter and calculate the full + * size of the PUBLISH packet. */ + *pRemainingLength = packetSize; + + packetSize += 1U + variableLengthEncodedSize( packetSize ); - packetSize += 1U + remainingLengthEncodedSize( packetSize ); + if( packetSize > maxPacketSize ) + { + LogError( ( "Packet size is greater than the allowed maximum packet size." ) ); + status = MQTTBadParameter; + } + else + { *pPacketSize = packetSize; } } @@ -621,6 +700,70 @@ static bool calculatePublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, LogDebug( ( "PUBLISH packet remaining length=%lu and packet size=%lu.", ( unsigned long ) *pRemainingLength, ( unsigned long ) *pPacketSize ) ); + + return status; +} + +/*-----------------------------------------------------------*/ + +static MQTTStatus_t deserializePubAcks( const MQTTPacketInfo_t * pAck, + uint16_t * pPacketIdentifier, + MQTTReasonCodeInfo_t * pReasonCode, + bool requestProblem, + MQTTPropBuilder_t * pPropBuffer ) +{ + assert( pAck != NULL ); + assert( pPacketIdentifier != NULL ); + assert( pReasonCode != NULL ); + assert( pAck->pRemainingData != NULL ); + + MQTTStatus_t status = MQTTSuccess; + uint8_t * pIndex = pAck->pRemainingData; + + if( pAck->remainingLength < 2U ) + { + status = MQTTBadResponse; + } + else + { + /* 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 send the reason code or not. */ + if( ( status == MQTTSuccess ) && ( pAck->remainingLength > 2U ) ) + { + pReasonCode->reasonCode = pIndex; + pReasonCode->reasonCodeLength = 1U; + pIndex++; + } + + if( ( status == MQTTSuccess ) && ( pAck->remainingLength > 3U ) ) + { + /* Protocol error to send user property and reason string if client has set request problem to false. */ + if( requestProblem == false ) + { + LogError( ( "User property and reason string not expected in ACK packet when requestProblem is false." ) ); + status = MQTTBadResponse; + } + else + { + /* 3 bytes have been used up by the packet ID (2) and reason code (1). */ + status = decodePubAckProperties( pPropBuffer, pIndex, pAck->remainingLength - 3U ); + } + } + return status; } @@ -1597,55 +1740,63 @@ static MQTTStatus_t validateSubscriptionSerializeParams( const MQTTSubscribeInfo static MQTTStatus_t deserializePublish( const MQTTPacketInfo_t * pIncomingPacket, uint16_t * pPacketId, - MQTTPublishInfo_t * pPublishInfo ) + MQTTPublishInfo_t * pPublishInfo, + MQTTPropBuilder_t * pPropBuffer, + uint16_t topicAliasMax ) { MQTTStatus_t status = MQTTSuccess; - const uint8_t * pVariableHeader, * pPacketIdentifierHigh = NULL; + const uint8_t * pPacketIdentifierHigh = NULL; + uint8_t * pIndex = NULL; assert( pIncomingPacket != NULL ); + assert( pIncomingPacket->pRemainingData != NULL ); assert( pPacketId != NULL ); assert( pPublishInfo != NULL ); - assert( pIncomingPacket->pRemainingData != NULL ); - pVariableHeader = pIncomingPacket->pRemainingData; + pIndex = pIncomingPacket->pRemainingData; /* The flags are the lower 4 bits of the first byte in PUBLISH. */ status = processPublishFlags( ( pIncomingPacket->type & 0x0FU ), pPublishInfo ); if( status == MQTTSuccess ) { /* Sanity checks for "Remaining length". A QoS 0 PUBLISH must have a remaining - * length of at least 3 to accommodate topic name length (2 bytes) and topic - * name (at least 1 byte). A QoS 1 or 2 PUBLISH must have a remaining length of - * at least 5 for the packet identifier in addition to the topic name length and - * topic name. */ + * length of at least 4 to accommodate topic name length (2 bytes), topic name + * (at least 1 byte) and, Property Length (at least 1 byte for 0 properties). + * A QoS 1 or 2 PUBLISH must have a remaining length of at least 5 for the packet + * identifier in addition to the topic name length and topic name. */ status = checkPublishRemainingLength( pIncomingPacket->remainingLength, pPublishInfo->qos, - MQTT_MIN_PUBLISH_REMAINING_LENGTH_QOS0 ); + 4U ); } if( status == MQTTSuccess ) { /* Extract the topic name starting from the first byte of the variable header. * The topic name string starts at byte 3 in the variable header. */ - pPublishInfo->topicNameLength = UINT16_DECODE( pVariableHeader ); + pPublishInfo->topicNameLength = UINT16_DECODE( pIndex ); + pIndex = &pIndex[ sizeof( uint16_t ) ]; /* Sanity checks for topic name length and "Remaining length". The remaining - * length must be at least as large as the variable length header. */ + * length must be at least as large as the variable length header: + * 2 bytes to encode the Topic Length + + * length of the topic string + + * 1 byte for the property length (when 0 properties). + */ status = checkPublishRemainingLength( pIncomingPacket->remainingLength, pPublishInfo->qos, - pPublishInfo->topicNameLength + sizeof( uint16_t ) ); + sizeof( uint16_t ) + pPublishInfo->topicNameLength + sizeof( uint8_t ) ); } if( status == MQTTSuccess ) { /* Parse the topic. */ - pPublishInfo->pTopicName = ( const char * ) ( &pVariableHeader[ sizeof( uint16_t ) ] ); - LogDebug( ( "Topic name length: %hu.", ( unsigned short ) pPublishInfo->topicNameLength ) ); + pPublishInfo->pTopicName = ( char * ) pIndex; + LogDebug( ( "Topic name: %hu.", ( unsigned short ) pPublishInfo->topicNameLength ) ); /* Extract the packet identifier for QoS 1 or 2 PUBLISH packets. Packet * identifier starts immediately after the topic name. */ - /* coverity[tainted_scalar] */ pPacketIdentifierHigh = ( const uint8_t * ) ( &pPublishInfo->pTopicName[ pPublishInfo->topicNameLength ] ); + pIndex = &pIndex[ pPublishInfo->topicNameLength ]; if( pPublishInfo->qos > MQTTQoS0 ) { @@ -1654,23 +1805,36 @@ static MQTTStatus_t deserializePublish( const MQTTPacketInfo_t * pIncomingPacket LogDebug( ( "Packet identifier %hu.", ( unsigned short ) *pPacketId ) ); - /* Advance pointer two bytes to start of payload as in the QoS 0 case. */ - pPacketIdentifierHigh = &pPacketIdentifierHigh[ sizeof( uint16_t ) ]; - /* Packet identifier cannot be 0. */ if( *pPacketId == 0U ) { LogError( ( "Packet identifier cannot be 0." ) ); status = MQTTBadResponse; } + + if( status == MQTTSuccess ) + { + pIndex = &pIndex[ sizeof( uint16_t ) ]; + } } } if( status == MQTTSuccess ) { + status = deserializePublishProperties( pPublishInfo, pPropBuffer, pIndex, + topicAliasMax, pIncomingPacket->remainingLength ); + } + + if( status == MQTTSuccess ) + { + pIndex = &pIndex[ variableLengthEncodedSize( pPublishInfo->propertyLength ) ]; + pIndex = &pIndex[ pPublishInfo->propertyLength ]; + /* Calculate the length of the payload. QoS 1 or 2 PUBLISH packets contain * a packet identifier, but QoS 0 PUBLISH packets do not. */ - pPublishInfo->payloadLength = pIncomingPacket->remainingLength - pPublishInfo->topicNameLength - sizeof( uint16_t ); + pPublishInfo->payloadLength = pIncomingPacket->remainingLength - pPublishInfo->topicNameLength - + sizeof( uint16_t ) - pPublishInfo->propertyLength - + variableLengthEncodedSize( pPublishInfo->propertyLength ); if( pPublishInfo->qos != MQTTQoS0 ) { @@ -1679,7 +1843,8 @@ static MQTTStatus_t deserializePublish( const MQTTPacketInfo_t * pIncomingPacket } /* Set payload if it exists. */ - pPublishInfo->pPayload = ( pPublishInfo->payloadLength != 0U ) ? pPacketIdentifierHigh : NULL; + + pPublishInfo->pPayload = ( pPublishInfo->payloadLength != 0U ) ? pIndex : NULL; LogDebug( ( "Payload length %lu.", ( unsigned long ) pPublishInfo->payloadLength ) ); @@ -2229,6 +2394,167 @@ static void serializeConnectPacket( const MQTTConnectInfo_t * pConnectInfo, /*-----------------------------------------------------------*/ +static MQTTStatus_t deserializePublishProperties( MQTTPublishInfo_t * pPublishInfo, + MQTTPropBuilder_t * pPropBuffer, + uint8_t * pIndex, + uint16_t topicAliasMax, + size_t remainingLength ) +{ + MQTTStatus_t status = MQTTSuccess; + uint32_t propertyLength = 0U; + uint8_t * pLocalIndex = pIndex; + uint32_t subscriptionId; + size_t remainingLengthForProperties; + bool contentType = false; + bool messageExpiryInterval = false; + bool responseTopic = false; + bool topicAlias = false; + bool payloadFormatIndicator = false; + bool correlationData = false; + uint16_t topicAliasVal; + + /*Decode Property Length */ + remainingLengthForProperties = remainingLength; + remainingLengthForProperties -= pPublishInfo->topicNameLength + sizeof( uint16_t ); + remainingLengthForProperties -= ( pPublishInfo->qos > MQTTQoS0 ) ? sizeof( uint16_t ) : 0U; + + status = decodeVariableLength( pLocalIndex, remainingLengthForProperties, &propertyLength ); + pPublishInfo->propertyLength = propertyLength; + + if( status == MQTTSuccess ) + { + status = checkPublishRemainingLength( remainingLength, + pPublishInfo->qos, + pPublishInfo->topicNameLength + + sizeof( uint16_t ) + propertyLength + + variableLengthEncodedSize( propertyLength ) ); + } + + if( status == MQTTSuccess ) + { + pLocalIndex = &pLocalIndex[ variableLengthEncodedSize( propertyLength ) ]; + } + + if( pPropBuffer != NULL ) + { + pPropBuffer->pBuffer = pLocalIndex; + pPropBuffer->bufferLength = propertyLength; + pPropBuffer->currentIndex = propertyLength; + } + + while( ( propertyLength > 0U ) && ( status == MQTTSuccess ) ) + { + uint8_t propertyId = *pLocalIndex; + pLocalIndex = &pLocalIndex[ 1 ]; + propertyLength -= sizeof( uint8_t ); + + switch( propertyId ) + { + case MQTT_PAYLOAD_FORMAT_ID: + { + uint8_t property; + status = decodeUint8t( &property, &propertyLength, &payloadFormatIndicator, &pLocalIndex ); + if( status == MQTTSuccess ) + { + /* Payload format must only be 0 or 1. */ + if( property > 1 ) + { + status = MQTTBadResponse; + LogError( ( "Payload Format Indicator is not 0x00. " ) ); + } + } + } + break; + + case MQTT_TOPIC_ALIAS_ID: + status = decodeUint16t( &topicAliasVal, &propertyLength, &topicAlias, &pLocalIndex ); + if( status == MQTTSuccess ) + { + if( topicAliasVal == 0 ) + { + status = MQTTBadResponse; + LogError( ( "Topic Alias value of 0 is not permitted." ) ); + } + } + break; + + case MQTT_RESPONSE_TOPIC_ID: + { + const char *pProperty; + uint16_t length; + status = decodeUtf8( &pProperty, &length, &propertyLength, &responseTopic, &pLocalIndex ); + } + break; + + case MQTT_CORRELATION_DATA_ID: + { + const char *pProperty; + uint16_t length; + status = decodeUtf8( &pProperty, &length, &propertyLength, &correlationData, &pLocalIndex ); + } + break; + + case MQTT_MSG_EXPIRY_ID: + { + uint32_t property; + status = decodeUint32t( &property, &propertyLength, &messageExpiryInterval, &pLocalIndex); + } + break; + + case MQTT_CONTENT_TYPE_ID: + { + const char *pProperty; + uint16_t length; + status = decodeUtf8( &pProperty, &length, &propertyLength, &contentType, &pLocalIndex ); + } + break; + + case MQTT_SUBSCRIPTION_ID_ID: + status = decodeVariableLength( pLocalIndex, propertyLength, &subscriptionId ); + + if( status == MQTTSuccess ) + { + pLocalIndex = &pLocalIndex[ variableLengthEncodedSize( subscriptionId ) ]; + propertyLength -= variableLengthEncodedSize( subscriptionId ); + } + + break; + + case MQTT_USER_PROPERTY_ID: + { + const char *pPropertyKey; + uint16_t propertyKeyLen; + const char *pPropertyValue; + uint16_t propertyValueLen; + status = decodeUserProp( &pPropertyKey, + &propertyKeyLen, + &pPropertyValue, + &propertyValueLen, + &propertyLength, + &pLocalIndex ); + } + break; + + default: + status = MQTTBadResponse; + break; + } + } + + if( ( status == MQTTSuccess ) && ( topicAlias == true ) ) + { + if( topicAliasMax < topicAliasVal ) + { + status = MQTTBadResponse; + LogError( ( "Topic Alias greater than Topic Alias Max. " ) ); + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + uint8_t * encodeVariableLength( uint8_t * pDestination, uint32_t length ) { @@ -2495,6 +2821,84 @@ MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * pConnectInfo, /*-----------------------------------------------------------*/ +static MQTTStatus_t decodePubAckProperties( MQTTPropBuilder_t * pPropBuffer, + uint8_t * pIndex, + size_t remainingLength ) +{ + uint32_t propertyLength = 0U; + MQTTStatus_t status = MQTTSuccess; + uint8_t * pLocalIndex = pIndex; + bool reasonString = false; + + /* Decode the property length */ + status = decodeVariableLength( pLocalIndex, remainingLength, &propertyLength ); + + if( status == MQTTSuccess ) + { + /* Validate the remaining length. Since properties are the last in the MQTT packet + * the length which is remaining must be: + * Bytes taken to encode the property length + + * The actual length of the properties + */ + if( remainingLength != ( propertyLength + variableLengthEncodedSize( propertyLength ) ) ) + { + status = MQTTBadResponse; + } + else + { + pLocalIndex = &pLocalIndex[ variableLengthEncodedSize( propertyLength ) ]; + } + } + + if( ( pPropBuffer != NULL ) && ( status == MQTTSuccess ) ) + { + pPropBuffer->pBuffer = pLocalIndex; + pPropBuffer->bufferLength = propertyLength; + } + + while( ( propertyLength > 0U ) && ( status == MQTTSuccess ) ) + { + /*Decode the property id.*/ + uint8_t propertyId = *pLocalIndex; + pLocalIndex = &pLocalIndex[ 1 ]; + propertyLength -= sizeof( uint8_t ); + + switch( propertyId ) + { + case MQTT_REASON_STRING_ID: + { + const char *pProperty; + uint16_t length; + status = decodeUtf8( &pProperty, &length, &propertyLength, &reasonString, &pLocalIndex ); + break; + } + + case MQTT_USER_PROPERTY_ID: + { + const char *pPropertyKey; + uint16_t propertyKeyLen; + const char *pPropertyValue; + uint16_t propertyValueLen; + status = decodeUserProp( &pPropertyKey, + &propertyKeyLen, + &pPropertyValue, + &propertyValueLen, + &propertyLength, + &pLocalIndex ); + break; + } + + default: + status = MQTTBadResponse; + break; + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * pConnectInfo, const MQTTPublishInfo_t * pWillInfo, size_t remainingLength, @@ -2769,10 +3173,18 @@ MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * pSubscriptio /*-----------------------------------------------------------*/ MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, + const MQTTPropBuilder_t * pPublishProperties, size_t * pRemainingLength, - size_t * pPacketSize ) + size_t * pPacketSize, + uint32_t maxPacketSize ) { MQTTStatus_t status = MQTTSuccess; + size_t propertyLength = 0U; + + if( ( pPublishProperties != NULL ) && ( pPublishProperties->pBuffer != NULL ) ) + { + propertyLength = pPublishProperties->currentIndex; + } if( ( pPublishInfo == NULL ) || ( pRemainingLength == NULL ) || ( pPacketSize == NULL ) ) { @@ -2783,25 +3195,10 @@ MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, ( 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 { - /* Calculate the "Remaining length" field and total packet size. If it exceeds - * what is allowed in the MQTT standard, return an error. */ - if( calculatePublishPacketSize( pPublishInfo, pRemainingLength, pPacketSize ) == false ) - { - LogError( ( "PUBLISH packet remaining length exceeds %lu, which is the " - "maximum size allowed by MQTT 3.1.1.", - MQTT_MAX_REMAINING_LENGTH ) ); - status = MQTTBadParameter; - } + status = calculatePublishPacketSize( pPublishInfo, pRemainingLength, + pPacketSize, maxPacketSize, propertyLength ); } return status; @@ -3160,7 +3557,10 @@ MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * pFixedBuffer ) MQTTStatus_t MQTT_DeserializePublish( const MQTTPacketInfo_t * pIncomingPacket, uint16_t * pPacketId, - MQTTPublishInfo_t * pPublishInfo ) + MQTTPublishInfo_t * pPublishInfo, + MQTTPropBuilder_t * propBuffer, + uint32_t maxPacketSize, + uint16_t topicAliasMax ) { MQTTStatus_t status = MQTTSuccess; @@ -3185,9 +3585,15 @@ MQTTStatus_t MQTT_DeserializePublish( const MQTTPacketInfo_t * pIncomingPacket, "pIncomingPacket->pRemainingData is NULL." ) ); status = MQTTBadParameter; } + else if( ( pIncomingPacket->remainingLength + + variableLengthEncodedSize( pIncomingPacket->remainingLength ) + + 1U ) > maxPacketSize ) + { + status = MQTTBadResponse; + } else { - status = deserializePublish( pIncomingPacket, pPacketId, pPublishInfo ); + status = deserializePublish( pIncomingPacket, pPacketId, pPublishInfo, propBuffer, topicAliasMax ); } return status; @@ -3320,8 +3726,11 @@ MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * pIncomingPacket, case MQTT_PACKET_TYPE_PUBREC: case MQTT_PACKET_TYPE_PUBREL: case MQTT_PACKET_TYPE_PUBCOMP: - /* TODO: Add this section. */ - status = MQTTBadParameter; + status = deserializePubAcks( pIncomingPacket, + pPacketId, + pReasonCode, + pConnectProperties->requestProblemInfo, + pPropBuffer ); break; case MQTT_PACKET_TYPE_SUBACK: @@ -3480,6 +3889,8 @@ MQTTStatus_t MQTT_ProcessIncomingPacketTypeAndLength( const uint8_t * pBuffer, /* Check validity. */ if( incomingPacketValid( pIncomingPacket->type ) == true ) { + LogTrace( ( "Incoming packet type: %s", + MQTT_GetPacketTypeString( pIncomingPacket->type ) ) ); status = processRemainingLength( pBuffer, pIndex, pIncomingPacket ); @@ -3977,3 +4388,267 @@ MQTTStatus_t MQTT_ValidateSubscribeProperties( bool isSubscriptionIdAvailable, return status; } + +/*-----------------------------------------------------------*/ + +MQTTStatus_t MQTT_ValidatePublishProperties( uint16_t serverTopicAliasMax, + const MQTTPropBuilder_t * propBuilder, + uint16_t * topicAlias ) +{ + MQTTStatus_t status = MQTTSuccess; + uint32_t propertyLength = 0U; + uint8_t * pLocalIndex = NULL; + bool topicAliasBool = false; + + if( ( propBuilder == NULL ) || ( propBuilder->pBuffer == NULL ) ) + { + LogError( ( "Property Builder is NULL. " ) ); + status = MQTTBadParameter; + } + else if( topicAlias == NULL ) + { + LogError( ( "Topic Alias is NULL" ) ); + status = MQTTBadParameter; + } + else + { + propertyLength = propBuilder->currentIndex; + pLocalIndex = propBuilder->pBuffer; + } + + while( ( propertyLength > 0U ) && ( status == MQTTSuccess ) ) + { + uint8_t propertyId = *pLocalIndex; + bool used = false; + pLocalIndex = &pLocalIndex[ 1 ]; + propertyLength -= sizeof( uint8_t ); + + switch( propertyId ) + { + case MQTT_PAYLOAD_FORMAT_ID: + { + uint8_t property; + status = decodeUint8t( &property, &propertyLength, &used, &pLocalIndex ); + } + break; + + case MQTT_MSG_EXPIRY_ID: + { + uint32_t property; + status = decodeUint32t( &property, &propertyLength, &used, &pLocalIndex ); + break; + } + + case MQTT_CONTENT_TYPE_ID: + case MQTT_CORRELATION_DATA_ID: + case MQTT_RESPONSE_TOPIC_ID: + { + const char *pProperty; + uint16_t length; + status = decodeUtf8( &pProperty, &length, &propertyLength, &used, &pLocalIndex ); + break; + } + + case MQTT_TOPIC_ALIAS_ID: + { + uint16_t property; + status = decodeUint16t( &property, &propertyLength, &topicAliasBool, &pLocalIndex ); + + if( ( status == MQTTSuccess ) && ( serverTopicAliasMax < *topicAlias ) ) + { + LogError( ( "Protocol Error: Topic Alias greater than Topic Alias Max" ) ); + status = MQTTBadParameter; + } + } + break; + + case MQTT_USER_PROPERTY_ID: + { + const char *pPropertyKey; + uint16_t propertyKeyLen; + const char *pPropertyValue; + uint16_t propertyValueLen; + status = decodeUserProp( &pPropertyKey, + &propertyKeyLen, + &pPropertyValue, + &propertyValueLen, + &propertyLength, + &pLocalIndex ); + } + break; + + default: + status = MQTTBadParameter; + break; + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +MQTTStatus_t MQTT_ValidatePublishParams( const MQTTPublishInfo_t * pPublishInfo, + uint8_t retainAvailable, + uint8_t maxQos, + uint16_t topicAlias, + uint32_t maxPacketSize ) +{ + MQTTStatus_t status; + + if( pPublishInfo == NULL ) + { + LogError( ( "Argument cannot be NULL: pPublishInfo=%p ", + ( void * ) pPublishInfo + ) ); + 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->qos ) ); + status = MQTTBadParameter; + } + else if( ( 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( ( 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( maxPacketSize == 0U ) + { + status = MQTTBadParameter; + } + else + { + status = MQTTSuccess; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +MQTTStatus_t MQTT_ValidatePublishAckProperties( const MQTTPropBuilder_t * pPropertyBuilder ) +{ + MQTTStatus_t status = MQTTSuccess; + uint32_t propertyLength = 0U; + uint8_t * pIndex = NULL; + bool used = false; + + if( ( pPropertyBuilder != NULL ) && ( pPropertyBuilder->pBuffer != NULL ) ) + { + propertyLength = pPropertyBuilder->currentIndex; + pIndex = pPropertyBuilder->pBuffer; + } + + while( ( propertyLength > 0U ) && ( status == MQTTSuccess ) ) + { + uint8_t propertyId = *pIndex; + pIndex = &pIndex[ 1 ]; + propertyLength -= sizeof( uint8_t ); + + switch( propertyId ) + { + case MQTT_REASON_STRING_ID: + { + const char *pProperty; + uint16_t length; + status = decodeUtf8( &pProperty, &length, &propertyLength, &used, &pIndex ); + } + break; + + case MQTT_USER_PROPERTY_ID: + { + const char * key, *value; + uint16_t keyLength, valueLength; + status = decodeUserProp( &key, + &keyLength, + &value, + &valueLength, + &propertyLength, + &pIndex ); + } + break; + + default: + status = MQTTBadParameter; + break; + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +MQTTStatus_t MQTT_GetAckPacketSize( size_t * pRemainingLength, + size_t * pPacketSize, + uint32_t maxPacketSize, + size_t ackPropertyLength ) +{ + MQTTStatus_t status = MQTTSuccess; + size_t length = 0U; + size_t propertyLength = 0U; + size_t packetSize = 0U; + + propertyLength = ackPropertyLength; + + /*Validate the parameters.*/ + if( ( pRemainingLength == NULL ) || ( pPacketSize == NULL ) ) + { + status = MQTTBadParameter; + } + else if( maxPacketSize == 0U ) + { + status = MQTTBadParameter; + } + else + { + length += MQTT_PUBLISH_ACK_PACKET_SIZE_WITH_REASON; + + length += variableLengthEncodedSize( propertyLength ) + propertyLength; + + if( length > MQTT_MAX_REMAINING_LENGTH ) + { + status = MQTTBadParameter; + LogError( ( "Remaining Length greater than Maximum Remaining Length according to MQTTv5 spec." ) ); + } + else + { + *pRemainingLength = length; + } + } + + if( status == MQTTSuccess ) + { + packetSize = length + 1U + variableLengthEncodedSize( length ); + + if( packetSize > maxPacketSize ) + { + status = MQTTBadParameter; + LogError( ( "Packet size greater than Max Packet Size specified in the CONNACK" ) ); + } + else + { + *pPacketSize = packetSize; + } + } + + return status; +} diff --git a/source/core_mqtt_serializer_private.c b/source/core_mqtt_serializer_private.c index ce51baeb..9218e5f9 100644 --- a/source/core_mqtt_serializer_private.c +++ b/source/core_mqtt_serializer_private.c @@ -364,3 +364,28 @@ MQTTStatus_t decodeVariableLength( const uint8_t * pBuffer, } /*-----------------------------------------------------------*/ + +uint8_t * serializeAckFixed( uint8_t * pIndex, + uint8_t packetType, + uint16_t packetId, + size_t remainingLength, + MQTTSuccessFailReasonCode_t reasonCode ) +{ + 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 = encodeVariableLength( pIndexLocal, remainingLength ); + /*Encode the packet id.*/ + pIndexLocal[ 0 ] = UINT16_HIGH_BYTE( packetId ); + pIndexLocal[ 1 ] = UINT16_LOW_BYTE( packetId ); + pIndexLocal = &pIndexLocal[ 2 ]; + /*We are now sending the ack.*/ + *pIndexLocal = ( uint8_t ) reasonCode; + pIndexLocal++; + return pIndexLocal; +} + +/*-----------------------------------------------------------*/ diff --git a/source/include/core_mqtt.h b/source/include/core_mqtt.h index 5c89fa21..e542087f 100644 --- a/source/include/core_mqtt.h +++ b/source/include/core_mqtt.h @@ -925,16 +925,28 @@ MQTTStatus_t MQTT_Subscribe( MQTTContext_t * pContext, * @param[in] pContext Initialized MQTT context. * @param[in] pPublishInfo MQTT PUBLISH packet parameters. * @param[in] packetId packet ID generated by #MQTT_GetPacketId. + * @param[in] pPropertyBuilder Properties to be sent in the outgoing packet. * - * @return #MQTTNoMemory if pBuffer is too small to hold the MQTT packet; - * #MQTTBadParameter if invalid parameters are passed; - * #MQTTSendFailed if transport write failed; - * #MQTTStatusNotConnected if the connection is not established yet + * @return + * #MQTTBadParameter if invalid parameters are passed;
+ * #MQTTBadResponse if there is an error in property parsing;
+ * #MQTTSendFailed if transport write failed;
+ * #MQTTStatusNotConnected if the connection is not established yet
* #MQTTStatusDisconnectPending if the user is expected to call MQTT_Disconnect - * before calling any other API + * before calling any other API
* #MQTTPublishStoreFailed if the user provided callback to copy and store the - * outgoing publish packet fails - * #MQTTSuccess otherwise. + * outgoing publish packet fails
+ * #MQTTSuccess otherwise.
+ * + * Functions to add optional properties to the PUBLISH packet are: + * + * - #MQTTPropAdd_PubPayloadFormat + * - #MQTTPropAdd_PubMessageExpiry + * - #MQTTPropAdd_PubTopicAlias + * - #MQTTPropAdd_PubResponseTopic + * - #MQTTPropAdd_PubCorrelationData + * - #MQTTPropAdd_PubContentType + * - #MQTTPropAdd_UserProp * * Example * @code{c} @@ -952,11 +964,19 @@ MQTTStatus_t MQTT_Subscribe( MQTTContext_t * pContext, * publishInfo.topicNameLength = strlen( publishInfo.pTopicName ); * publishInfo.pPayload = "Hello World!"; * publishInfo.payloadLength = strlen( "Hello World!" ); + * // Optional properties to be sent in the PUBLISH packet. + * MQTTPropBuilder_t propertyBuilder; + * uint8_t propertyBuffer[ 100 ]; + * size_t propertyBufferLength = sizeof( propertyBuffer ); + * status = MQTTPropertyBuilder_Init( &propertyBuilder, propertyBuffer, propertyBufferLength ); + * + * // Set a property in the propertyBuilder + * status = MQTTPropAdd_PubPayloadFormat( &propertyBuilder, 1); * * // Packet ID is needed for QoS > 0. * packetId = MQTT_GetPacketId( pContext ); * - * status = MQTT_Publish( pContext, &publishInfo, packetId ); + * status = MQTT_Publish( pContext, &publishInfo, packetId, &propertyBuilder ); * * if( status == MQTTSuccess ) * { @@ -968,7 +988,8 @@ MQTTStatus_t MQTT_Subscribe( MQTTContext_t * pContext, /* @[declare_mqtt_publish] */ MQTTStatus_t MQTT_Publish( MQTTContext_t * pContext, const MQTTPublishInfo_t * pPublishInfo, - uint16_t packetId ); + uint16_t packetId, + const MQTTPropBuilder_t * pPropertyBuilder ); /* @[declare_mqtt_publish] */ /** @@ -1392,6 +1413,30 @@ void MQTT_SerializeMQTTVec( uint8_t * pAllocatedMem, const MQTTVec_t * pVec ); /* @[declare_mqtt_serializemqttvec] */ +/** + * @brief Get a human-readable string representation of an MQTT packet type. + * + * This function converts an MQTT packet type byte into a corresponding + * string representation for debugging and logging purposes. + * + * @param[in] packetType The MQTT packet type byte to convert. + * + * @return A pointer to a constant string containing the packet type name. + * Returns "UNKNOWN" if the packet type is not recognized. + * + * @note The returned string is statically allocated and should not be freed. + * @note For PUBLISH packets, the function masks the lower 4 bits (flags) and + * returns "PUBLISH" regardless of the QoS, DUP, or RETAIN flag values. + * + * Example + * @code{c} + * uint8_t packetType = MQTT_PACKET_TYPE_PUBLISH; + * const char * packetName = MQTT_GetPacketTypeString( packetType ); + * printf( "Received packet: %s\n", packetName ); // Prints "Received packet: PUBLISH" + * @endcode + */ +const char * MQTT_GetPacketTypeString( uint8_t packetType ); + /* *INDENT-OFF* */ #ifdef __cplusplus } diff --git a/source/include/core_mqtt_config_defaults.h b/source/include/core_mqtt_config_defaults.h index 3fd79f97..1c3f9526 100644 --- a/source/include/core_mqtt_config_defaults.h +++ b/source/include/core_mqtt_config_defaults.h @@ -275,6 +275,26 @@ #define LogDebug( message ) #endif +/** + * @brief Macro that is called in the MQTT library for logging "Trace" level + * messages. + * + * To enable trace level logging from MQTT library, this macro should be mapped to the + * application-specific logging implementation that supports trace logging. + * + * @note This logging macro is called in the MQTT library with parameters wrapped in + * double parentheses to be ISO C89/C90 standard compliant. For a reference + * POSIX implementation of the logging macros, refer to core_mqtt_config.h files, and the + * logging-stack in demos folder of the + * [AWS IoT Embedded C SDK repository](https://github.com/aws/aws-iot-device-sdk-embedded-C/). + * + * Default value: Trace logging is turned off, and no code is generated for calls + * to the macro in the MQTT library on compilation. + */ +#ifndef LogTrace + #define LogTrace( message ) +#endif + /* *INDENT-OFF* */ #ifdef __cplusplus } diff --git a/source/include/core_mqtt_serializer.h b/source/include/core_mqtt_serializer.h index 0ccdf353..8a82c6a1 100644 --- a/source/include/core_mqtt_serializer.h +++ b/source/include/core_mqtt_serializer.h @@ -427,6 +427,12 @@ typedef struct MQTTPublishInfo * @brief Message payload length. */ size_t payloadLength; + + /** + * @brief Length of the properties. + */ + uint32_t propertyLength; + } MQTTPublishInfo_t; /** @@ -1122,18 +1128,18 @@ MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * pSubscriptio /** * @brief Get the packet size and remaining length of an MQTT PUBLISH packet. * - * 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 + * #MQTT_ValidatePublishParams should be called with @p pPublishInfo before invoking this function + * to validate the publish parameters. This function must be called before #sendPublishWithoutCopy + * in order to get the size of the MQTT PUBLISH packet that is generated from #MQTTPublishInfo_t + * and optional publish properties. 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[in] pPublishProperties MQTT PUBLISH properties builder. Pass NULL if not used. * @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. @@ -1144,6 +1150,10 @@ MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * pSubscriptio * // Variables used in this example. * MQTTStatus_t status; * MQTTPublishInfo_t publishInfo = { 0 }; + * MQTTPropBuilder_t publishProperties = { 0 }; + * uint16_t topicAliasMax; + * uint8_t retainAvailable; + * uint8_t maxQos; * size_t remainingLength = 0, packetSize = 0; * * // Initialize the publish info. @@ -1153,22 +1163,33 @@ MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * pSubscriptio * publishInfo.pPayload = "Hello World!"; * publishInfo.payloadLength = strlen( "Hello World!" ); * + * // Initialize publish properties (if needed) + * initializePublishProperties( &publishProperties ); + * + * // Validate publish parameters + * status = MQTT_ValidatePublishParams(&publishInfo, topicAliasMax, retainAvailable, maxQos); + * * // Get the size requirement for the publish packet. * status = MQTT_GetPublishPacketSize( - * &publishInfo, &remainingLength, &packetSize + * &publishInfo, + * &publishProperties, + * &remainingLength, + * &packetSize, + * maxPacketSize * ); * * if( status == MQTTSuccess ) * { - * // The application should allocate or use a static #MQTTFixedBuffer_t - * // of size >= packetSize to serialize the publish. + * // The publish packet can now be sent to the broker. * } * @endcode */ /* @[declare_mqtt_getpublishpacketsize] */ MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, + const MQTTPropBuilder_t * pPublishProperties, size_t * pRemainingLength, - size_t * pPacketSize ); + size_t * pPacketSize, + uint32_t maxPacketSize); /* @[declare_mqtt_getpublishpacketsize] */ /** @@ -1530,8 +1551,14 @@ MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * pFixedBuffer ); * @param[in] pIncomingPacket #MQTTPacketInfo_t containing the buffer. * @param[out] pPacketId The packet ID obtained from the buffer. * @param[out] pPublishInfo Struct containing information about the publish. + * @param[in] propBuffer Buffer to hold the properties. + * @param[in] maxPacketSize Maximum packet size. + * @param[in] topicAliasMax Maximum topic alias specified in the CONNECT packet. * - * @return #MQTTBadParameter, #MQTTBadResponse, or #MQTTSuccess. + * @return + * - #MQTTBadParameter if invalid parameters are passed + * - #MQTTBadResponse if invalid packet is read + * - #MQTTSuccess otherwise. * * Example * @code{c} @@ -1549,7 +1576,10 @@ MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * pFixedBuffer ); * MQTTStatus_t status; * MQTTPacketInfo_t incomingPacket; * MQTTPublishInfo_t publishInfo = { 0 }; + * MQTTPropBuilder_t propBuffer ; * uint16_t packetId; + * uint32_t maxPacketSize = pContext->connectionProperties.maxPacketSize; + * uint16_t topicAliasMax = pContext->connectionProperties.topicAliasMax; * * int32_t bytesRecvd; * // A buffer to hold remaining data of the incoming packet. @@ -1573,7 +1603,8 @@ MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * pFixedBuffer ); * // Deserialize the publish information if the incoming packet is a publish. * if( ( incomingPacket.type & 0xF0 ) == MQTT_PACKET_TYPE_PUBLISH ) * { - * status = MQTT_DeserializePublish( &incomingPacket, &packetId, &publishInfo ); + * status = MQTT_DeserializePublish( &incomingPacket, &packetId, &publishInfo, + * &propBuffer, maxPacketSize, topicAliasMax ); * if( status == MQTTSuccess ) * { * // The deserialized publish information can now be used from `publishInfo`. @@ -1582,9 +1613,12 @@ MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * pFixedBuffer ); * @endcode */ /* @[declare_mqtt_deserializepublish] */ -MQTTStatus_t MQTT_DeserializePublish( const MQTTPacketInfo_t * pIncomingPacket, - uint16_t * pPacketId, - MQTTPublishInfo_t * pPublishInfo ); +MQTTStatus_t MQTT_DeserializePublish( const MQTTPacketInfo_t* pIncomingPacket, + uint16_t* pPacketId, + MQTTPublishInfo_t* pPublishInfo, + MQTTPropBuilder_t* propBuffer, + uint32_t maxPacketSize, + uint16_t topicAliasMax ); /* @[declare_mqtt_deserializepublish] */ /** @@ -2729,7 +2763,7 @@ MQTTStatus_t MQTTPropGet_CorrelationData( MQTTPropBuilder_t * pPropertyBuilder, * @return #MQTTSuccess if property is retrieved successfully; * #MQTTBadParameter if invalid parameters are passed. */ -MQTTStatus_t MQTTPropGet_PubSubscriptionId( MQTTPropBuilder_t * pPropertyBuilder, +MQTTStatus_t MQTTPropGet_SubscriptionId( MQTTPropBuilder_t * pPropertyBuilder, uint32_t * currentIndex, uint32_t * pSubscriptionId ); @@ -2749,6 +2783,124 @@ MQTTStatus_t MQTTPropGet_PubContentType( MQTTPropBuilder_t * pPropertyBuilder, const char ** pContentType, uint16_t * pContentTypeLength ); +/** + * @brief Validates the properties of a PUBLISH packet. + * + * This function validates the properties in the property builder for a PUBLISH packet. + * + * @param[in] serverTopicAliasMax Maximum topic alias value allowed by the server. + * @param[in] propBuilder Pointer to the property builder structure. + * @param[out] topicAlias Pointer to store the topic alias value if present. + * + * @return Returns one of the following: + * - #MQTTSuccess if the properties are valid + * - #MQTTBadParameter if invalid parameters are passed + * - #MQTTBadResponse if an invalid packet is read + */ +/* @[declare_mqtt_validatepublishproperties] */ +MQTTStatus_t MQTT_ValidatePublishProperties(uint16_t serverTopicAliasMax, const MQTTPropBuilder_t* propBuilder, uint16_t *topicAlias); +/* @[declare_mqtt_validatepublishproperties] */ + +/** + * @brief Validate the publish parameters present in the given publish structure @p pPublishInfo. + * + * This function must be called before #MQTT_GetPublishPacketSize in order to validate the publish parameters. + * + * @param[in] pPublishInfo MQTT publish packet parameters. + * @param[in] retainAvailable Whether server allows retain or not. + * @param[in] maxQos Maximum QoS supported by the server. + * @param[in] topicAlias Topic alias in the PUBLISH packet. + * @param[in] maxPacketSize Maximum packet size allowed 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 topicAlias; + * uint8_t retainAvailable; + * uint8_t maxQos; + * // Set in the CONNACK packet. + * uint32_t maxPacketSize ; + * + * //Set the publish info parameters. + * + * //Validate the publish packet + * status = MQTT_ValidatePublishParams(&publishInfo, retainAvailable, maxQos, topicAlias, maxPacketSize); + * + * if( status == MQTTSuccess ) + * { + * // Get the packet size and serialize the publish packet. + * } + * @endcode + */ +/* @[declare_mqtt_validatepublishparams] */ +MQTTStatus_t MQTT_ValidatePublishParams(const MQTTPublishInfo_t* pPublishInfo, + uint8_t retainAvailable, + uint8_t maxQos, + uint16_t topicAlias, + uint32_t maxPacketSize); +/* @[declare_mqtt_validatepublishparams] */ + +/** + * @brief Validates the properties specified for an MQTT PUBLISH ACK packet. + * + * @param[in] pPropertyBuilder Pointer to the property builder structure containing unsubscribe properties. + * + * @return Returns one of the following: + * - #MQTTSuccess , #MQTTBadParameter or #MQTTBadResponse. + */ +/* @[declare_mqtt_validatepublishackproperties] */ +MQTTStatus_t MQTT_ValidatePublishAckProperties( const MQTTPropBuilder_t * pPropertyBuilder ); +/* @[declare_mqtt_validatepublishackproperties] */ + +/** + * @brief Get the size of an outgoing PUBLISH ACK packet. + * + * @note If no reason code is sent and property length is zero then #MQTT_SerializeAck can be used directly. + * + * @param[out] pRemainingLength The remaining length of the packet to be serialized. + * @param[out] pPacketSize The size of the packet to be serialized. + * @param[in] maxPacketSize Maximum packet size allowed by the server. + * @param[in] ackPropertyLength The length of the properties. + * + * @return #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; + * size_t remainingLength =0; + * size_t packetSize = 0; + * size_t ackPropertyLength = 0; + * uint32_t maxPacketSize; + * //set the parameters. + * // Get the size requirement for the ack packet. + * status = MQTT_GetAckPacketSize(&remainingLength,&packetSize,maxPacketSize, ackPropertyLength); + * } + * @endcode + */ +/* @[declare_mqtt_getackpacketsize] */ +MQTTStatus_t MQTT_GetAckPacketSize(size_t* pRemainingLength, + size_t* pPacketSize, + uint32_t maxPacketSize, + size_t ackPropertyLength); +/* @[declare_mqtt_getackpacketsize] */ /* *INDENT-OFF* */ #ifdef __cplusplus diff --git a/source/include/private/core_mqtt_serializer_private.h b/source/include/private/core_mqtt_serializer_private.h index 68480b00..48f05717 100644 --- a/source/include/private/core_mqtt_serializer_private.h +++ b/source/include/private/core_mqtt_serializer_private.h @@ -406,4 +406,23 @@ MQTTStatus_t decodeUtf8( const char ** pProperty, MQTTStatus_t decodeVariableLength( const uint8_t * pBuffer, size_t bufferLength, uint32_t * pLength ); + +/** + * @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] reasonCode Reason code for the ack packet. + * + * @return A pointer to the end of the encoded string. + */ +uint8_t * serializeAckFixed( uint8_t* pIndex, + uint8_t packetType, + uint16_t packetId, + size_t remainingLength, + MQTTSuccessFailReasonCode_t reasonCode ); + #endif