Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
220 changes: 199 additions & 21 deletions applications/helpers/ota_orchestrator/src/ota_orchestrator.c
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
#define NUM_OF_BLOCKS_REQUESTED 1U
#define START_JOB_MSG_LENGTH 147U
#define MAX_JOB_ID_LENGTH 64U
#define UPDATE_JOB_MSG_LENGTH 48U
#define UPDATE_JOB_MSG_LENGTH 128U

extern void vOtaNotActiveHook( void );
extern void vOtaActiveHook( void );
Expand Down Expand Up @@ -178,6 +178,8 @@ char globalJobId[ MAX_JOB_ID_LENGTH ] = { 0 };
*/
static AfrOtaJobDocumentFields_t jobFields = { 0 };

static AfrOtaJobDocumentStatusDetails_t jobStatusDetails = { 0 };

/**
* @brief Structure used to hold data from a job document.
*/
Expand Down Expand Up @@ -252,9 +254,26 @@ STATIC bool closeFile( void );
STATIC bool activateImage( void );

/**
* @brief Send a message to notify that the firmware image was accepted.
* @brief Send a message to the cloud to update the statusDetails field of the
* job.
*/
STATIC void sendStatusDetailsMessage( void );

/**
* @brief Send a message to notify the cloud of the job's final status (i.e.
* accepted or failed).
*/
STATIC bool sendSuccessMessage( void );
STATIC void sendFinalJobStatusMessage( JobCurrentStatus_t status );

/**
* @brief Print the OTA job document metadata.
*
* @param[in] jobId String containing the job ID.
* @param[in] jobFields Pointer to the parameters extracted from the OTA job
* document.
*/
STATIC void printJobParams( const char * jobId,
AfrOtaJobDocumentFields_t jobFields );

/**
* @brief Send the necessary message to request a job document.
Expand Down Expand Up @@ -439,7 +458,7 @@ STATIC bool activateImage( void )
return otaPal_ActivateNewImage( &jobFields );
}

STATIC bool sendSuccessMessage( void )
STATIC void sendStatusDetailsMessage( void )
{
char topicBuffer[ TOPIC_BUFFER_SIZE + 1 ] = { 0 };
size_t topicBufferLength = 0U;
Expand All @@ -457,20 +476,119 @@ STATIC bool sendSuccessMessage( void )
( uint16_t ) app_strnlen( globalJobId, 1000U ),
&topicBufferLength );

/*
* Convert the current app firmware version to a string and send it to the
* the cloud
*/

/*
* Calling snprintf() with NULL and 0 as first two parameters gives the
* required length of the destination string.
*/
int updatedByBufferLength = snprintf( NULL,
0,
"%u",
appFirmwareVersion.u.x.build );

char updatedByBuffer[ updatedByBufferLength + 1 ];

( void ) snprintf( updatedByBuffer,
updatedByBufferLength + 1,
"%u",
appFirmwareVersion.u.x.build );

/*
* AWS IoT Jobs library:
* Creating the message which contains the status of OTA job.
* It will be published on the topic created in the previous step.
*/
size_t messageBufferLength = Jobs_UpdateMsg( Succeeded,
size_t messageBufferLength = Jobs_UpdateMsg( InProgress,
"2",
1U,
updatedByBuffer,
updatedByBufferLength,
messageBuffer,
UPDATE_JOB_MSG_LENGTH );

prvMQTTPublish( topicBuffer,
topicBufferLength,
messageBuffer,
messageBufferLength,
0 );
}

STATIC void sendFinalJobStatusMessage( JobCurrentStatus_t status )
{
char topicBuffer[ TOPIC_BUFFER_SIZE + 1 ] = { 0 };
size_t topicBufferLength = 0U;
char messageBuffer[ UPDATE_JOB_MSG_LENGTH ] = { 0 };

/*
* AWS IoT Jobs library:
* Creating the MQTT topic to update the status of OTA job.
*/
Jobs_Update( topicBuffer,
TOPIC_BUFFER_SIZE,
OTA_THING_NAME,
( uint16_t ) app_strnlen( OTA_THING_NAME, 1000U ),
globalJobId,
( uint16_t ) app_strnlen( globalJobId, 1000U ),
&topicBufferLength );

/*
* AWS IoT Jobs library:
* Creating the message which contains the status of OTA job.
* It will be published on the topic created in the previous step.
*/
size_t messageBufferLength = Jobs_UpdateMsg( status,
"3",
1U,
jobStatusDetails.updatedBy,
jobStatusDetails.updatedByLen,
messageBuffer,
UPDATE_JOB_MSG_LENGTH );

prvMQTTPublish( topicBuffer, topicBufferLength, messageBuffer, messageBufferLength, 0 );
LogInfo( ( "OTA update completed successfully.\n" ) );
globalJobId[ 0 ] = 0U;

if( status == Succeeded )
{
LogInfo( ( "OTA update completed successfully.\n" ) );
}
}

STATIC void printJobParams( const char * jobId,
AfrOtaJobDocumentFields_t jobFields )
{
char streamName[ jobFields.imageRefLen ];
char filePath[ jobFields.filepathLen ];
char certFile[ jobFields.certfileLen ];
char sig[ jobFields.signatureLen ];

LogInfo( ( "Extracted parameter: [jobid: %s]\n", jobId ) );

/*
* Strings in the jobFields structure are not null terminated so copy them
* to a buffer and null terminate them for printing.
*/
( void ) memcpy( streamName, jobFields.imageRef, jobFields.imageRefLen );
streamName[ jobFields.imageRefLen ] = '\0';
LogInfo( ( "Extracted parameter: [streamname: %s]\n", streamName ) );

( void ) memcpy( filePath, jobFields.filepath, jobFields.filepathLen );
filePath[ jobFields.filepathLen ] = '\0';
LogInfo( ( "Extracted parameter: [filepath: %s]\n", filePath ) );

LogInfo( ( "Extracted parameter: [filesize: %u]\n", jobFields.fileSize ) );
LogInfo( ( "Extracted parameter: [fileid: %u]\n", jobFields.fileId ) );

( void ) memcpy( certFile, jobFields.certfile, jobFields.certfileLen );
certFile[ jobFields.certfileLen ] = '\0';
LogInfo( ( "Extracted parameter: [certfile: %s]\n", certFile ) );

( void ) memcpy( sig, jobFields.signature, jobFields.signatureLen );
sig[ jobFields.signatureLen ] = '\0';
LogInfo( ( "Extracted parameter: [sig-sha256-rsa: %s]\n", sig ) );
}

/* -------------------------------------------------------------------------- */
Expand Down Expand Up @@ -665,29 +783,83 @@ STATIC OtaPalJobDocProcessingResult_t receivedJobDocumentHandler( OtaJobEventDat
{
bool handled = jobDocumentParser( ( char * ) jobDoc->jobData, jobDoc->jobDataLength, &jobFields );

if( otaPal_GetPlatformImageState( &jobFields ) == OtaPalImageStatePendingCommit )
{
( void ) sendSuccessMessage();

otaAgentShutdown();
}
populateJobStatusDetailsFields( ( char * ) jobDoc->jobData, jobDoc->jobDataLength, &jobStatusDetails );

if( handled )
{
initMqttDownloader( &jobFields );
printJobParams( globalJobId, jobFields );

/* AWS IoT core returns the signature in a PEM format. We need to
* convert it to DER format for image signature verification. */
/*In pending commit state, the device is in self test mode */
if( otaPal_GetPlatformImageState( &jobFields ) == OtaPalImageStatePendingCommit )
{
/*
* Convert the updatedBy string to an integer so the updatedBy
* version can be compared to the update firmware version.
*/
char updatedByBuffer[ jobStatusDetails.updatedByLen ];
char * endPtr;

/*
* updatedBy string is not null terminated so copy it to a
* temporary string and null terminate.
*/
( void ) memcpy( updatedByBuffer,
jobStatusDetails.updatedBy,
jobStatusDetails.updatedByLen );

updatedByBuffer[ jobStatusDetails.updatedByLen ] = '\0';

uint16_t updatedByVer = ( uint16_t ) strtoul( updatedByBuffer,
&endPtr,
10 );

if( updatedByVer < appFirmwareVersion.u.x.build )
{
LogInfo( ( "New image has a higher version number than the current image: "
"New image version=%u"
", Previous image version=%u",
appFirmwareVersion.u.x.build,
updatedByVer ) );

handled = convertSignatureToDER( OtaImageSignatureDecoded, sizeof( OtaImageSignatureDecoded ), &jobFields );
otaPal_SetPlatformImageState( &jobFields, OtaImageStateAccepted );
( void ) sendFinalJobStatusMessage( Succeeded );

if( handled )
{
xResult = otaPal_CreateFileForRx( &jobFields );
xResult = OtaPalNewImageBooted;
}
else
{
LogInfo( ( "Application version of the new image is not higher than the current image: "
"New images are expected to have a higher version number." ) );

otaPal_SetPlatformImageState( &jobFields, OtaImageStateRejected );

/*
* Mark the job as FAILED (AWS Job Service will not allow
* the job to be set to REJECTED if the job has been
* started already).
*/
( void ) sendFinalJobStatusMessage( Failed );

xResult = OtaPalNewImageBootFailed;
}
}
else
{
LogError( ( "Failed to decode the image signature to DER format." ) );
initMqttDownloader( &jobFields );

/* AWS IoT core returns the signature in a PEM format. We need to
* convert it to DER format for image signature verification. */

handled = convertSignatureToDER( OtaImageSignatureDecoded, sizeof( OtaImageSignatureDecoded ), &jobFields );

if( handled )
{
xResult = otaPal_CreateFileForRx( &jobFields );
}
else
{
LogError( ( "Failed to decode the image signature to DER format." ) );
}
}
}
}
Expand Down Expand Up @@ -862,11 +1034,15 @@ STATIC void processOTAEvents()
vOtaActiveHook();
break;

case OtaPalNewImageBooted:
LogInfo( ( "New firmware image booted.\n" ) );
vOtaNotActiveHook();
break;

case OtaPalJobDocFileCreateFailed:
case OtaPalNewImageBootFailed:
case OtaPalJobDocProcessingStateInvalid:
LogInfo( ( "No OTA job available. \n" ) );
otaAgentShutdown();
break;
}

Expand Down Expand Up @@ -958,6 +1134,8 @@ STATIC void processOTAEvents()
case OtaAgentEventActivateImage:
LogInfo( ( "Attempting to activate image.\n" ) );

sendStatusDetailsMessage();

if( activateImage() == true )
{
LogInfo( ( "Activated image.\n" ) );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ else()
set(PATCH_FILES
"${PATCH_FILES_DIRECTORY}/0001-Check-for-RSA-signature-instead-of-ECDSA.patch"
"${PATCH_FILES_DIRECTORY}/0002-Use-custom-strnlen-implementation.patch"
"${PATCH_FILES_DIRECTORY}/0003-Add-functionality-for-sending-and-retrieving-updated.patch"
)
iot_reference_arm_corstone3xx_apply_patches("${jobs-for-aws-iot-embedded-sdk_SOURCE_DIR}" "${PATCH_FILES}")

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
From 6ea98c733a2ea3c9d8cfdf4d3b689598ffdf8d54 Mon Sep 17 00:00:00 2001
From: Chuyue Luo <[email protected]>
Date: Wed, 4 Dec 2024 15:20:34 +0000
Subject: [PATCH 1/2] Check for RSA signature instead of ECDSA
Subject: [PATCH 1/3] Check for RSA signature instead of ECDSA

The Jobs-for-AWS-IoT-embedded-sdk library assumes the OTA job is signed
using ECDSA, but we currently use RSA. Thus, change the check for an
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
From 02b777ed41b393163c3f793d8ff3b608ac0b9634 Mon Sep 17 00:00:00 2001
From: Chuyue Luo <[email protected]>
Date: Wed, 4 Dec 2024 15:24:44 +0000
Subject: [PATCH 2/2] Use custom `strnlen` implementation
Subject: [PATCH 2/3] Use custom `strnlen` implementation

The Arm Compiler for Embedded (v6.21) does not support the `strnlen`
function. Therefore, use our own implementation (`app_strnlen`) instead.
Expand Down
Loading
Loading