Skip to content

Commit f1827d8

Browse files
DakshitBabbarDakshit Babbaractions-userAniruddhaKanhere
authored
Enabling Unclean Session Publish Re-Transmits (FreeRTOS#308)
<!--- Title --> Description ----------- <!--- Describe your changes in detail. --> This PR enables the coreMQTT library to resend unacked publishes on an unclean session connection. Following is a brief summary of changes: 1. Add a new API `MQTT_InitRetransmits` that will initialise the context to handle publish retransmits on an unclean session connection 2. Add signatures of callback function pointers that users will define in order to: a. copy and store outgoing publishes b. retrieve copied publish on an unclean session connection to resend c. clear a copied publish when a `PUBACK`/`PUBREC` is received d. clear all copied publishes on a clean session connection 3. Update the API's to check if callback's are defined and implement resend publishes as required. Following are the specifics of the changes: 1. Add 3 new MQTTStatus_t values: MQTTPublishStoreFailed, MQTTPublishRetrieveFailed and MQTTPublishClearAllFailed 2. Update `MQTTContext_t` to hold the callback function pointers a. `MQTTRetransmitStorePacket storeFunction` b. `MQTTRetransmitRetrievePacket retrieveFunction` c. `MQTTRetransmitClearPacket clearFunction` d. `MQTTRetransmitClearAllPackets clearAllFunction` 3. Update the `MQTT_Status_strerror` function to handle the new `MQTTStatus_t` values 4. Add a new API function `MQTT_InitRetransmits` that will initialise the new callback functions in the `MQTTContext_t` 5. Add this API to the core_mqtt.h file to make it available to users 6. Modify `MQTT_Publish` a. copy the outgoing publish packet in form of an array of `TransportOutVector_t` if the callback if defined b. if copy fails then bubble up corresponding error status code 7. Modify `MQTT_ReceiveLoop` a. on receiving a `PUBACK`/`PUBREC` clear the copy of that particular publish after the state of the publish record has been successfully updated, if the callback if defined 8. Modify `MQTT_Connect` a. on a clean session clear all the copies of publishes stored if the callback is defined b. if clear all fails then bubble up corresponding error status code c. on an unclean session get the packetID of the unacked publishes and retrieve the copies of those if the callback is defined d. if retrieve fails then bubble up corresponding error status code Approaches Taken --------------- - To let user know about the changes we have made we will add them to a changelog and have a minor version bump - To be in line with the zero copy principle in our library we chose to provide and retrieve the publish packets for storing and resending in form of an array of `TransportOutVector_t` - Code is written in a way that on receiving a `PUBACK`/`PUBREC` the copy will be cleared after the state of the publish record is changed so that if state update fails the copy won't be cleared. Otherwise if the state does not change and the copy is cleared then when a connection is made with an unclean session there will be a retrieve fail as the system is in an inconsistent state. - We are storing the copies of the publishes with the Duplicate flag set this is because on retrieving the packet we will get it in the form of a `TransportOutVector_t` that holds the data in a `const` pointer which cannot be changed after retrieving. Pending Tasks --------------- - [ ] Changelog - [ ] Minor version bump - [x] Doxygen example for the new API - [x] Better API Names - [x] Unit Test Updates - [x] CBMC Proof --------- Co-authored-by: Dakshit Babbar <[email protected]> Co-authored-by: GitHub Action <[email protected]> Co-authored-by: AniruddhaKanhere <[email protected]>
1 parent 86fc7d1 commit f1827d8

File tree

8 files changed

+1391
-77
lines changed

8 files changed

+1391
-77
lines changed

.github/.cSpellWords.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ DLIBRARY
2020
DNDEBUG
2121
DUNITTEST
2222
DUNITY
23+
getbytesinmqttvec
2324
getpacketid
2425
isystem
2526
lcov
@@ -34,6 +35,7 @@ NONDET
3435
pylint
3536
pytest
3637
pyyaml
38+
serializemqttvec
3739
sinclude
3840
UNACKED
3941
unpadded

docs/doxygen/include/size_table.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
</tr>
1010
<tr>
1111
<td>core_mqtt.c</td>
12-
<td><center>4.4K</center></td>
13-
<td><center>3.8K</center></td>
12+
<td><center>4.9K</center></td>
13+
<td><center>4.2K</center></td>
1414
</tr>
1515
<tr>
1616
<td>core_mqtt_state.c</td>
@@ -19,12 +19,12 @@
1919
</tr>
2020
<tr>
2121
<td>core_mqtt_serializer.c</td>
22-
<td><center>2.8K</center></td>
23-
<td><center>2.2K</center></td>
22+
<td><center>2.9K</center></td>
23+
<td><center>2.3K</center></td>
2424
</tr>
2525
<tr>
2626
<td><b>Total estimates</b></td>
27-
<td><b><center>8.9K</center></b></td>
28-
<td><b><center>7.3K</center></b></td>
27+
<td><b><center>9.5K</center></b></td>
28+
<td><b><center>7.8K</center></b></td>
2929
</tr>
3030
</table>

source/core_mqtt.c

Lines changed: 200 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@
9090
*/
9191
#define CORE_MQTT_UNSUBSCRIBE_PER_TOPIC_VECTOR_LENGTH ( 2U )
9292

93+
struct MQTTVec
94+
{
95+
TransportOutVector_t * pVector; /**< Pointer to transport vector. USER SHOULD NOT ACCESS THIS DIRECTLY - IT IS AN INTERNAL DETAIL AND CAN CHANGE. */
96+
size_t vectorLen; /**< Length of the transport vector. USER SHOULD NOT ACCESS THIS DIRECTLY - IT IS AN INTERNAL DETAIL AND CAN CHANGE. */
97+
};
98+
9399
/*-----------------------------------------------------------*/
94100

95101
/**
@@ -444,8 +450,10 @@ static MQTTStatus_t handleUncleanSessionResumption( MQTTContext_t * pContext );
444450
* @brief Clears existing state records for a clean session.
445451
*
446452
* @param[in] pContext Initialized MQTT context.
453+
*
454+
* @return #MQTTSuccess always otherwise.
447455
*/
448-
static void handleCleanSession( MQTTContext_t * pContext );
456+
static MQTTStatus_t handleCleanSession( MQTTContext_t * pContext );
449457

450458
/**
451459
* @brief Send the publish packet without copying the topic string and payload in
@@ -463,7 +471,7 @@ static void handleCleanSession( MQTTContext_t * pContext );
463471
*/
464472
static MQTTStatus_t sendPublishWithoutCopy( MQTTContext_t * pContext,
465473
const MQTTPublishInfo_t * pPublishInfo,
466-
const uint8_t * pMqttHeader,
474+
uint8_t * pMqttHeader,
467475
size_t headerSize,
468476
uint16_t packetId );
469477

@@ -1597,6 +1605,15 @@ static MQTTStatus_t handlePublishAcks( MQTTContext_t * pContext,
15971605
}
15981606
}
15991607

1608+
if( ( ackType == MQTTPuback ) || ( ackType == MQTTPubrec ) )
1609+
{
1610+
if( ( status == MQTTSuccess ) &&
1611+
( pContext->clearFunction != NULL ) )
1612+
{
1613+
pContext->clearFunction( pContext, packetIdentifier );
1614+
}
1615+
}
1616+
16001617
if( status == MQTTSuccess )
16011618
{
16021619
/* Set fields of deserialized struct. */
@@ -2133,13 +2150,14 @@ static MQTTStatus_t sendUnsubscribeWithoutCopy( MQTTContext_t * pContext,
21332150

21342151
static MQTTStatus_t sendPublishWithoutCopy( MQTTContext_t * pContext,
21352152
const MQTTPublishInfo_t * pPublishInfo,
2136-
const uint8_t * pMqttHeader,
2153+
uint8_t * pMqttHeader,
21372154
size_t headerSize,
21382155
uint16_t packetId )
21392156
{
21402157
MQTTStatus_t status = MQTTSuccess;
21412158
size_t ioVectorLength;
21422159
size_t totalMessageLength;
2160+
bool dupFlagChanged = false;
21432161

21442162
/* Bytes required to encode the packet ID in an MQTT header according to
21452163
* the MQTT specification. */
@@ -2190,7 +2208,42 @@ static MQTTStatus_t sendPublishWithoutCopy( MQTTContext_t * pContext,
21902208
totalMessageLength += pPublishInfo->payloadLength;
21912209
}
21922210

2193-
if( sendMessageVector( pContext, pIoVector, ioVectorLength ) != ( int32_t ) totalMessageLength )
2211+
/* If not already set, set the dup flag before storing a copy of the publish
2212+
* this is because on retrieving back this copy we will get it in the form of an
2213+
* array of TransportOutVector_t that holds the data in a const pointer which cannot be
2214+
* changed after retrieving. */
2215+
if( pPublishInfo->dup != true )
2216+
{
2217+
MQTT_UpdateDuplicatePublishFlag( pMqttHeader, true );
2218+
2219+
dupFlagChanged = true;
2220+
}
2221+
2222+
/* store a copy of the publish for retransmission purposes */
2223+
if( ( pPublishInfo->qos > MQTTQoS0 ) &&
2224+
( pContext->storeFunction != NULL ) )
2225+
{
2226+
MQTTVec_t mqttVec;
2227+
2228+
mqttVec.pVector = pIoVector;
2229+
mqttVec.vectorLen = ioVectorLength;
2230+
2231+
if( pContext->storeFunction( pContext, packetId, &mqttVec ) != true )
2232+
{
2233+
status = MQTTPublishStoreFailed;
2234+
}
2235+
}
2236+
2237+
/* change the value of the dup flag to its original, if it was changed */
2238+
if( dupFlagChanged )
2239+
{
2240+
MQTT_UpdateDuplicatePublishFlag( pMqttHeader, false );
2241+
2242+
dupFlagChanged = false;
2243+
}
2244+
2245+
if( ( status == MQTTSuccess ) &&
2246+
( sendMessageVector( pContext, pIoVector, ioVectorLength ) != ( int32_t ) totalMessageLength ) )
21942247
{
21952248
status = MQTTSendFailed;
21962249
}
@@ -2477,6 +2530,8 @@ static MQTTStatus_t handleUncleanSessionResumption( MQTTContext_t * pContext )
24772530
MQTTStateCursor_t cursor = MQTT_STATE_CURSOR_INITIALIZER;
24782531
uint16_t packetId = MQTT_PACKET_ID_INVALID;
24792532
MQTTPublishState_t state = MQTTStateNull;
2533+
size_t totalMessageLength;
2534+
uint8_t * pMqttPacket;
24802535

24812536
assert( pContext != NULL );
24822537

@@ -2492,17 +2547,71 @@ static MQTTStatus_t handleUncleanSessionResumption( MQTTContext_t * pContext )
24922547
packetId = MQTT_PubrelToResend( pContext, &cursor, &state );
24932548
}
24942549

2550+
if( ( status == MQTTSuccess ) &&
2551+
( pContext->retrieveFunction != NULL ) )
2552+
{
2553+
cursor = MQTT_STATE_CURSOR_INITIALIZER;
2554+
2555+
/* Resend all the PUBLISH for which PUBACK/PUBREC is not received
2556+
* after session is reestablished. */
2557+
do
2558+
{
2559+
packetId = MQTT_PublishToResend( pContext, &cursor );
2560+
2561+
if( packetId != MQTT_PACKET_ID_INVALID )
2562+
{
2563+
if( pContext->retrieveFunction( pContext, packetId, &pMqttPacket, &totalMessageLength ) != true )
2564+
{
2565+
status = MQTTPublishRetrieveFailed;
2566+
break;
2567+
}
2568+
2569+
MQTT_PRE_STATE_UPDATE_HOOK( pContext );
2570+
2571+
if( sendBuffer( pContext, pMqttPacket, totalMessageLength ) != ( int32_t ) totalMessageLength )
2572+
{
2573+
status = MQTTSendFailed;
2574+
}
2575+
2576+
MQTT_POST_STATE_UPDATE_HOOK( pContext );
2577+
}
2578+
} while( ( packetId != MQTT_PACKET_ID_INVALID ) &&
2579+
( status == MQTTSuccess ) );
2580+
}
2581+
24952582
return status;
24962583
}
24972584

2498-
static void handleCleanSession( MQTTContext_t * pContext )
2585+
static MQTTStatus_t handleCleanSession( MQTTContext_t * pContext )
24992586
{
2587+
MQTTStatus_t status = MQTTSuccess;
2588+
MQTTStateCursor_t cursor = MQTT_STATE_CURSOR_INITIALIZER;
2589+
uint16_t packetId = MQTT_PACKET_ID_INVALID;
2590+
25002591
assert( pContext != NULL );
25012592

25022593
/* Reset the index and clear the buffer when a new session is established. */
25032594
pContext->index = 0;
25042595
( void ) memset( pContext->networkBuffer.pBuffer, 0, pContext->networkBuffer.size );
25052596

2597+
if( pContext->clearFunction != NULL )
2598+
{
2599+
cursor = MQTT_STATE_CURSOR_INITIALIZER;
2600+
2601+
/* Resend all the PUBLISH for which PUBACK/PUBREC is not received
2602+
* after session is reestablished. */
2603+
do
2604+
{
2605+
packetId = MQTT_PublishToResend( pContext, &cursor );
2606+
2607+
if( packetId != MQTT_PACKET_ID_INVALID )
2608+
{
2609+
pContext->clearFunction( pContext, packetId );
2610+
}
2611+
} while( ( packetId != MQTT_PACKET_ID_INVALID ) &&
2612+
( status == MQTTSuccess ) );
2613+
}
2614+
25062615
if( pContext->outgoingPublishRecordMaxCount > 0U )
25072616
{
25082617
/* Clear any existing records if a new session is established. */
@@ -2517,6 +2626,8 @@ static void handleCleanSession( MQTTContext_t * pContext )
25172626
0x00,
25182627
pContext->incomingPublishRecordMaxCount * sizeof( *pContext->incomingPublishRecords ) );
25192628
}
2629+
2630+
return status;
25202631
}
25212632

25222633
static MQTTStatus_t validatePublishParams( const MQTTContext_t * pContext,
@@ -2681,6 +2792,46 @@ MQTTStatus_t MQTT_InitStatefulQoS( MQTTContext_t * pContext,
26812792

26822793
/*-----------------------------------------------------------*/
26832794

2795+
MQTTStatus_t MQTT_InitRetransmits( MQTTContext_t * pContext,
2796+
MQTTStorePacketForRetransmit storeFunction,
2797+
MQTTRetrievePacketForRetransmit retrieveFunction,
2798+
MQTTClearPacketForRetransmit clearFunction )
2799+
{
2800+
MQTTStatus_t status = MQTTSuccess;
2801+
2802+
if( pContext == NULL )
2803+
{
2804+
LogError( ( "Argument cannot be NULL: pContext=%p\n",
2805+
( void * ) pContext ) );
2806+
status = MQTTBadParameter;
2807+
}
2808+
else if( storeFunction == NULL )
2809+
{
2810+
LogError( ( "Invalid parameter: storeFunction is NULL" ) );
2811+
status = MQTTBadParameter;
2812+
}
2813+
else if( retrieveFunction == NULL )
2814+
{
2815+
LogError( ( "Invalid parameter: retrieveFunction is NULL" ) );
2816+
status = MQTTBadParameter;
2817+
}
2818+
else if( clearFunction == NULL )
2819+
{
2820+
LogError( ( "Invalid parameter: clearFunction is NULL" ) );
2821+
status = MQTTBadParameter;
2822+
}
2823+
else
2824+
{
2825+
pContext->storeFunction = storeFunction;
2826+
pContext->retrieveFunction = retrieveFunction;
2827+
pContext->clearFunction = clearFunction;
2828+
}
2829+
2830+
return status;
2831+
}
2832+
2833+
/*-----------------------------------------------------------*/
2834+
26842835
MQTTStatus_t MQTT_CancelCallback( const MQTTContext_t * pContext,
26852836
uint16_t packetId )
26862837
{
@@ -2820,7 +2971,7 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * pContext,
28202971

28212972
if( ( status == MQTTSuccess ) && ( *pSessionPresent != true ) )
28222973
{
2823-
handleCleanSession( pContext );
2974+
status = handleCleanSession( pContext );
28242975
}
28252976

28262977
if( status == MQTTSuccess )
@@ -2837,7 +2988,7 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * pContext,
28372988

28382989
if( ( status == MQTTSuccess ) && ( *pSessionPresent == true ) )
28392990
{
2840-
/* Resend PUBRELs when reestablishing a session */
2991+
/* Resend PUBRELs and PUBLISHES when reestablishing a session */
28412992
status = handleUncleanSessionResumption( pContext );
28422993
}
28432994

@@ -3560,6 +3711,14 @@ const char * MQTT_Status_strerror( MQTTStatus_t status )
35603711
str = "MQTTStatusDisconnectPending";
35613712
break;
35623713

3714+
case MQTTPublishStoreFailed:
3715+
str = "MQTTPublishStoreFailed";
3716+
break;
3717+
3718+
case MQTTPublishRetrieveFailed:
3719+
str = "MQTTPublishRetrieveFailed";
3720+
break;
3721+
35633722
default:
35643723
str = "Invalid MQTT Status code";
35653724
break;
@@ -3569,3 +3728,37 @@ const char * MQTT_Status_strerror( MQTTStatus_t status )
35693728
}
35703729

35713730
/*-----------------------------------------------------------*/
3731+
3732+
size_t MQTT_GetBytesInMQTTVec( MQTTVec_t * pVec )
3733+
{
3734+
size_t memoryRequired = 0;
3735+
size_t i;
3736+
TransportOutVector_t * pTransportVec = pVec->pVector;
3737+
size_t vecLen = pVec->vectorLen;
3738+
3739+
for( i = 0; i < vecLen; i++ )
3740+
{
3741+
memoryRequired += pTransportVec[ i ].iov_len;
3742+
}
3743+
3744+
return memoryRequired;
3745+
}
3746+
3747+
/*-----------------------------------------------------------*/
3748+
3749+
void MQTT_SerializeMQTTVec( uint8_t * pAllocatedMem,
3750+
MQTTVec_t * pVec )
3751+
{
3752+
TransportOutVector_t * pTransportVec = pVec->pVector;
3753+
const size_t vecLen = pVec->vectorLen;
3754+
size_t index = 0;
3755+
size_t i = 0;
3756+
3757+
for( i = 0; i < vecLen; i++ )
3758+
{
3759+
memcpy( &pAllocatedMem[ index ], pTransportVec[ i ].iov_base, pTransportVec[ i ].iov_len );
3760+
index += pTransportVec[ i ].iov_len;
3761+
}
3762+
}
3763+
3764+
/*-----------------------------------------------------------*/

0 commit comments

Comments
 (0)