diff --git a/docs/doxygen/pages.dox b/docs/doxygen/pages.dox index 18b8cb235..5b2da625b 100644 --- a/docs/doxygen/pages.dox +++ b/docs/doxygen/pages.dox @@ -188,6 +188,9 @@ Some configuration settings are C pre-processor constants, and some are function @section MQTT_PINGRESP_TIMEOUT_MS @copydoc MQTT_PINGRESP_TIMEOUT_MS +@section MQTT_RECV_POLLING_TIMEOUT_MS +@copydoc MQTT_RECV_POLLING_TIMEOUT_MS + @section MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT @copydoc MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT diff --git a/lexicon.txt b/lexicon.txt index 7a9ad274d..955371872 100644 --- a/lexicon.txt +++ b/lexicon.txt @@ -174,6 +174,7 @@ mytcpsocketcontext mytlscontext networkbuffer networkcontext +networkinterfacereceivestub networkinterfacesendstub networkrecv networksend @@ -279,6 +280,7 @@ receivepacket recordcount recordindex recv +recvexact recvfunc reestablishment remaininglength diff --git a/source/core_mqtt.c b/source/core_mqtt.c index 90f629142..5cb08d77b 100644 --- a/source/core_mqtt.c +++ b/source/core_mqtt.c @@ -71,17 +71,25 @@ static uint32_t calculateElapsedTime( uint32_t later, static MQTTPubAckType_t getAckFromPacketType( uint8_t packetType ); /** - * @brief Receive bytes into the network buffer, with a timeout. + * @brief Receive bytes into the network buffer. * * @param[in] pContext Initialized MQTT Context. * @param[in] bytesToRecv Number of bytes to receive. - * @param[in] timeoutMs Time remaining to receive the packet. + * + * @note This operation calls the transport receive function + * repeatedly to read bytes from the network until either: + * 1. The requested number of bytes @a bytesToRecv are read. + * OR + * 2. No data is received from the network for MQTT_RECV_POLLING_TIMEOUT_MS duration. + * + * OR + * 3. There is an error in reading from the network. + * * * @return Number of bytes received, or negative number on network error. */ static int32_t recvExact( const MQTTContext_t * pContext, - size_t bytesToRecv, - uint32_t timeoutMs ); + size_t bytesToRecv ); /** * @brief Discard a packet from the transport interface. @@ -683,13 +691,12 @@ static MQTTPubAckType_t getAckFromPacketType( uint8_t packetType ) /*-----------------------------------------------------------*/ static int32_t recvExact( const MQTTContext_t * pContext, - size_t bytesToRecv, - uint32_t timeoutMs ) + size_t bytesToRecv ) { uint8_t * pIndex = NULL; size_t bytesRemaining = bytesToRecv; int32_t totalBytesRecvd = 0, bytesRecvd; - uint32_t entryTimeMs = 0U, elapsedTimeMs = 0U; + uint32_t lastDataRecvTimeMs = 0U, timeSinceLastRecvMs = 0U; TransportRecv_t recvFunc = NULL; MQTTGetCurrentTimeFunc_t getTimeStampMs = NULL; bool receiveError = false; @@ -704,7 +711,8 @@ static int32_t recvExact( const MQTTContext_t * pContext, recvFunc = pContext->transportInterface.recv; getTimeStampMs = pContext->getTime; - entryTimeMs = getTimeStampMs(); + /* Part of the MQTT packet has been read before calling this function. */ + lastDataRecvTimeMs = getTimeStampMs(); while( ( bytesRemaining > 0U ) && ( receiveError == false ) ) { @@ -719,8 +727,11 @@ static int32_t recvExact( const MQTTContext_t * pContext, totalBytesRecvd = bytesRecvd; receiveError = true; } - else + else if( bytesRecvd > 0 ) { + /* Reset the starting time as we have received some data from the network. */ + lastDataRecvTimeMs = getTimeStampMs(); + /* It is a bug in the application's transport receive implementation * if more bytes than expected are received. To avoid a possible * overflow in converting bytesRemaining from unsigned to signed, @@ -732,18 +743,20 @@ static int32_t recvExact( const MQTTContext_t * pContext, totalBytesRecvd += ( int32_t ) bytesRecvd; pIndex += bytesRecvd; LogDebug( ( "BytesReceived=%ld, BytesRemaining=%lu, " - "TotalBytesReceived=%ld.", ( long int ) bytesRecvd, - ( unsigned long ) bytesRemaining, - ( long int ) totalBytesRecvd ) ); + ( unsigned long ) bytesRemaining ) ); } - - elapsedTimeMs = calculateElapsedTime( getTimeStampMs(), entryTimeMs ); - - if( ( bytesRemaining > 0U ) && ( elapsedTimeMs >= timeoutMs ) ) + else { - LogError( ( "Time expired while receiving packet." ) ); - receiveError = true; + /* No bytes were read from the network. */ + timeSinceLastRecvMs = calculateElapsedTime( getTimeStampMs(), lastDataRecvTimeMs ); + + /* Check for timeout if we have been waiting to receive any byte on the network. */ + if( timeSinceLastRecvMs >= MQTT_RECV_POLLING_TIMEOUT_MS ) + { + LogError( ( "Time expired while receiving packet." ) ); + receiveError = true; + } } } @@ -760,7 +773,6 @@ static MQTTStatus_t discardPacket( const MQTTContext_t * pContext, int32_t bytesReceived = 0; size_t bytesToReceive = 0U; uint32_t totalBytesReceived = 0U, entryTimeMs = 0U, elapsedTimeMs = 0U; - uint32_t remainingTimeMs = timeoutMs; MQTTGetCurrentTimeFunc_t getTimeStampMs = NULL; bool receiveError = false; @@ -779,7 +791,7 @@ static MQTTStatus_t discardPacket( const MQTTContext_t * pContext, bytesToReceive = remainingLength - totalBytesReceived; } - bytesReceived = recvExact( pContext, bytesToReceive, remainingTimeMs ); + bytesReceived = recvExact( pContext, bytesToReceive ); if( bytesReceived != ( int32_t ) bytesToReceive ) { @@ -795,12 +807,8 @@ static MQTTStatus_t discardPacket( const MQTTContext_t * pContext, elapsedTimeMs = calculateElapsedTime( getTimeStampMs(), entryTimeMs ); - /* Update remaining time and check for timeout. */ - if( elapsedTimeMs < timeoutMs ) - { - remainingTimeMs = timeoutMs - elapsedTimeMs; - } - else + /* Check for timeout. */ + if( elapsedTimeMs >= timeoutMs ) { LogError( ( "Time expired while discarding packet." ) ); receiveError = true; @@ -846,7 +854,7 @@ static MQTTStatus_t receivePacket( const MQTTContext_t * pContext, else { bytesToReceive = incomingPacket.remainingLength; - bytesReceived = recvExact( pContext, bytesToReceive, remainingTimeMs ); + bytesReceived = recvExact( pContext, bytesToReceive ); if( bytesReceived == ( int32_t ) bytesToReceive ) { @@ -2167,7 +2175,7 @@ MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * pContext, elapsedTimeMs = calculateElapsedTime( pContext->getTime(), entryTimeMs ); - if( elapsedTimeMs > timeoutMs ) + if( elapsedTimeMs >= timeoutMs ) { break; } diff --git a/source/include/core_mqtt.h b/source/include/core_mqtt.h index f9c813ab6..2f0f0b005 100644 --- a/source/include/core_mqtt.h +++ b/source/include/core_mqtt.h @@ -603,6 +603,14 @@ MQTTStatus_t MQTT_Disconnect( MQTTContext_t * pContext ); * @param[in] timeoutMs Minimum time in milliseconds that the receive loop will * run, unless an error occurs. * + * @note Calling this function blocks the calling context for a time period that + * depends on the passed @p timeoutMs, the configuration macro, #MQTT_RECV_POLLING_TIMEOUT_MS, + * and the underlying transport interface implementation timeouts, unless an error + * occurs. + * Blocking Time = Max( timeoutMs parameter, + * MQTT_RECV_POLLING_TIMEOUT_MS, + * Transport interface send/recv implementation timeout ) + * * @return #MQTTBadParameter if context is NULL; * #MQTTRecvFailed if a network error occurs during reception; * #MQTTSendFailed if a network error occurs while sending an ACK or PINGREQ; @@ -655,6 +663,14 @@ MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * pContext, * @param[in] timeoutMs Minimum time in milliseconds that the receive loop will * run, unless an error occurs. * + * @note Calling this function blocks the calling context for a time period that + * depends on the passed @p timeoutMs, the configuration macro, #MQTT_RECV_POLLING_TIMEOUT_MS, + * and the underlying transport interface implementation timeouts, unless an error + * occurs. + * Blocking Time = Max( timeoutMs parameter, + * MQTT_RECV_POLLING_TIMEOUT_MS, + * Transport interface send/recv implementation timeout ) + * * @return #MQTTBadParameter if context is NULL; * #MQTTRecvFailed if a network error occurs during reception; * #MQTTSendFailed if a network error occurs while sending an ACK or PINGREQ; diff --git a/source/include/core_mqtt_config_defaults.h b/source/include/core_mqtt_config_defaults.h index 5b7c26c25..019c3ac71 100644 --- a/source/include/core_mqtt_config_defaults.h +++ b/source/include/core_mqtt_config_defaults.h @@ -108,6 +108,27 @@ #define MQTT_PINGRESP_TIMEOUT_MS ( 500U ) #endif +/** + * @brief The maximum duration between non-empty network reads while + * receiving an MQTT packet via the #MQTT_ProcessLoop or #MQTT_ReceiveLoop + * API functions. + * + * When an incoming MQTT packet is detected, the transport receive function + * may be called multiple times until all of the expected number of bytes of the + * packet are received. This timeout represents the maximum polling duration that + * is allowed without any data reception from the network for the incoming packet. + * If the timeout expires, the #MQTT_ProcessLoop and #MQTT_ReceiveLoop functions + * return #MQTTRecvFailed. + * + * Possible values: Any positive integer up to SIZE_MAX. Recommended to + * use a small timeout value.
+ * Default value: `10` + * + */ +#ifndef MQTT_RECV_POLLING_TIMEOUT_MS + #define MQTT_RECV_POLLING_TIMEOUT_MS ( 10U ) +#endif + /** * @brief Macro that is called in the MQTT library for logging "Error" level * messages. diff --git a/source/interface/transport_interface.h b/source/interface/transport_interface.h index c71991acf..a0beb30a5 100644 --- a/source/interface/transport_interface.h +++ b/source/interface/transport_interface.h @@ -163,7 +163,11 @@ typedef struct NetworkContext NetworkContext_t; * @param[in] pBuffer Buffer to receive the data into. * @param[in] bytesToRecv Number of bytes requested from the network. * - * @return The number of bytes received or a negative error code. + * @return The number of bytes received or a negative value to indicate + * error. + * @note If no data is available on the network to read and no error + * has occurred, zero MUST be the return value. Zero MUST NOT be used + * if a network disconnection has occurred. */ /* @[define_transportrecv] */ typedef int32_t ( * TransportRecv_t )( NetworkContext_t * pNetworkContext, diff --git a/test/cbmc/include/core_mqtt_config.h b/test/cbmc/include/core_mqtt_config.h index c8bb21699..16964e14d 100644 --- a/test/cbmc/include/core_mqtt_config.h +++ b/test/cbmc/include/core_mqtt_config.h @@ -70,4 +70,22 @@ struct NetworkContext */ #define MQTT_PINGRESP_TIMEOUT_MS ( 500U ) +/** + * @brief The maximum duration of receiving no data over network when + * attempting to read an incoming MQTT packet by the #MQTT_ProcessLoop or + * #MQTT_ReceiveLoop API functions. + * + * When an incoming MQTT packet is detected, the transport receive function + * may be called multiple times until all the expected number of bytes for the + * packet are received. This timeout represents the maximum duration of polling + * for any data to be received over the network for the incoming. + * If the timeout expires, the #MQTT_ProcessLoop or #MQTT_ReceiveLoop functions + * return #MQTTRecvFailed. + * + * This is set to 1 to exit right away after a zero is received in the transport + * receive stub. There is no added value, in proving memory safety, to repeat + * the logic that checks if the polling timeout is reached. + */ +#define MQTT_RECV_POLLING_TIMEOUT_MS ( 1U ) + #endif /* ifndef CORE_MQTT_CONFIG_H_ */ diff --git a/test/cbmc/proofs/MQTT_Connect/Makefile b/test/cbmc/proofs/MQTT_Connect/Makefile index 5e1bc80b6..05f867721 100644 --- a/test/cbmc/proofs/MQTT_Connect/Makefile +++ b/test/cbmc/proofs/MQTT_Connect/Makefile @@ -32,12 +32,17 @@ MAX_NETWORK_SEND_TRIES=3 # time out of 3 we can get coverage of the entire function. Another iteration # performed will unnecessarily duplicate the proof. MQTT_RECEIVE_TIMEOUT=3 +# The NetworkInterfaceReceiveStub is called once for getting the incoming packet +# type with one byte of data, then it is called multiple times to reveive the +# packet. +MAX_NETWORK_RECV_TRIES=4 # Please see test/cbmc/include/core_mqtt_config.h for more # information on these defines. MQTT_STATE_ARRAY_MAX_COUNT=11 MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT=3 DEFINES += -DMQTT_RECEIVE_TIMEOUT=$(MQTT_RECEIVE_TIMEOUT) DEFINES += -DMAX_NETWORK_SEND_TRIES=$(MAX_NETWORK_SEND_TRIES) +DEFINES += -DMAX_NETWORK_RECV_TRIES=$(MAX_NETWORK_RECV_TRIES) INCLUDES += # These functions do not coincide with the call graph of MQTT_Connect, but are @@ -54,10 +59,10 @@ REMOVE_FUNCTION_BODY += __CPROVER_file_local_core_mqtt_c_handleKeepAlive # function. REMOVE_FUNCTION_BODY += memcpy -# The loops below are unwound once more than the timeout. The loops below use +# The loop below is unwound once more than the timeout. The loop below uses # the user passed in timeout to break the loop. -UNWINDSET += __CPROVER_file_local_core_mqtt_c_recvExact.0:$(MQTT_RECEIVE_TIMEOUT) UNWINDSET += __CPROVER_file_local_core_mqtt_c_discardPacket.0:$(MQTT_RECEIVE_TIMEOUT) +UNWINDSET += __CPROVER_file_local_core_mqtt_c_recvExact.0:$(MAX_NETWORK_RECV_TRIES) # If the user passed in timeout is zero, then the loop will run until the # MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT is reached. UNWINDSET += __CPROVER_file_local_core_mqtt_c_receiveConnack.0:$(MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT) diff --git a/test/cbmc/proofs/MQTT_ProcessLoop/Makefile b/test/cbmc/proofs/MQTT_ProcessLoop/Makefile index d43e928f1..f221e4b6b 100644 --- a/test/cbmc/proofs/MQTT_ProcessLoop/Makefile +++ b/test/cbmc/proofs/MQTT_ProcessLoop/Makefile @@ -32,11 +32,16 @@ MQTT_RECEIVE_TIMEOUT=3 # Please see test/cbmc/stubs/network_interface_subs.c for # more information on MAX_NETWORK_SEND_TRIES. MAX_NETWORK_SEND_TRIES=3 +# The NetworkInterfaceReceiveStub is called once for getting the incoming packet +# type with one byte of data, then it is called multiple times to reveive the +# packet. +MAX_NETWORK_RECV_TRIES=4 # Please see test/cbmc/include/core_mqtt_config.h for more # information. MQTT_STATE_ARRAY_MAX_COUNT=11 DEFINES += -DMQTT_RECEIVE_TIMEOUT=$(MQTT_RECEIVE_TIMEOUT) DEFINES += -DMAX_NETWORK_SEND_TRIES=$(MAX_NETWORK_SEND_TRIES) +DEFINES += -DMAX_NETWORK_RECV_TRIES=$(MAX_NETWORK_RECV_TRIES) INCLUDES += # These functions have their memory saftey proven in other harnesses. @@ -46,7 +51,7 @@ REMOVE_FUNCTION_BODY += MQTT_SerializeAck UNWINDSET += MQTT_ProcessLoop.0:$(MQTT_RECEIVE_TIMEOUT) UNWINDSET += __CPROVER_file_local_core_mqtt_c_discardPacket.0:$(MQTT_RECEIVE_TIMEOUT) -UNWINDSET += __CPROVER_file_local_core_mqtt_c_recvExact.0:$(MQTT_RECEIVE_TIMEOUT) +UNWINDSET += __CPROVER_file_local_core_mqtt_c_recvExact.0:$(MAX_NETWORK_RECV_TRIES) # Unlike recvExact, sendPacket is not bounded by the timeout. The loop in # sendPacket will continue until all the bytes are sent or a network error # occurs. Please see NetworkInterfaceReceiveStub in diff --git a/test/cbmc/proofs/MQTT_ReceiveLoop/Makefile b/test/cbmc/proofs/MQTT_ReceiveLoop/Makefile index 4ae14c6af..f41d6a331 100644 --- a/test/cbmc/proofs/MQTT_ReceiveLoop/Makefile +++ b/test/cbmc/proofs/MQTT_ReceiveLoop/Makefile @@ -11,6 +11,10 @@ PROOF_UID=MQTT_ReceiveLoop # out of 2 we can get coverage of the entire function. Another iteration will # performed unnecessarily duplicating of the proof. MQTT_RECEIVE_TIMEOUT=3 +# The NetworkInterfaceReceiveStub is called once for getting the incoming packet +# type with one byte of data, then it is called multiple times to reveive the +# packet. +MAX_NETWORK_RECV_TRIES=4 # Please see test/cbmc/stubs/network_interface_subs.c for # more information on MAX_NETWORK_SEND_TRIES. MAX_NETWORK_SEND_TRIES=3 @@ -19,6 +23,7 @@ MAX_NETWORK_SEND_TRIES=3 MQTT_STATE_ARRAY_MAX_COUNT=11 DEFINES += -DMQTT_RECEIVE_TIMEOUT=$(MQTT_RECEIVE_TIMEOUT) DEFINES += -DMAX_NETWORK_SEND_TRIES=$(MAX_NETWORK_SEND_TRIES) +DEFINES += -DMAX_NETWORK_RECV_TRIES=$(MAX_NETWORK_RECV_TRIES) INCLUDES += # These functions have their memory saftey proven in other harnesses. @@ -28,7 +33,7 @@ REMOVE_FUNCTION_BODY += MQTT_SerializeAck # The loops below are unwound once more than the exclusive timeout bound. UNWINDSET += MQTT_ReceiveLoop.0:$(MQTT_RECEIVE_TIMEOUT) UNWINDSET += __CPROVER_file_local_core_mqtt_c_discardPacket.0:$(MQTT_RECEIVE_TIMEOUT) -UNWINDSET += __CPROVER_file_local_core_mqtt_c_recvExact.0:$(MQTT_RECEIVE_TIMEOUT) +UNWINDSET += __CPROVER_file_local_core_mqtt_c_recvExact.0:$(MAX_NETWORK_RECV_TRIES) # Unlike recvExact, sendPacket is not bounded by the timeout. The loop in # sendPacket will continue until all the bytes are sent or a network error # occurs. Please see NetworkInterfaceReceiveStub in diff --git a/test/cbmc/stubs/network_interface_stubs.c b/test/cbmc/stubs/network_interface_stubs.c index 6b042e9fe..cb182c42b 100644 --- a/test/cbmc/stubs/network_interface_stubs.c +++ b/test/cbmc/stubs/network_interface_stubs.c @@ -35,6 +35,13 @@ #define MAX_NETWORK_SEND_TRIES 3 #endif +/* An exclusive bound on the times that the NetworkInterfaceReceiveStub will + * return an unbound value. At this value and beyond, the + * NetworkInterfaceReceiveStub will return zero on every call. */ +#ifndef MAX_NETWORK_RECV_TRIES + #define MAX_NETWORK_RECV_TRIES 4 +#endif + int32_t NetworkInterfaceReceiveStub( NetworkContext_t * pNetworkContext, void * pBuffer, size_t bytesToRecv ) @@ -48,11 +55,21 @@ int32_t NetworkInterfaceReceiveStub( NetworkContext_t * pNetworkContext, __CPROVER_havoc_object( pBuffer ); int32_t bytesOrError; + static size_t tries = 0; /* It is a bug for the application defined transport send function to return * more than bytesToRecv. */ __CPROVER_assume( bytesOrError <= ( int32_t ) bytesToRecv ); + if( tries < ( MAX_NETWORK_RECV_TRIES - 1 ) ) + { + tries++; + } + else + { + bytesOrError = 0; + } + return bytesOrError; } diff --git a/test/unit-test/core_mqtt_utest.c b/test/unit-test/core_mqtt_utest.c index cf8fd922d..fb96fd6ca 100644 --- a/test/unit-test/core_mqtt_utest.c +++ b/test/unit-test/core_mqtt_utest.c @@ -132,6 +132,7 @@ typedef struct ProcessLoopReturns 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; /** @@ -365,6 +366,20 @@ static int32_t transportRecvOneByte( NetworkContext_t * pNetworkContext, return 1; } +/** + * @brief Mocked transport returning zero bytes to simulate reception + * of no data over network. + */ +static int32_t transportRecvNoData( NetworkContext_t * pNetworkContext, + void * pBuffer, + size_t bytesToRead ) +{ + ( void ) pNetworkContext; + ( void ) pBuffer; + ( void ) bytesToRead; + return 0; +} + /** * @brief Initialize the transport interface with the mocked functions for * send and receive. @@ -405,6 +420,7 @@ static void resetProcessLoopParams( ProcessLoopReturns_t * pExpectParams ) pExpectParams->processLoopStatus = MQTTSuccess; pExpectParams->incomingPublish = false; pExpectParams->pPubInfo = NULL; + pExpectParams->timeoutMs = MQTT_NO_TIMEOUT_MS; } /** @@ -548,7 +564,7 @@ static void expectProcessLoopCalls( MQTTContext_t * const pContext, } /* Expect the above calls when running MQTT_ProcessLoop. */ - mqttStatus = MQTT_ProcessLoop( pContext, MQTT_NO_TIMEOUT_MS ); + mqttStatus = MQTT_ProcessLoop( pContext, pExpectParams->timeoutMs ); TEST_ASSERT_EQUAL( processLoopStatus, mqttStatus ); /* Any final assertions to end the test. */ @@ -853,7 +869,7 @@ void test_MQTT_Connect_partial_receive() setupTransportInterface( &transport ); setupNetworkBuffer( &networkBuffer ); - transport.recv = transportRecvOneByte; + transport.recv = transportRecvNoData; memset( &mqttContext, 0x0, sizeof( mqttContext ) ); MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer ); @@ -864,14 +880,16 @@ void test_MQTT_Connect_partial_receive() incomingPacket.type = MQTT_PACKET_TYPE_CONNACK; incomingPacket.remainingLength = 2; - /* Not enough time to receive entire packet, for branch coverage. This is due - * to the fact the mocked receive function reads only one byte at a time. */ - timeout = 1; + /* Timeout in receiving entire packet, for branch coverage. This is due to the fact that the mocked + * receive function always returns 0 bytes read. */ MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); TEST_ASSERT_EQUAL_INT( MQTTRecvFailed, status ); + /* Update to use mock receive function that receives one byte at a time for the + * rest of the test. */ + mqttContext.transportInterface.recv = transportRecvOneByte; timeout = 10; /* Not enough space for packet, discard it. */ @@ -1573,6 +1591,53 @@ void test_MQTT_ProcessLoop_handleIncomingPublish_Error_Paths( void ) TEST_ASSERT_FALSE( isEventCallbackInvoked ); } +/** + * @brief This test checks that the ProcessLoop API function is able to + * support receiving an entire incoming MQTT packet over the network when + * the transport recv function only reads less than requested bytes at a + * time, and the timeout passed to the API is "0ms". + */ +void test_MQTT_ProcessLoop_Zero_Duration_And_Partial_Network_Read( void ) +{ + MQTTStatus_t mqttStatus; + MQTTContext_t context; + TransportInterface_t transport; + MQTTFixedBuffer_t networkBuffer; + ProcessLoopReturns_t expectParams = { 0 }; + + setupNetworkBuffer( &networkBuffer ); + + transport.send = transportSendSuccess; + + /* Set the transport recv function for the test to the mock function that represents + * partial read of data from network (i.e. less than requested number of bytes) + * at a time. */ + transport.recv = transportRecvOneByte; + + /* Initialize the context. */ + mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + + /* Set flag required for configuring behavior of expectProcessLoopCalls() + * helper function. */ + modifyIncomingPacketStatus = MQTTSuccess; + + /* Test the ProcessLoop() call with zero duration timeout to verify that it + * will be able to support reading the packet over network over multiple calls to + * the transport receive function. */ + expectParams.timeoutMs = MQTT_NO_TIMEOUT_MS; + + /* Test with an incoming PUBLISH packet whose payload is read only one byte + * per call to the transport recv function. */ + currentPacketType = MQTT_PACKET_TYPE_PUBLISH; + /* Set expected return values during the loop. */ + resetProcessLoopParams( &expectParams ); + expectParams.stateAfterDeserialize = MQTTPubAckSend; + expectParams.stateAfterSerialize = MQTTPublishDone; + expectParams.incomingPublish = true; + expectProcessLoopCalls( &context, &expectParams ); +} + /** * @brief This test case covers all calls to the private method, * handleIncomingAck(...),