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(...),