Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix: Fix interrupted network read operation #120

Merged
merged 34 commits into from
Dec 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
5ec1c2f
Fix issue of data recv being interrupted
aggarw13 Dec 3, 2020
4205109
Rename elapsedTimeMs variable to its express its new meaning
aggarw13 Dec 3, 2020
0704c72
Use configuration const for recvExact timeout
aggarw13 Dec 3, 2020
adce766
Remove timeout check from discardPacket and address CI check failures
aggarw13 Dec 3, 2020
f5aa1b3
Fix more CI check failures
aggarw13 Dec 3, 2020
76166d0
Remove another unused local variable
aggarw13 Dec 3, 2020
1d73bb7
Re-instate timeout in discard to reduce scope of changes
aggarw13 Dec 3, 2020
0c5a9e3
Remove unused variable again
aggarw13 Dec 3, 2020
34adc86
Fix failing unit test
aggarw13 Dec 3, 2020
2a3cc9a
Rename new config macro, and attempt to fix CBMC failures
aggarw13 Dec 3, 2020
70b181a
Doc: Improvement suggestions from code review
aggarw13 Dec 3, 2020
68fcb16
Fix quality check failures
aggarw13 Dec 3, 2020
4ba8c53
Add test case to check partial network reads with zero timeout durati…
aggarw13 Dec 3, 2020
b710790
style: Improving naming
aggarw13 Dec 3, 2020
f8586dc
Address complexity failure
aggarw13 Dec 3, 2020
c59d39b
Address comments
aggarw13 Dec 4, 2020
af1ca02
Doc: Add blocking time equation of Receive/ProcessLoop functions in t…
aggarw13 Dec 4, 2020
86798bf
Improvement in API doc
aggarw13 Dec 4, 2020
e1de841
Set MQTT_RECV_POLLING_TIMEOUT_MS so that recvExact runs in one iterat…
sarenameas Dec 4, 2020
9a792bc
Merge branch 'main' into fix/recv-timeout-issue
aggarw13 Dec 4, 2020
ebcfb92
doc: Add information about zero return value for Transport_Recv_t
aggarw13 Dec 4, 2020
fe0a071
Merge branch 'fix/recv-timeout-issue' of https://github.com/aggarw13/…
aggarw13 Dec 4, 2020
32f0291
fix: prevent possibility of infinite loop in timeout logic of Process…
aggarw13 Dec 4, 2020
dd91637
Merge branch 'main' into fix/recv-timeout-issue
aggarw13 Dec 4, 2020
a4bf796
Merge branch 'main' into fix/recv-timeout-issue
aggarw13 Dec 4, 2020
bf6f94d
style: Minor changes
aggarw13 Dec 4, 2020
092e7ff
Merge branch 'fix/recv-timeout-issue' of https://github.com/aggarw13/…
aggarw13 Dec 4, 2020
f0a1808
hygiene: minor name fix
aggarw13 Dec 4, 2020
fcaf4bb
fix: Add additional check in timeout logic of recvExact
aggarw13 Dec 4, 2020
b923ea5
fix: Add additional check on recvExact
aggarw13 Dec 4, 2020
b1eba61
hygiene: move timeout logic within else block for better readability
aggarw13 Dec 4, 2020
25e74d6
hygiene: remove always true condition from if block
aggarw13 Dec 4, 2020
b4b0c78
Update CBMC proofs that use recvExact to specify a transport recv stu…
sarenameas Dec 5, 2020
95abb93
Uncrustify.:
sarenameas Dec 5, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/doxygen/pages.dox
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

@yourslab yourslab Dec 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the doc for MQTT_Init, we state the following:

If there is no time implementation,
it is the responsibility of the application to provide a dummy function to always return 0

We need to update this to say that MQTT_RECV_POLLING_TIMEOUT_MS must also be set to 0 to prevent an infinite loop.

However, this is now insufficient as one iteration of MQTT_ReceiveLoop or MQTT_ProcessLoop may not be enough to receive the entire payload. The way around that is to set MQTT_RECV_POLLING_TIMEOUT_MS to a desired number of receive iterations, then pass a timer query function that increments on each call. However, that contradicts labeling the unit of time as milliseconds, but why does the timeout need to be in milliseconds here anyway?

Copy link
Contributor Author

@aggarw13 aggarw13 Dec 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that setting the timer function to a value that returns 0 can result in an infinite loop for the MQTT_ProcessLoop function as this is the condition used for determining that the timeout period has expired:

if( elapsedTimeMs > timeoutMs )
{
                break;
}

We should change that condition to be >= instead...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The goal is to allow unbound number of retries for partial reads of packet data over the network as long as the transport recv function is able to successfully read data over the network. (Reason is that the recvExact function is called after some part of the MQTT packet is already read, and thus, we want to attempt to read the remaining packet data as long as the transport recv function does not return an error).
Thus, even if a dummy timer function that always returns 0 is used, as long as the transport receive function is able to perform partial reads, the transport recv calls will be retried.

If a dummy timer that always returns 0 is used and the network read returns 0 bytes, there still wouldn't be an infinite loop inside the recvExact function as it uses the >= check for the timeout.

Copy link
Contributor

@muneebahmed10 muneebahmed10 Dec 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should change that condition to be >= instead...

That definitely looks like a typo as it is now. MQTT_ReceiveLoop uses >=, which looks correct

Copy link
Contributor Author

@aggarw13 aggarw13 Dec 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have fixed it for MQTT_ProcessLoop in 32f0291


@section MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT
@copydoc MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT

Expand Down
2 changes: 2 additions & 0 deletions lexicon.txt
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ mytcpsocketcontext
mytlscontext
networkbuffer
networkcontext
networkinterfacereceivestub
networkinterfacesendstub
networkrecv
networksend
Expand Down Expand Up @@ -279,6 +280,7 @@ receivepacket
recordcount
recordindex
recv
recvexact
recvfunc
reestablishment
remaininglength
Expand Down
64 changes: 36 additions & 28 deletions source/core_mqtt.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Due to the removal of this parameter, some more timeouts could probably be removed due to similar reasoning. I don't think it's critical though and definitely shouldn't be done in this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I agree that the timeout passed to discardPacket and the timeout logic in the function can also be removed (as hygiene). However, I decided to keep the scope of this PR limited to just fixing the recvExact issue.

size_t bytesToRecv );

/**
* @brief Discard a packet from the transport interface.
Expand Down Expand Up @@ -683,13 +691,12 @@ static MQTTPubAckType_t getAckFromPacketType( uint8_t packetType )
/*-----------------------------------------------------------*/

static int32_t recvExact( const MQTTContext_t * pContext,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

static int32_t recvExact( const MQTTContext_t * pContext,
                          size_t bytesToRecv )
{
    uint8_t * pIndex = NULL;
    size_t bytesRemaining = bytesToRecv;
    int32_t totalBytesRecvd = 0, bytesRecvd;
    uint32_t timestampBeforeRecvMs = 0U, timeSpentInRecvMs = 0U;
    TransportRecv_t recvFunc = NULL;
    MQTTGetCurrentTimeFunc_t getTimeStampMs = NULL;
    bool receiveError = false;

    assert( pContext != NULL );
    assert( bytesToRecv <= pContext->networkBuffer.size );
    assert( pContext->getTime != NULL );
    assert( pContext->transportInterface.recv != NULL );
    assert( pContext->networkBuffer.pBuffer != NULL );

    pIndex = pContext->networkBuffer.pBuffer;
    recvFunc = pContext->transportInterface.recv;
    getTimeStampMs = pContext->getTime;

    while( ( bytesRemaining > 0U ) && ( receiveError == false ) )
    {
        timestampBeforeRecvMs = getTimeStampMs();

        bytesRecvd = recvFunc( pContext->transportInterface.pNetworkContext,
                               pIndex,
                               bytesRemaining );

        timeSpentInRecvMs = calculateElapsedTime( getTimeStampMs(), timestampBeforeRecvMs );

        if( bytesRecvd < 0 )
        {
            LogError( ( "Network error while receiving packet: ReturnCode=%ld.",
                        ( long int ) bytesRecvd ) );
            totalBytesRecvd = bytesRecvd;
            receiveError = true;
        }
        else if( bytesRecvd > 0 )
        {
            /* 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,
             * this assert must exist after the check for bytesRecvd being
             * negative. */
            assert( ( size_t ) bytesRecvd <= bytesRemaining );

            bytesRemaining -= ( size_t ) bytesRecvd;
            totalBytesRecvd += ( int32_t ) bytesRecvd;
            pIndex += bytesRecvd;
            LogDebug( ( "BytesReceived=%ld, BytesRemaining=%lu, "
                        "TotalBytesReceived=%ld.",
                        ( long int ) bytesRecvd,
                        ( unsigned long ) bytesRemaining,
                        ( long int ) totalBytesRecvd ) );
        }
        else
        {
            /* No bytes were read from the network. */
        }

        /* If there is more data to be received and nothing was received
         * for MQTT_RECV_POLLING_TIMEOUT_MS, treat this as error. */
        if( ( bytesRemaining > 0U ) &&
            ( timeSpentInRecvMs >= MQTT_RECV_POLLING_TIMEOUT_MS ) &&
            ( bytesRecvd == 0 ) )
        {
            LogError( ( "Time expired while receiving packet." ) );
            receiveError = true;
        }
    }

    return totalBytesRecvd;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will only calculate the time spent for each call of recvFunc. Wouldn't there be an infinite loop if every recvFunc call returned 0 bytes but spent slightly less than MQTT_RECV_POLLING_TIMEOUT_MS?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In your code suggestion, timeSpentInRecvMs represents the time spent only in a single transport recv call, where as my changes look at the time spent across multiple calls to transport recv function that return zero bytes to determine if there has been a polling timeout.
With your code suggestion, there can be an issue of getting into an infinite loop of the transport recv function returning zero bytes, but the underlying SOCKET_TIMEOUT of transport recv being < MQTT_RECV_POLLING_TIMEOUT_MS, which will prevent the receiveError from being set to true and thus, the loop will not terminate

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. In that case, changing the variable names would make it a bit more readable:

entryTimeMs  --> lastDataRecvdTimestampMs
noDataRecvdTimeMs  --> timeSinceLastDataWasRecvd

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;
Expand All @@ -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 ) )
{
Expand All @@ -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,
Expand All @@ -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;
}
}
}

Expand All @@ -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;

Expand All @@ -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 )
{
Expand All @@ -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;
Expand Down Expand Up @@ -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 )
{
Expand Down Expand Up @@ -2167,7 +2175,7 @@ MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * pContext,
elapsedTimeMs = calculateElapsedTime( pContext->getTime(),
entryTimeMs );

if( elapsedTimeMs > timeoutMs )
if( elapsedTimeMs >= timeoutMs )
{
break;
}
Expand Down
16 changes: 16 additions & 0 deletions source/include/core_mqtt.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
21 changes: 21 additions & 0 deletions source/include/core_mqtt_config_defaults.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
* <b>Possible values:</b> Any positive integer up to SIZE_MAX. Recommended to
* use a small timeout value. <br>
* <b>Default value:</b> `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.
Expand Down
6 changes: 5 additions & 1 deletion source/interface/transport_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
18 changes: 18 additions & 0 deletions test/cbmc/include/core_mqtt_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_ */
9 changes: 7 additions & 2 deletions test/cbmc/proofs/MQTT_Connect/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down
7 changes: 6 additions & 1 deletion test/cbmc/proofs/MQTT_ProcessLoop/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down
7 changes: 6 additions & 1 deletion test/cbmc/proofs/MQTT_ReceiveLoop/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -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
Expand Down
17 changes: 17 additions & 0 deletions test/cbmc/stubs/network_interface_stubs.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 )
Expand All @@ -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;
}

Expand Down
Loading