3715 lines
128 KiB
C
3715 lines
128 KiB
C
/*
|
|
* AWS IoT Over-the-air Update v3.2.0
|
|
* Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
* this software and associated documentation files (the "Software"), to deal in
|
|
* the Software without restriction, including without limitation the rights to
|
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
|
* subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
/**
|
|
* @file ota.c
|
|
* @brief Implementation of the AWS IoT Over-The-Air Updates Client Library.
|
|
*/
|
|
|
|
/* Standard library includes. */
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <assert.h>
|
|
|
|
/* OTA agent includes. */
|
|
#include "ota.h"
|
|
|
|
/* OTA_DO_NOT_USE_CUSTOM_CONFIG allows building the OTA library
|
|
* without a custom config. If a custom config is provided, the
|
|
* OTA_DO_NOT_USE_CUSTOM_CONFIG macro should not be defined. */
|
|
#ifndef OTA_DO_NOT_USE_CUSTOM_CONFIG
|
|
#include "ota_config.h"
|
|
#endif
|
|
|
|
/* Include config defaults header to get default values of configs not defined
|
|
* in ota_config.h file. */
|
|
#include "ota_config_defaults.h"
|
|
|
|
/* OTA Base64 includes */
|
|
#include "ota_base64_private.h"
|
|
|
|
/* OTA pal includes. */
|
|
#include "ota_platform_interface.h"
|
|
|
|
/* Internal header file for shared OTA definitions. */
|
|
#include "ota_private.h"
|
|
|
|
/* OTA interface includes. */
|
|
#include "ota_interface_private.h"
|
|
|
|
/* OTA OS interface. */
|
|
#include "ota_os_interface.h"
|
|
|
|
/* Core JSON include */
|
|
#include "core_json.h"
|
|
|
|
/* Include firmware version struct definition. */
|
|
#include "ota_appversion32.h"
|
|
|
|
|
|
/**
|
|
* @brief Offset helper.
|
|
*/
|
|
#define U16_OFFSET( type, member ) ( ( uint16_t ) offsetof( type, member ) )
|
|
|
|
/**
|
|
* @brief OTA event handler definition.
|
|
*/
|
|
typedef OtaErr_t ( * OtaEventHandler_t )( const OtaEventData_t * pEventMsg );
|
|
|
|
/**
|
|
* @ingroup ota_datatypes_structs
|
|
* @brief OTA Agent state table entry.
|
|
* */
|
|
|
|
typedef struct OtaStateTableEntry
|
|
{
|
|
OtaState_t currentState; /**< Current state of the agent. */
|
|
OtaEvent_t eventId; /**< Event corresponding to the action. */
|
|
OtaEventHandler_t handler; /**< Handler to invoke the next action. */
|
|
OtaState_t nextState; /**< New state to be triggered*/
|
|
} OtaStateTableEntry_t;
|
|
|
|
/**
|
|
* @brief OTA control interface.
|
|
*/
|
|
static OtaControlInterface_t otaControlInterface;
|
|
|
|
/**
|
|
* @brief OTA data interface.
|
|
*/
|
|
static OtaDataInterface_t otaDataInterface;
|
|
|
|
/* OTA agent private function prototypes. */
|
|
|
|
/**
|
|
* @brief Ingest a data block.
|
|
*
|
|
* A block of file data was received by the application via some configured communication protocol.
|
|
* If it looks like it is in range, write it to persistent storage. If it's the last block we're
|
|
* expecting, close the file and perform the final signature check on it. If the close and signature
|
|
* check are OK, let the caller know so it can be used by the system. Firmware updates generally
|
|
* reboot the system and perform a self test phase. If the close or signature check fails, abort
|
|
* the file transfer and return the result and any available details to the caller.
|
|
*
|
|
* @param[in] pFileContext Information of file to be streamed.
|
|
* @param[in] pRawMsg Raw job document.
|
|
* @param[in] messageSize Length of document.
|
|
* @param[in] pCloseResult Result of closing file in PAL.
|
|
* @return IngestResult_t IngestResultAccepted_Continue if successful, other error for failure.
|
|
*/
|
|
static IngestResult_t ingestDataBlock( OtaFileContext_t * pFileContext,
|
|
const uint8_t * pRawMsg,
|
|
uint32_t messageSize,
|
|
OtaPalStatus_t * pCloseResult );
|
|
|
|
/**
|
|
* @brief Validate the incoming data block and store it in the file context.
|
|
*
|
|
* @param[in] pFileContext Information of file to be streamed.
|
|
* @param[in] uBlockIndex Incoming block index.
|
|
* @param[in] uBlockSize Incoming block size.
|
|
* @param[out] pCloseResult Result of closing file in PAL.
|
|
* @param[in] pPayload Data from the block.
|
|
* @return IngestResult_t IngestResultAccepted_Continue if successful, other error for failure.
|
|
*/
|
|
static IngestResult_t processDataBlock( OtaFileContext_t * pFileContext,
|
|
uint32_t uBlockIndex,
|
|
uint32_t uBlockSize,
|
|
OtaPalStatus_t * pCloseResult,
|
|
uint8_t * pPayload );
|
|
|
|
/**
|
|
* @brief Free the resources allocated for data ingestion and close the file handle.
|
|
*
|
|
* @param[in] pFileContext Information of file to be streamed.
|
|
* @param[out] pCloseResult Result of closing file in PAL.
|
|
* @return IngestResult_t IngestResultAccepted_Continue if successful, other error for failure.
|
|
*/
|
|
|
|
static IngestResult_t ingestDataBlockCleanup( OtaFileContext_t * pFileContext,
|
|
OtaPalStatus_t * pCloseResult );
|
|
|
|
/**
|
|
* @brief Get the File Context From Job Document.
|
|
*
|
|
* We received an OTA update job message from the job service so process
|
|
* the message and update the file context.
|
|
*
|
|
* @param[in] pRawMsg Raw job document.
|
|
* @param[in] messageLength length of document.
|
|
* @return OtaFileContext_t* Information of file to be streamed.
|
|
*/
|
|
static OtaFileContext_t * getFileContextFromJob( const char * pRawMsg,
|
|
uint32_t messageLength );
|
|
|
|
/**
|
|
* @brief Validate JSON document and the DocModel.
|
|
*
|
|
* @param[in] pJson JSON job document.
|
|
* @param[in] messageLength Length of the job document.
|
|
* @return DocParseErr_t DocParseErrNone if successful, JSON document parser errors.
|
|
*/
|
|
|
|
static DocParseErr_t validateJSON( const char * pJson,
|
|
uint32_t messageLength );
|
|
|
|
/**
|
|
* @brief Store the parameter from the json to the offset specified by the document model.
|
|
*
|
|
* @param[in] docParam Structure to store the details of keys and where to store them.
|
|
* @param[in] pContextBase Start of file context.
|
|
* @param[in] pValueInJson Pointer to the value of the key in JSON buffer.
|
|
* @param[in] valueLength Length of the value.
|
|
* @return DocParseErr_t DocParseErrNone if successful, JSON document parser errors.
|
|
*/
|
|
static DocParseErr_t extractParameter( JsonDocParam_t docParam,
|
|
void * pContextBase,
|
|
const char * pValueInJson,
|
|
size_t valueLength );
|
|
|
|
/**
|
|
* @brief Extract the desired fields from the JSON document based on the specified document model.
|
|
*
|
|
* @param[in] pJson JSON job document.
|
|
* @param[in] messageLength Length of the job document.
|
|
* @param[in] pDocModel Details of expected parameters in the job doc.
|
|
* @return DocParseErr_t DocParseErr_t DocParseErrNone if successful, JSON document parser errors.
|
|
*/
|
|
static DocParseErr_t parseJSONbyModel( const char * pJson,
|
|
uint32_t messageLength,
|
|
JsonDocModel_t * pDocModel );
|
|
|
|
/**
|
|
* @brief Decode the base64 encoded file signature key from the job document and store it in file context.
|
|
*
|
|
* @param[in] pValueInJson Pointer to the value of the key in JSON buffer.
|
|
* @param[in] valueLength Length of the value.
|
|
* @param[out] pParamAdd Pointer to the location where the value is stored.
|
|
* @return DocParseErr_t DocParseErrNone if successful, JSON document parser errors.
|
|
*/
|
|
static DocParseErr_t decodeAndStoreKey( const char * pValueInJson,
|
|
size_t valueLength,
|
|
void * pParamAdd );
|
|
|
|
/**
|
|
* @brief Extract the value from json and store it into the allocated memory.
|
|
*
|
|
* @param[in] pKey Name of the Key to extract.
|
|
* @param[in] pValueInJson Pointer to the value of the key in JSON buffer.
|
|
* @param[in] valueLength Length of the value.
|
|
* @param[out] pParamAdd Pointer to the location where the value is stored.
|
|
* @param[in] pParamSizeAdd Size required to store the param.
|
|
* @return DocParseErr_t DocParseErrNone if successful, JSON document parser errors.
|
|
*/
|
|
static DocParseErr_t extractAndStoreArray( const char * pKey,
|
|
const char * pValueInJson,
|
|
size_t valueLength,
|
|
void * pParamAdd,
|
|
uint32_t * pParamSizeAdd );
|
|
|
|
/**
|
|
* @brief Check if all the required parameters for job document are extracted from the JSON.
|
|
*
|
|
* @param[in] pModelParam Structure to store the details of keys and where to store them.
|
|
* @param[in] pDocModel Details of expected parameters in the job doc.
|
|
* @return DocParseErr_t DocParseErrNone if successful, JSON document parser errors.
|
|
*/
|
|
static DocParseErr_t verifyRequiredParamsExtracted( const JsonDocParam_t * pModelParam,
|
|
const JsonDocModel_t * pDocModel );
|
|
|
|
/**
|
|
* @brief Validate the version of the update received.
|
|
*
|
|
* @param[in] pFileContext Information of file to be streamed.
|
|
* @return OtaErr_t OtaErrNone if successful, other error codes if failure.
|
|
*/
|
|
static OtaErr_t validateUpdateVersion( const OtaFileContext_t * pFileContext );
|
|
|
|
/**
|
|
* @brief Check if the JSON can be parsed through the app callback if initial parsing fails.
|
|
*
|
|
* @param[in] pJson JSON job document.
|
|
* @param[in] messageLength Length of the job document.
|
|
* @return OtaJobParseErr_t OtaJobParseErrNone if successful, other error codes if failure.
|
|
*/
|
|
static OtaJobParseErr_t handleCustomJob( const char * pJson,
|
|
uint32_t messageLength );
|
|
|
|
/**
|
|
* @brief Check if the incoming job document is not conflicting with current job status.
|
|
*
|
|
* @param[in] pFileContext Information of file to be streamed.
|
|
* @param[out] pFinalFile File that stores all extracted params.
|
|
* @param[out] pUpdateJob Represents if the job is accepted.
|
|
* @return OtaJobParseErr_t OtaErrNone if successful, other error codes if failure.
|
|
*/
|
|
static OtaJobParseErr_t verifyActiveJobStatus( OtaFileContext_t * pFileContext,
|
|
OtaFileContext_t ** pFinalFile,
|
|
bool * pUpdateJob );
|
|
|
|
/**
|
|
* @brief Check if all the file context params are valid and initialize resources for the job transfer.
|
|
*
|
|
* @param[in] pFileContext Information of file to be streamed.
|
|
* @param[out] pFinalFile File that stores all extracted params.
|
|
* @param[out] pUpdateJob Represents if the job is accepted.
|
|
* @return OtaJobParseErr_t OtaJobParseErrNone if successful, other error codes if failure.
|
|
*/
|
|
static OtaJobParseErr_t validateAndStartJob( OtaFileContext_t * pFileContext,
|
|
OtaFileContext_t ** pFinalFile,
|
|
bool * pUpdateJob );
|
|
|
|
/**
|
|
* @brief Parse the OTA job document, validate and return the populated OTA context if valid.
|
|
*
|
|
* @param[in] pJsonExpectedParams Structure to store the details of keys and where to store them.
|
|
* @param[in] numJobParams Number of parameters to be extracted.
|
|
* @param[in] pJson JSON job document.
|
|
* @param[in] messageLength Length of the job document.
|
|
* @param[in] pUpdateJob Represents if the job is accepted.
|
|
* @return OtaFileContext_t* File context to store file information.
|
|
*/
|
|
static OtaFileContext_t * parseJobDoc( const JsonDocParam_t * pJsonExpectedParams,
|
|
uint16_t numJobParams,
|
|
const char * pJson,
|
|
uint32_t messageLength,
|
|
bool * pUpdateJob );
|
|
|
|
/**
|
|
* @brief Validate block index and block size of the data block.
|
|
*
|
|
* @param[in] pFileContext Information of file to be streamed.
|
|
* @param[in] blockIndex Block index of incoming data block.
|
|
* @param[in] blockSize Block size of incoming data block.
|
|
* @return true if successful, false otherwise.
|
|
*/
|
|
static bool validateDataBlock( const OtaFileContext_t * pFileContext,
|
|
uint32_t blockIndex,
|
|
uint32_t blockSize );
|
|
|
|
/**
|
|
* @brief Decode and ingest the incoming data block.
|
|
*
|
|
* @param[in] pFileContext Information of file to be streamed.
|
|
* @param[in] pRawMsg Raw job document.
|
|
* @param[in] messageSize Length of document.
|
|
* @param[in] pPayload Data stored in the document.
|
|
* @param[out] pBlockSize Block size of incoming data block.
|
|
* @param[out] pBlockIndex Block index of incoming data block.
|
|
* @return IngestResult_t IngestResultAccepted_Continue if successful, other error for failure.
|
|
*/
|
|
static IngestResult_t decodeAndStoreDataBlock( OtaFileContext_t * pFileContext,
|
|
const uint8_t * pRawMsg,
|
|
uint32_t messageSize,
|
|
uint8_t ** pPayload,
|
|
uint32_t * pBlockSize,
|
|
uint32_t * pBlockIndex );
|
|
|
|
/**
|
|
* @brief Close an open OTA file context and free it.
|
|
*
|
|
* @param[in] pFileContext Information of file to be streamed.
|
|
* @return true if successful, false otherwise.
|
|
*/
|
|
static bool otaClose( OtaFileContext_t * const pFileContext );
|
|
|
|
/**
|
|
* @brief OTA Timer callback.
|
|
*
|
|
* @param[in] otaTimerId Reference to the timer to use.
|
|
*/
|
|
static void otaTimerCallback( OtaTimerId_t otaTimerId );
|
|
|
|
/**
|
|
* @brief Internal function to set the image state including an optional reason code.
|
|
*
|
|
* @param[in] stateToSet State to set.
|
|
* @param[in] reasonToSet Reason to set.
|
|
* @return OtaErr_t OtaErrNone if successful, other codes on failure.
|
|
*/
|
|
static OtaErr_t setImageStateWithReason( OtaImageState_t stateToSet,
|
|
uint32_t reasonToSet );
|
|
|
|
/**
|
|
* @brief Internal function to update the job status to the jobs service from current image state.
|
|
*
|
|
* @param[in] state State to set.
|
|
* @param[in] subReason Reason for status.
|
|
* @return OtaErr_t OtaErrNone if successful, other codes on failure.
|
|
*/
|
|
static OtaErr_t updateJobStatusFromImageState( OtaImageState_t state,
|
|
int32_t subReason );
|
|
|
|
/**
|
|
* @brief A helper function to cleanup resources during OTA agent shutdown.
|
|
*/
|
|
static void agentShutdownCleanup( void );
|
|
|
|
/**
|
|
* @brief A helper function to cleanup resources when data ingestion is complete.
|
|
*/
|
|
static void dataHandlerCleanup( void );
|
|
|
|
/**
|
|
* @brief Prepare the document model for use by sanity checking the initialization parameters and detecting all required parameters.
|
|
*
|
|
* @param[inout] pDocModel Details of expected parameters in the job doc.
|
|
* @param[in] pBodyDef Structure to store the details of keys and where to store them.
|
|
* @param[in] contextBaseAddr Start of file context.
|
|
* @param[in] contextSize Size of file context.
|
|
* @param[in] numJobParams Number of parameters to be extracted.
|
|
* @return DocParseErr_t DocParseErrNone if successful, JSON document parser errors.
|
|
*/
|
|
static DocParseErr_t initDocModel( JsonDocModel_t * pDocModel,
|
|
const JsonDocParam_t * pBodyDef,
|
|
void * contextBaseAddr,
|
|
uint32_t contextSize,
|
|
uint16_t numJobParams );
|
|
|
|
/**
|
|
* @brief Initialize buffers for storing the file attributes.
|
|
*
|
|
* @param[out] pOtaBuffer OTA Application buffers.
|
|
*/
|
|
static void initializeAppBuffers( OtaAppBuffer_t * pOtaBuffer );
|
|
|
|
/**
|
|
* @brief Initialize jobId and protocol buffers.
|
|
*/
|
|
static void initializeLocalBuffers( void );
|
|
|
|
/**
|
|
* @brief Search the state transition table for the entry based on current state and incoming event.
|
|
*
|
|
* @param[in] pEventMsg Incoming event information.
|
|
* @return uint32_t Index of the transition.
|
|
*/
|
|
static uint32_t searchTransition( const OtaEventMsg_t * pEventMsg );
|
|
|
|
/**
|
|
* @brief Initiate download if not in self-test else reboot
|
|
*
|
|
* @return OtaErr_t OtaErrNone if successful.
|
|
*/
|
|
static OtaErr_t processValidFileContext( void );
|
|
|
|
/**
|
|
* @brief Validate update version when receiving job doc in self test state.
|
|
*
|
|
* @param[in] pFileContext Stores file information.
|
|
*/
|
|
static void handleSelfTestJobDoc( OtaFileContext_t * pFileContext );
|
|
|
|
/**
|
|
* @brief Handle invalid file context.
|
|
*
|
|
* @return OtaErr_t OtaErrNone if job parsing is handled.
|
|
*/
|
|
static OtaErr_t processNullFileContext( void );
|
|
|
|
/**
|
|
* @brief Check if the platform is in self-test
|
|
*
|
|
* @return true if in self-test, else false.
|
|
*/
|
|
static bool platformInSelftest( void );
|
|
|
|
/**
|
|
* @brief Function to handle events that were unexpected in the current state.
|
|
*
|
|
* @param[in] pEventMsg Stores information of the event.
|
|
*/
|
|
static void handleUnexpectedEvents( const OtaEventMsg_t * pEventMsg );
|
|
|
|
/**
|
|
* @brief Free or clear multiple buffers used in the file context.
|
|
*
|
|
* @param[in] pFileContext Information of file to be streamed.
|
|
*/
|
|
static void freeFileContextMem( OtaFileContext_t * const pFileContext );
|
|
|
|
/**
|
|
* @brief Handle job parsing error.
|
|
*
|
|
* @param[in] pFileContext Pointer to the file context.
|
|
*
|
|
* @param[in] err Parsing error of type OtaJobParseErr_t.
|
|
*/
|
|
static void handleJobParsingError( const OtaFileContext_t * pFileContext,
|
|
OtaJobParseErr_t err );
|
|
|
|
/**
|
|
* @brief Receive and process the next available event from the event queue.
|
|
*
|
|
* Each event is processed based on the behavior defined in the OTA transition
|
|
* table. The state of the OTA state machine will be updated and the
|
|
* corresponding event handler will be called.
|
|
*/
|
|
static void receiveAndProcessOtaEvent( void );
|
|
|
|
/* OTA state event handler functions. */
|
|
|
|
static OtaErr_t startHandler( const OtaEventData_t * pEventData ); /*!< Start timers and initiate request for job document. */
|
|
static OtaErr_t requestJobHandler( const OtaEventData_t * pEventData ); /*!< Initiate a request for a job. */
|
|
static OtaErr_t processJobHandler( const OtaEventData_t * pEventData ); /*!< Update file context from job document. */
|
|
static OtaErr_t inSelfTestHandler( const OtaEventData_t * pEventData ); /*!< Handle self test. */
|
|
static OtaErr_t initFileHandler( const OtaEventData_t * pEventData ); /*!< Initialize and handle file transfer. */
|
|
static OtaErr_t processDataHandler( const OtaEventData_t * pEventData ); /*!< Process incoming data blocks. */
|
|
static OtaErr_t requestDataHandler( const OtaEventData_t * pEventData ); /*!< Request for data blocks. */
|
|
static OtaErr_t shutdownHandler( const OtaEventData_t * pEventData ); /*!< Shutdown OTA and cleanup. */
|
|
static OtaErr_t closeFileHandler( const OtaEventData_t * pEventData ); /*!< Close file opened for download. */
|
|
static OtaErr_t userAbortHandler( const OtaEventData_t * pEventData ); /*!< Handle user interrupt to abort task. */
|
|
static OtaErr_t suspendHandler( const OtaEventData_t * pEventData ); /*!< Handle suspend event for OTA agent. */
|
|
static OtaErr_t resumeHandler( const OtaEventData_t * pEventData ); /*!< Resume from a suspended state. */
|
|
static OtaErr_t jobNotificationHandler( const OtaEventData_t * pEventData ); /*!< Upon receiving a new job document cancel current job if present and initiate new download. */
|
|
static void executeHandler( uint32_t index,
|
|
const OtaEventMsg_t * const pEventMsg ); /*!< Execute the handler for selected index from the transition table. */
|
|
|
|
/**
|
|
* @brief This is THE OTA agent context and initialization state.
|
|
*/
|
|
static OtaAgentContext_t otaAgent =
|
|
{
|
|
OtaAgentStateStopped, /* state */
|
|
{ 0 }, /* pThingName */
|
|
{ 0 }, /* fileContext */
|
|
0, /* fileIndex */
|
|
0, /* serverFileID */
|
|
{ 0 }, /* pActiveJobName */
|
|
NULL, /* pClientTokenFromJob */
|
|
0, /* timestampFromJob */
|
|
OtaImageStateUnknown, /* imageState */
|
|
1, /* numOfBlocksToReceive */
|
|
{ 0 }, /* statistics */
|
|
0, /* requestMomentum */
|
|
NULL, /* pOtaInterface */
|
|
NULL, /* OtaAppCallback */
|
|
1 /* unsubscribe flag */
|
|
};
|
|
|
|
/**
|
|
* @brief Transition table for the OTA state machine.
|
|
*/
|
|
static OtaStateTableEntry_t otaTransitionTable[] =
|
|
{
|
|
/*STATE , EVENT , ACTION , NEXT STATE */
|
|
{ OtaAgentStateReady, OtaAgentEventStart, startHandler, OtaAgentStateRequestingJob },
|
|
{ OtaAgentStateRequestingJob, OtaAgentEventRequestJobDocument, requestJobHandler, OtaAgentStateWaitingForJob },
|
|
{ OtaAgentStateRequestingJob, OtaAgentEventRequestTimer, requestJobHandler, OtaAgentStateWaitingForJob },
|
|
{ OtaAgentStateWaitingForJob, OtaAgentEventReceivedJobDocument, processJobHandler, OtaAgentStateCreatingFile },
|
|
{ OtaAgentStateCreatingFile, OtaAgentEventStartSelfTest, inSelfTestHandler, OtaAgentStateWaitingForJob },
|
|
{ OtaAgentStateCreatingFile, OtaAgentEventCreateFile, initFileHandler, OtaAgentStateRequestingFileBlock },
|
|
{ OtaAgentStateCreatingFile, OtaAgentEventRequestTimer, initFileHandler, OtaAgentStateRequestingFileBlock },
|
|
{ OtaAgentStateRequestingFileBlock, OtaAgentEventRequestFileBlock, requestDataHandler, OtaAgentStateWaitingForFileBlock },
|
|
{ OtaAgentStateRequestingFileBlock, OtaAgentEventRequestTimer, requestDataHandler, OtaAgentStateWaitingForFileBlock },
|
|
{ OtaAgentStateWaitingForFileBlock, OtaAgentEventReceivedFileBlock, processDataHandler, OtaAgentStateWaitingForFileBlock },
|
|
{ OtaAgentStateWaitingForFileBlock, OtaAgentEventRequestTimer, requestDataHandler, OtaAgentStateWaitingForFileBlock },
|
|
{ OtaAgentStateWaitingForFileBlock, OtaAgentEventRequestFileBlock, requestDataHandler, OtaAgentStateWaitingForFileBlock },
|
|
{ OtaAgentStateWaitingForFileBlock, OtaAgentEventRequestJobDocument, requestJobHandler, OtaAgentStateWaitingForJob },
|
|
{ OtaAgentStateWaitingForFileBlock, OtaAgentEventReceivedJobDocument, jobNotificationHandler, OtaAgentStateRequestingJob },
|
|
{ OtaAgentStateWaitingForFileBlock, OtaAgentEventCloseFile, closeFileHandler, OtaAgentStateWaitingForJob },
|
|
{ OtaAgentStateSuspended, OtaAgentEventResume, resumeHandler, OtaAgentStateRequestingJob },
|
|
{ OtaAgentStateAll, OtaAgentEventSuspend, suspendHandler, OtaAgentStateSuspended },
|
|
{ OtaAgentStateAll, OtaAgentEventUserAbort, userAbortHandler, OtaAgentStateWaitingForJob },
|
|
{ OtaAgentStateAll, OtaAgentEventShutdown, shutdownHandler, OtaAgentStateStopped },
|
|
};
|
|
|
|
/* MISRA rule 2.2 warns about unused variables. These 2 variables are used in log messages, which is
|
|
* disabled when running static analysis. So it's a false positive. */
|
|
/* coverity[misra_c_2012_rule_2_2_violation] */
|
|
/*!< String set to represent the States of the OTA agent. */
|
|
static const char * pOtaAgentStateStrings[ OtaAgentStateAll + 1 ] =
|
|
{
|
|
"Init",
|
|
"Ready",
|
|
"RequestingJob",
|
|
"WaitingForJob",
|
|
"CreatingFile",
|
|
"RequestingFileBlock",
|
|
"WaitingForFileBlock",
|
|
"ClosingFile",
|
|
"Suspended",
|
|
"ShuttingDown",
|
|
"Stopped",
|
|
"All"
|
|
};
|
|
|
|
/* coverity[misra_c_2012_rule_2_2_violation] */
|
|
/*!< String set to represent the Events for the OTA agent. */
|
|
static const char * pOtaEventStrings[ OtaAgentEventMax ] =
|
|
{
|
|
"Start",
|
|
"StartSelfTest",
|
|
"RequestJobDocument",
|
|
"ReceivedJobDocument",
|
|
"CreateFile",
|
|
"RequestFileBlock",
|
|
"ReceivedFileBlock",
|
|
"RequestTimer",
|
|
"CloseFile",
|
|
"Suspend",
|
|
"Resume",
|
|
"UserAbort",
|
|
"Shutdown"
|
|
};
|
|
|
|
/**
|
|
* @brief This is the OTA job document model describing the parameters, their types, destination and how to extract.
|
|
*/
|
|
static const JsonDocParam_t otaJobDocModelParamStructure[ OTA_NUM_JOB_PARAMS ] =
|
|
{
|
|
{ OTA_JSON_CLIENT_TOKEN_KEY, OTA_JOB_PARAM_OPTIONAL, OTA_DONT_STORE_PARAM, OTA_DONT_STORE_PARAM, ModelParamTypeStringInDoc },
|
|
{ OTA_JSON_TIMESTAMP_KEY, OTA_JOB_PARAM_OPTIONAL, OTA_DONT_STORE_PARAM, OTA_DONT_STORE_PARAM, ModelParamTypeUInt32 },
|
|
{ OTA_JSON_EXECUTION_KEY, OTA_JOB_PARAM_REQUIRED, OTA_DONT_STORE_PARAM, OTA_DONT_STORE_PARAM, ModelParamTypeObject },
|
|
{ OTA_JSON_JOB_ID_KEY, OTA_JOB_PARAM_REQUIRED, U16_OFFSET( OtaFileContext_t, pJobName ), U16_OFFSET( OtaFileContext_t, jobNameMaxSize ), ModelParamTypeStringCopy},
|
|
{ OTA_JSON_STATUS_DETAILS_KEY, OTA_JOB_PARAM_OPTIONAL, OTA_DONT_STORE_PARAM, OTA_DONT_STORE_PARAM, ModelParamTypeObject },
|
|
{ OTA_JSON_SELF_TEST_KEY, OTA_JOB_PARAM_OPTIONAL, U16_OFFSET( OtaFileContext_t, isInSelfTest ), OTA_DONT_STORE_PARAM, ModelParamTypeIdent},
|
|
{ OTA_JSON_UPDATED_BY_KEY, OTA_JOB_PARAM_OPTIONAL, U16_OFFSET( OtaFileContext_t, updaterVersion ), OTA_DONT_STORE_PARAM, ModelParamTypeUInt32},
|
|
{ OTA_JSON_JOB_DOC_KEY, OTA_JOB_PARAM_REQUIRED, OTA_DONT_STORE_PARAM, OTA_DONT_STORE_PARAM, ModelParamTypeObject },
|
|
{ OTA_JSON_OTA_UNIT_KEY, OTA_JOB_PARAM_REQUIRED, OTA_DONT_STORE_PARAM, OTA_DONT_STORE_PARAM, ModelParamTypeObject },
|
|
{ OTA_JSON_STREAM_NAME_KEY, OTA_JOB_PARAM_OPTIONAL, U16_OFFSET( OtaFileContext_t, pStreamName ), U16_OFFSET( OtaFileContext_t, streamNameMaxSize ), ModelParamTypeStringCopy},
|
|
{ OTA_JSON_PROTOCOLS_KEY, OTA_JOB_PARAM_REQUIRED, U16_OFFSET( OtaFileContext_t, pProtocols ), U16_OFFSET( OtaFileContext_t, protocolMaxSize ), ModelParamTypeArrayCopy},
|
|
{ OTA_JSON_FILE_GROUP_KEY, OTA_JOB_PARAM_REQUIRED, OTA_STORE_NESTED_JSON, OTA_STORE_NESTED_JSON, ModelParamTypeArray },
|
|
{ OTA_JSON_FILE_PATH_KEY, OTA_JOB_PARAM_OPTIONAL, U16_OFFSET( OtaFileContext_t, pFilePath ), U16_OFFSET( OtaFileContext_t, filePathMaxSize ), ModelParamTypeStringCopy},
|
|
{ OTA_JSON_FILE_SIZE_KEY, OTA_JOB_PARAM_REQUIRED, U16_OFFSET( OtaFileContext_t, fileSize ), OTA_DONT_STORE_PARAM, ModelParamTypeUInt32},
|
|
{ OTA_JSON_FILE_ID_KEY, OTA_JOB_PARAM_REQUIRED, U16_OFFSET( OtaFileContext_t, serverFileID ), OTA_DONT_STORE_PARAM, ModelParamTypeUInt32},
|
|
{ OTA_JSON_FILE_CERT_NAME_KEY, OTA_JOB_PARAM_OPTIONAL, U16_OFFSET( OtaFileContext_t, pCertFilepath ), U16_OFFSET( OtaFileContext_t, certFilePathMaxSize ), ModelParamTypeStringCopy},
|
|
{ OTA_JSON_UPDATE_DATA_URL_KEY, OTA_JOB_PARAM_OPTIONAL, U16_OFFSET( OtaFileContext_t, pUpdateUrlPath ), U16_OFFSET( OtaFileContext_t, updateUrlMaxSize ), ModelParamTypeStringCopy},
|
|
{ OTA_JSON_AUTH_SCHEME_KEY, OTA_JOB_PARAM_OPTIONAL, U16_OFFSET( OtaFileContext_t, pAuthScheme ), U16_OFFSET( OtaFileContext_t, authSchemeMaxSize ), ModelParamTypeStringCopy},
|
|
{ OTA_JsonFileSignatureKey, OTA_JOB_PARAM_OPTIONAL, U16_OFFSET( OtaFileContext_t, pSignature ), OTA_DONT_STORE_PARAM, ModelParamTypeSigBase64},
|
|
{ OTA_JSON_FILE_ATTRIBUTE_KEY, OTA_JOB_PARAM_OPTIONAL, U16_OFFSET( OtaFileContext_t, fileAttributes ), OTA_DONT_STORE_PARAM, ModelParamTypeUInt32},
|
|
{ OTA_JSON_FILETYPE_KEY, OTA_JOB_PARAM_OPTIONAL, U16_OFFSET( OtaFileContext_t, fileType ), OTA_DONT_STORE_PARAM, ModelParamTypeUInt32}
|
|
};
|
|
|
|
static uint8_t pJobNameBuffer[ OTA_JOB_ID_MAX_SIZE ]; /*!< Buffer to store job name. */
|
|
static uint8_t pProtocolBuffer[ OTA_PROTOCOL_BUFFER_SIZE ]; /*!< Buffer to store data protocol. */
|
|
static Sig256_t sig256Buffer; /*!< Buffer to store key file signature. */
|
|
|
|
static void otaTimerCallback( OtaTimerId_t otaTimerId )
|
|
{
|
|
assert( ( otaTimerId == OtaRequestTimer ) || ( otaTimerId == OtaSelfTestTimer ) );
|
|
|
|
if( otaTimerId == OtaRequestTimer )
|
|
{
|
|
OtaEventMsg_t xEventMsg = { 0 };
|
|
|
|
LogDebug( ( "Self-test expired within %ums",
|
|
otaconfigFILE_REQUEST_WAIT_MS ) );
|
|
|
|
xEventMsg.eventId = OtaAgentEventRequestTimer;
|
|
|
|
/* Send request timer event. */
|
|
if( OTA_SignalEvent( &xEventMsg ) == false )
|
|
{
|
|
LogError( ( "Failed to signal the OTA Agent to start request timer" ) );
|
|
}
|
|
}
|
|
else /* ( otaTimerId == OtaSelfTestTimer ) */
|
|
{
|
|
LogError( ( "Self test failed to complete within %ums",
|
|
otaconfigSELF_TEST_RESPONSE_WAIT_MS ) );
|
|
|
|
( void ) otaAgent.pOtaInterface->pal.reset( &otaAgent.fileContext );
|
|
}
|
|
}
|
|
|
|
|
|
static bool platformInSelftest( void )
|
|
{
|
|
bool selfTest = false;
|
|
|
|
/*
|
|
* Get the platform state from the OTA pal layer.
|
|
*/
|
|
if( otaAgent.pOtaInterface->pal.getPlatformImageState( &( otaAgent.fileContext ) ) == OtaPalImageStatePendingCommit )
|
|
{
|
|
selfTest = true;
|
|
}
|
|
|
|
return selfTest;
|
|
}
|
|
|
|
static OtaErr_t updateJobStatusFromImageState( OtaImageState_t state,
|
|
int32_t subReason )
|
|
{
|
|
OtaErr_t err = OtaErrNone;
|
|
OtaJobReason_t reason = 0;
|
|
|
|
if( state == OtaImageStateTesting )
|
|
{
|
|
/* We discovered we're ready for test mode, put job status in self_test active. */
|
|
err = otaControlInterface.updateJobStatus( &otaAgent,
|
|
JobStatusInProgress,
|
|
JobReasonSelfTestActive,
|
|
0 );
|
|
}
|
|
else
|
|
{
|
|
if( state == OtaImageStateAccepted )
|
|
{
|
|
/* Now that we have accepted the firmware update, we can complete the job. */
|
|
err = otaControlInterface.updateJobStatus( &otaAgent,
|
|
JobStatusSucceeded,
|
|
JobReasonAccepted,
|
|
appFirmwareVersion.u.signedVersion32 );
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* The firmware update was either rejected or aborted, complete the job as FAILED (Job service
|
|
* will not allow us to set REJECTED after the job has been started already).
|
|
*/
|
|
reason = ( state == OtaImageStateRejected ) ? JobReasonRejected : JobReasonAborted;
|
|
err = otaControlInterface.updateJobStatus( &otaAgent,
|
|
JobStatusFailed,
|
|
reason,
|
|
subReason );
|
|
}
|
|
|
|
/*
|
|
* We don't need the job name memory anymore since we're done with this job.
|
|
*/
|
|
( void ) memset( otaAgent.pActiveJobName, 0, OTA_JOB_ID_MAX_SIZE );
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static OtaErr_t setImageStateWithReason( OtaImageState_t stateToSet,
|
|
uint32_t reasonToSet )
|
|
{
|
|
OtaErr_t err = OtaErrNone;
|
|
OtaImageState_t state = stateToSet;
|
|
uint32_t reason = reasonToSet;
|
|
OtaPalStatus_t palStatus;
|
|
|
|
/* Call the platform specific code to set the image state. */
|
|
palStatus = otaAgent.pOtaInterface->pal.setPlatformImageState( &( otaAgent.fileContext ), state );
|
|
|
|
/*
|
|
* If the platform image state couldn't be set correctly, force fail the update by setting the
|
|
* image state to "Rejected" unless it's already in "Aborted".
|
|
*/
|
|
if( ( OTA_PAL_MAIN_ERR( palStatus ) != OtaPalSuccess ) && ( state != OtaImageStateAborted ) )
|
|
{
|
|
/* Intentionally override state since we failed within this function. */
|
|
state = OtaImageStateRejected;
|
|
|
|
/*
|
|
* Capture the failure reason if not already set (and we're not already Aborted as checked above). Otherwise Keep
|
|
* the original reject reason code since it is possible for the PAL to fail to update the image state in some
|
|
* cases (e.g. a reset already caused the bundle rollback and we failed to rollback again).
|
|
*/
|
|
if( reason == 0U )
|
|
{
|
|
/* Intentionally override reason since we failed within this function. */
|
|
reason = palStatus;
|
|
}
|
|
}
|
|
|
|
/* Now update the image state and job status on service side. */
|
|
otaAgent.imageState = state;
|
|
|
|
if( strlen( ( const char * ) otaAgent.pActiveJobName ) > 0u )
|
|
{
|
|
err = updateJobStatusFromImageState( state, ( int32_t ) reason );
|
|
}
|
|
else
|
|
{
|
|
err = OtaErrNoActiveJob;
|
|
}
|
|
|
|
if( err != OtaErrNone )
|
|
{
|
|
LogWarn( ( "Failed to set image state with reason: "
|
|
"OtaErr_t=%s"
|
|
", OtaPalStatus_t=%s"
|
|
", state=%d"
|
|
", reason=%d",
|
|
OTA_Err_strerror( err ),
|
|
OTA_PalStatus_strerror( OTA_PAL_MAIN_ERR( palStatus ) ),
|
|
stateToSet,
|
|
reasonToSet ) );
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static OtaErr_t startHandler( const OtaEventData_t * pEventData )
|
|
{
|
|
OtaErr_t retVal = OtaErrNone;
|
|
OtaEventMsg_t eventMsg = { 0 };
|
|
|
|
( void ) pEventData;
|
|
|
|
/* Start self-test timer, if platform is in self-test. */
|
|
if( platformInSelftest() == true )
|
|
{
|
|
( void ) otaAgent.pOtaInterface->os.timer.start( OtaSelfTestTimer,
|
|
"OtaSelfTestTimer",
|
|
otaconfigSELF_TEST_RESPONSE_WAIT_MS,
|
|
otaTimerCallback );
|
|
}
|
|
|
|
/* Send event to OTA task to get job document. */
|
|
eventMsg.eventId = OtaAgentEventRequestJobDocument;
|
|
|
|
if( OTA_SignalEvent( &eventMsg ) == false )
|
|
{
|
|
retVal = OtaErrSignalEventFailed;
|
|
}
|
|
|
|
return retVal;
|
|
}
|
|
|
|
static OtaErr_t inSelfTestHandler( const OtaEventData_t * pEventData )
|
|
{
|
|
OtaErr_t err = OtaErrNone;
|
|
|
|
( void ) pEventData;
|
|
|
|
LogInfo( ( "Beginning self-test." ) );
|
|
|
|
/* Check the platform's OTA update image state. It should also be in self test. */
|
|
if( platformInSelftest() == true )
|
|
{
|
|
/* Callback for application specific self-test. */
|
|
otaAgent.OtaAppCallback( OtaJobEventStartTest, NULL );
|
|
|
|
/* Clear self-test flag. */
|
|
otaAgent.fileContext.isInSelfTest = false;
|
|
|
|
/* Stop the self test timer as it is no longer required. */
|
|
( void ) otaAgent.pOtaInterface->os.timer.stop( OtaSelfTestTimer );
|
|
}
|
|
else
|
|
{
|
|
/* The job is in self test but the platform image state is not so it could be
|
|
* an attack on the platform image state. Reject the update (this should also
|
|
* cause the image to be erased), aborting the job and reset the device. */
|
|
LogWarn( ( "Rejecting new image and rebooting:"
|
|
"The job is in the self-test state while the platform is not." ) );
|
|
|
|
err = setImageStateWithReason( OtaImageStateRejected, ( uint32_t ) OtaErrImageStateMismatch );
|
|
( void ) otaAgent.pOtaInterface->pal.reset( &( otaAgent.fileContext ) );
|
|
}
|
|
|
|
if( err != OtaErrNone )
|
|
{
|
|
LogError( ( "Failed to start self-test: "
|
|
"OtaErr_t=%s",
|
|
OTA_Err_strerror( err ) ) );
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static OtaErr_t requestJobHandler( const OtaEventData_t * pEventData )
|
|
{
|
|
OtaErr_t retVal = OtaErrUninitialized;
|
|
OtaOsStatus_t osErr = OtaOsSuccess;
|
|
OtaEventMsg_t eventMsg = { 0 };
|
|
|
|
( void ) pEventData;
|
|
|
|
/*
|
|
* Check if any pending jobs are available from job service.
|
|
*/
|
|
retVal = otaControlInterface.requestJob( &otaAgent );
|
|
|
|
if( retVal != OtaErrNone )
|
|
{
|
|
if( otaAgent.requestMomentum < otaconfigMAX_NUM_REQUEST_MOMENTUM )
|
|
{
|
|
/* Start the request timer. */
|
|
osErr = otaAgent.pOtaInterface->os.timer.start( OtaRequestTimer,
|
|
"OtaRequestTimer",
|
|
otaconfigFILE_REQUEST_WAIT_MS,
|
|
otaTimerCallback );
|
|
|
|
if( osErr != OtaOsSuccess )
|
|
{
|
|
LogError( ( "Failed to start request timer: "
|
|
"OtaOsStatus_t=%s",
|
|
OTA_OsStatus_strerror( osErr ) ) );
|
|
retVal = OtaErrRequestJobFailed;
|
|
}
|
|
else
|
|
{
|
|
otaAgent.requestMomentum++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Stop the request timer. */
|
|
( void ) otaAgent.pOtaInterface->os.timer.stop( OtaRequestTimer );
|
|
|
|
/* Send shutdown event to the OTA Agent task. */
|
|
eventMsg.eventId = OtaAgentEventShutdown;
|
|
|
|
if( OTA_SignalEvent( &eventMsg ) == false )
|
|
{
|
|
retVal = OtaErrSignalEventFailed;
|
|
}
|
|
else
|
|
{
|
|
/* Too many requests have been sent without a response or too many failures
|
|
* when trying to publish the request message. Abort. */
|
|
retVal = OtaErrMomentumAbort;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Stop the request timer. */
|
|
( void ) otaAgent.pOtaInterface->os.timer.stop( OtaRequestTimer );
|
|
|
|
/* Reset the request momentum. */
|
|
otaAgent.requestMomentum = 0;
|
|
}
|
|
|
|
return retVal;
|
|
}
|
|
|
|
static OtaErr_t processNullFileContext( void )
|
|
{
|
|
OtaErr_t retVal = OtaErrNone;
|
|
OtaEventMsg_t eventMsg = { 0 };
|
|
|
|
/* If the OTA job is in the self_test state, alert the application layer. */
|
|
if( OTA_GetImageState() == OtaImageStateTesting )
|
|
{
|
|
/* Send event to OTA task to start self-test. */
|
|
eventMsg.eventId = OtaAgentEventStartSelfTest;
|
|
|
|
if( OTA_SignalEvent( &eventMsg ) == false )
|
|
{
|
|
retVal = OtaErrSignalEventFailed;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* If the job context returned NULL and the image state is not in the self_test state,
|
|
* then an error occurred parsing the OTA job message. */
|
|
|
|
retVal = OtaErrJobParserError;
|
|
}
|
|
|
|
return retVal;
|
|
}
|
|
|
|
static OtaErr_t processValidFileContext( void )
|
|
{
|
|
OtaErr_t retVal = OtaErrNone;
|
|
OtaEventMsg_t eventMsg = { 0 };
|
|
|
|
/* If the platform is not in the self_test state, initiate file download. */
|
|
if( platformInSelftest() == false )
|
|
{
|
|
/* Init data interface routines */
|
|
retVal = setDataInterface( &otaDataInterface, otaAgent.fileContext.pProtocols );
|
|
|
|
if( retVal == OtaErrNone )
|
|
{
|
|
LogInfo( ( "Setting OTA data interface." ) );
|
|
|
|
/* Received a valid context so send event to request file blocks. */
|
|
eventMsg.eventId = OtaAgentEventCreateFile;
|
|
|
|
/*Send the event to OTA Agent task. */
|
|
if( OTA_SignalEvent( &eventMsg ) == false )
|
|
{
|
|
retVal = OtaErrSignalEventFailed;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Failed to set the data interface so abort the OTA.If there is a valid job id,
|
|
* then a job status update will be sent.
|
|
*/
|
|
LogError( ( "Failed to set OTA data interface: OtaErr_t=%s, aborting current update.", OTA_Err_strerror( retVal ) ) );
|
|
|
|
retVal = setImageStateWithReason( OtaImageStateAborted, ( uint32_t ) retVal );
|
|
|
|
if( retVal != OtaErrNone )
|
|
{
|
|
LogError( ( "Failed to abort OTA update: OtaErr_t=%s", OTA_Err_strerror( retVal ) ) );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Received a job that is not in self-test but platform is, so reboot the device to allow
|
|
* roll back to previous image.
|
|
*/
|
|
LogWarn( ( "Rejecting new image and rebooting:"
|
|
"The platform is in the self-test state while the job is not." ) );
|
|
|
|
( void ) otaAgent.pOtaInterface->pal.reset( &( otaAgent.fileContext ) );
|
|
}
|
|
|
|
return retVal;
|
|
}
|
|
|
|
static OtaErr_t processJobHandler( const OtaEventData_t * pEventData )
|
|
{
|
|
OtaErr_t retVal = OtaErrNone;
|
|
OtaFileContext_t * pOtaFileContext = NULL;
|
|
|
|
/*
|
|
* Parse the job document and update file information in the file context.
|
|
*/
|
|
pOtaFileContext = getFileContextFromJob( ( const char * ) pEventData->data,
|
|
pEventData->dataLength );
|
|
|
|
/*
|
|
* A null context here could either mean we didn't receive a valid job or it could
|
|
* signify that we're in the self test phase (where the OTA file transfer is already
|
|
* completed and we have reset the device and are now running the new firmware). We
|
|
* will check the state to determine which case we're in.
|
|
*/
|
|
if( pOtaFileContext == NULL )
|
|
{
|
|
retVal = processNullFileContext();
|
|
}
|
|
else
|
|
{
|
|
retVal = processValidFileContext();
|
|
}
|
|
|
|
/* Application callback for event processed. */
|
|
otaAgent.OtaAppCallback( OtaJobEventProcessed, ( const void * ) pEventData );
|
|
|
|
return retVal;
|
|
}
|
|
|
|
static OtaErr_t initFileHandler( const OtaEventData_t * pEventData )
|
|
{
|
|
OtaErr_t err = OtaErrUninitialized;
|
|
OtaOsStatus_t osErr = OtaOsSuccess;
|
|
OtaEventMsg_t eventMsg = { 0 };
|
|
|
|
( void ) pEventData;
|
|
|
|
err = otaDataInterface.initFileTransfer( &otaAgent );
|
|
|
|
if( err != OtaErrNone )
|
|
{
|
|
if( otaAgent.requestMomentum < otaconfigMAX_NUM_REQUEST_MOMENTUM )
|
|
{
|
|
/* Start the request timer. */
|
|
osErr = otaAgent.pOtaInterface->os.timer.start( OtaRequestTimer,
|
|
"OtaRequestTimer",
|
|
otaconfigFILE_REQUEST_WAIT_MS,
|
|
otaTimerCallback );
|
|
|
|
if( osErr != OtaOsSuccess )
|
|
{
|
|
LogError( ( "Failed to start request timer: "
|
|
"OtaOsStatus_t=%s",
|
|
OTA_OsStatus_strerror( osErr ) ) );
|
|
err = OtaErrInitFileTransferFailed;
|
|
}
|
|
else
|
|
{
|
|
otaAgent.requestMomentum++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Stop the request timer. */
|
|
( void ) otaAgent.pOtaInterface->os.timer.stop( OtaRequestTimer );
|
|
|
|
/* Send shutdown event. */
|
|
eventMsg.eventId = OtaAgentEventShutdown;
|
|
|
|
if( OTA_SignalEvent( &eventMsg ) == false )
|
|
{
|
|
err = OtaErrSignalEventFailed;
|
|
}
|
|
else
|
|
{
|
|
/* Too many requests have been sent without a response or too many failures
|
|
* when trying to publish the request message. Abort. */
|
|
err = OtaErrMomentumAbort;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Reset the request momentum. */
|
|
otaAgent.requestMomentum = 0;
|
|
|
|
/* Reset the OTA statistics. */
|
|
( void ) memset( &otaAgent.statistics, 0, sizeof( otaAgent.statistics ) );
|
|
|
|
eventMsg.eventId = OtaAgentEventRequestFileBlock;
|
|
|
|
if( OTA_SignalEvent( &eventMsg ) == false )
|
|
{
|
|
err = OtaErrSignalEventFailed;
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static OtaErr_t requestDataHandler( const OtaEventData_t * pEventData )
|
|
{
|
|
OtaErr_t err = OtaErrNone;
|
|
OtaOsStatus_t osErr = OtaOsSuccess;
|
|
OtaEventMsg_t eventMsg = { 0 };
|
|
|
|
( void ) pEventData;
|
|
|
|
if( otaAgent.fileContext.blocksRemaining > 0U )
|
|
{
|
|
/* Start the request timer. */
|
|
osErr = otaAgent.pOtaInterface->os.timer.start( OtaRequestTimer,
|
|
"OtaRequestTimer",
|
|
otaconfigFILE_REQUEST_WAIT_MS,
|
|
otaTimerCallback );
|
|
|
|
if( ( osErr == OtaOsSuccess ) && ( otaAgent.requestMomentum < otaconfigMAX_NUM_REQUEST_MOMENTUM ) )
|
|
{
|
|
/* Request data blocks. */
|
|
err = otaDataInterface.requestFileBlock( &otaAgent );
|
|
|
|
/* Each request increases the momentum until a response is received. Too much momentum is
|
|
* interpreted as a failure to communicate and will cause us to abort the OTA. */
|
|
otaAgent.requestMomentum++;
|
|
}
|
|
else
|
|
{
|
|
/* Stop the request timer. */
|
|
( void ) otaAgent.pOtaInterface->os.timer.stop( OtaRequestTimer );
|
|
|
|
/* Failed to send data request abort and close file. */
|
|
err = setImageStateWithReason( OtaImageStateAborted, ( uint32_t ) err );
|
|
|
|
if( err != OtaErrNone )
|
|
{
|
|
LogError( ( "Failed to abort OTA update: OtaErr_t=%s", OTA_Err_strerror( err ) ) );
|
|
}
|
|
|
|
/* Send shutdown event. */
|
|
eventMsg.eventId = OtaAgentEventShutdown;
|
|
|
|
if( OTA_SignalEvent( &eventMsg ) == false )
|
|
{
|
|
err = OtaErrSignalEventFailed;
|
|
}
|
|
else
|
|
{
|
|
/* Too many requests have been sent without a response or too many failures
|
|
* when trying to publish the request message. Abort. */
|
|
err = OtaErrMomentumAbort;
|
|
|
|
/* Reset the request momentum. */
|
|
otaAgent.requestMomentum = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static void dataHandlerCleanup( void )
|
|
{
|
|
OtaEventMsg_t eventMsg = { 0 };
|
|
|
|
/* Stop the request timer. */
|
|
( void ) otaAgent.pOtaInterface->os.timer.stop( OtaRequestTimer );
|
|
|
|
/* Send event to close file. */
|
|
eventMsg.eventId = OtaAgentEventCloseFile;
|
|
|
|
if( OTA_SignalEvent( &eventMsg ) == false )
|
|
{
|
|
LogWarn( ( "Failed to trigger closing file: "
|
|
"Unable to signal event: "
|
|
"event=%d",
|
|
eventMsg.eventId ) );
|
|
}
|
|
|
|
/* Clear any remaining string memory holding the job name since this job is done. */
|
|
( void ) memset( otaAgent.pActiveJobName, 0, OTA_JOB_ID_MAX_SIZE );
|
|
}
|
|
|
|
static OtaErr_t processDataHandler( const OtaEventData_t * pEventData )
|
|
{
|
|
OtaErr_t err = OtaErrNone;
|
|
OtaPalStatus_t closeResult = OTA_PAL_COMBINE_ERR( OtaPalUninitialized, 0 );
|
|
OtaEventMsg_t eventMsg = { 0 };
|
|
IngestResult_t result = IngestResultUninitialized;
|
|
OtaJobDocument_t jobDoc = { 0 };
|
|
OtaJobEvent_t otaJobEvent = OtaLastJobEvent;
|
|
|
|
/* Get the file context. */
|
|
OtaFileContext_t * pFileContext = &( otaAgent.fileContext );
|
|
|
|
/* Set the job id and length from OTA context. */
|
|
jobDoc.pJobId = otaAgent.pActiveJobName;
|
|
jobDoc.jobIdLength = strlen( ( const char * ) otaAgent.pActiveJobName ) + 1U;
|
|
jobDoc.fileTypeId = otaAgent.fileContext.fileType;
|
|
|
|
/* Ingest data blocks received. */
|
|
if( pEventData != NULL )
|
|
{
|
|
result = ingestDataBlock( pFileContext,
|
|
pEventData->data,
|
|
pEventData->dataLength,
|
|
&closeResult );
|
|
}
|
|
else
|
|
{
|
|
result = IngestResultNullInput;
|
|
}
|
|
|
|
if( result == IngestResultFileComplete )
|
|
{
|
|
/* Check if this is firmware update. */
|
|
if( otaAgent.fileContext.fileType == configOTA_FIRMWARE_UPDATE_FILE_TYPE_ID )
|
|
{
|
|
jobDoc.status = JobStatusInProgress;
|
|
jobDoc.reason = JobReasonSigCheckPassed;
|
|
|
|
otaJobEvent = OtaJobEventActivate;
|
|
}
|
|
else
|
|
{
|
|
jobDoc.status = JobStatusSucceeded;
|
|
jobDoc.reason = JobReasonAccepted;
|
|
jobDoc.subReason = ( int32_t ) otaAgent.fileContext.fileType;
|
|
|
|
otaJobEvent = OtaJobEventUpdateComplete;
|
|
}
|
|
|
|
/* File receive is complete and authenticated. Update the job status. */
|
|
err = otaControlInterface.updateJobStatus( &otaAgent, jobDoc.status, jobDoc.reason, jobDoc.subReason );
|
|
|
|
dataHandlerCleanup();
|
|
|
|
/* Last file block processed, increment the statistics. */
|
|
otaAgent.statistics.otaPacketsProcessed++;
|
|
|
|
/* Let main application know that update is complete */
|
|
otaAgent.OtaAppCallback( otaJobEvent, &jobDoc );
|
|
}
|
|
else if( result < IngestResultFileComplete )
|
|
{
|
|
LogError( ( "Failed to ingest data block, rejecting image: ingestDataBlock returned error: OtaErr_t=%d", result ) );
|
|
|
|
/* Call the platform specific code to reject the image. */
|
|
( void ) otaAgent.pOtaInterface->pal.setPlatformImageState( &( otaAgent.fileContext ), OtaImageStateRejected );
|
|
|
|
jobDoc.status = JobStatusFailedWithVal;
|
|
jobDoc.reason = ( int32_t ) closeResult;
|
|
jobDoc.subReason = result;
|
|
|
|
/* Update the job status with the with failure code. */
|
|
err = otaControlInterface.updateJobStatus( &otaAgent, JobStatusFailedWithVal, ( int32_t ) closeResult, ( int32_t ) result );
|
|
|
|
dataHandlerCleanup();
|
|
|
|
/* Let main application know activate event. */
|
|
otaAgent.OtaAppCallback( OtaJobEventFail, &jobDoc );
|
|
}
|
|
else
|
|
{
|
|
if( result == IngestResultAccepted_Continue )
|
|
{
|
|
/* File block processed, increment the statistics. */
|
|
otaAgent.statistics.otaPacketsProcessed++;
|
|
|
|
/* Reset the momentum counter since we received a good block. */
|
|
otaAgent.requestMomentum = 0;
|
|
/* We're actively receiving a file so update the job status as needed. */
|
|
err = otaControlInterface.updateJobStatus( &otaAgent, JobStatusInProgress, JobReasonReceiving, 0 );
|
|
}
|
|
|
|
if( otaAgent.numOfBlocksToReceive > 1U )
|
|
{
|
|
otaAgent.numOfBlocksToReceive--;
|
|
}
|
|
else
|
|
{
|
|
/* Start the request timer. */
|
|
( void ) otaAgent.pOtaInterface->os.timer.start( OtaRequestTimer, "OtaRequestTimer", otaconfigFILE_REQUEST_WAIT_MS, otaTimerCallback );
|
|
|
|
eventMsg.eventId = OtaAgentEventRequestFileBlock;
|
|
|
|
if( OTA_SignalEvent( &eventMsg ) == false )
|
|
{
|
|
LogWarn( ( "Failed to trigger requesting the next block: Unable to signal event=%d", eventMsg.eventId ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Application callback for event processed. */
|
|
otaAgent.OtaAppCallback( OtaJobEventProcessed, ( const void * ) pEventData );
|
|
|
|
if( err != OtaErrNone )
|
|
{
|
|
LogError( ( "Failed to update job status: updateJobStatus returned error: OtaErr_t=%s",
|
|
OTA_Err_strerror( err ) ) );
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static OtaErr_t closeFileHandler( const OtaEventData_t * pEventData )
|
|
{
|
|
( void ) pEventData;
|
|
|
|
LogInfo( ( "Closing file: "
|
|
"file index=%u",
|
|
otaAgent.fileIndex ) );
|
|
|
|
( void ) otaClose( &( otaAgent.fileContext ) );
|
|
|
|
return OtaErrNone;
|
|
}
|
|
|
|
static OtaErr_t userAbortHandler( const OtaEventData_t * pEventData )
|
|
{
|
|
OtaErr_t err = OtaErrNone;
|
|
|
|
( void ) pEventData;
|
|
|
|
/* If we have active Job abort it and close the file. */
|
|
if( strlen( ( const char * ) otaAgent.pActiveJobName ) > 0u )
|
|
{
|
|
err = setImageStateWithReason( OtaImageStateAborted, ( uint32_t ) OtaErrUserAbort );
|
|
|
|
if( err == OtaErrNone )
|
|
{
|
|
( void ) otaClose( &( otaAgent.fileContext ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
err = OtaErrNoActiveJob;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static OtaErr_t shutdownHandler( const OtaEventData_t * pEventData )
|
|
{
|
|
( void ) pEventData;
|
|
|
|
LogInfo( ( "OTA Agent is shutting down." ) );
|
|
|
|
/* If we're here, we're shutting down the OTA agent. Free up all resources and quit. */
|
|
agentShutdownCleanup();
|
|
|
|
/* Clear the entire agent context. This includes the OTA agent state. */
|
|
( void ) memset( &otaAgent, 0, sizeof( otaAgent ) );
|
|
|
|
return OtaErrNone;
|
|
}
|
|
|
|
static OtaErr_t suspendHandler( const OtaEventData_t * pEventData )
|
|
{
|
|
( void ) pEventData;
|
|
|
|
/* Log the state change to suspended state.*/
|
|
LogInfo( ( "OTA Agent is suspended." ) );
|
|
|
|
return OtaErrNone;
|
|
}
|
|
|
|
static OtaErr_t resumeHandler( const OtaEventData_t * pEventData )
|
|
{
|
|
OtaEventMsg_t eventMsg = { 0 };
|
|
|
|
( void ) pEventData;
|
|
|
|
/*
|
|
* Send signal to request job document.
|
|
*/
|
|
eventMsg.eventId = OtaAgentEventRequestJobDocument;
|
|
|
|
return ( OTA_SignalEvent( &eventMsg ) == true ) ? OtaErrNone : OtaErrSignalEventFailed;
|
|
}
|
|
|
|
static OtaErr_t jobNotificationHandler( const OtaEventData_t * pEventData )
|
|
{
|
|
OtaEventMsg_t eventMsg = { 0 };
|
|
|
|
( void ) pEventData;
|
|
|
|
/* Stop the request timer. */
|
|
( void ) otaAgent.pOtaInterface->os.timer.stop( OtaRequestTimer );
|
|
|
|
/* Abort the current job. */
|
|
( void ) otaAgent.pOtaInterface->pal.setPlatformImageState( &( otaAgent.fileContext ), OtaImageStateAborted );
|
|
( void ) otaClose( &( otaAgent.fileContext ) );
|
|
|
|
/* Clear the active job name as its no longer required. */
|
|
( void ) memset( otaAgent.pActiveJobName, 0, OTA_JOB_ID_MAX_SIZE );
|
|
|
|
/*
|
|
* Send signal to request next OTA job document from service.
|
|
*/
|
|
eventMsg.eventId = OtaAgentEventRequestJobDocument;
|
|
|
|
return ( OTA_SignalEvent( &eventMsg ) == true ) ? OtaErrNone : OtaErrSignalEventFailed;
|
|
}
|
|
|
|
static void freeFileContextMem( OtaFileContext_t * const pFileContext )
|
|
{
|
|
assert( pFileContext != NULL );
|
|
|
|
/* Free or clear the filepath buffer.*/
|
|
if( pFileContext->pFilePath != NULL )
|
|
{
|
|
if( pFileContext->filePathMaxSize > 0u )
|
|
{
|
|
( void ) memset( pFileContext->pFilePath, 0, pFileContext->filePathMaxSize );
|
|
}
|
|
else
|
|
{
|
|
otaAgent.pOtaInterface->os.mem.free( pFileContext->pFilePath );
|
|
pFileContext->pFilePath = NULL;
|
|
}
|
|
}
|
|
|
|
/* Free or clear the certfile path buffer.*/
|
|
if( pFileContext->pCertFilepath != NULL )
|
|
{
|
|
if( pFileContext->certFilePathMaxSize > 0u )
|
|
{
|
|
( void ) memset( pFileContext->pCertFilepath, 0, pFileContext->certFilePathMaxSize );
|
|
}
|
|
else
|
|
{
|
|
otaAgent.pOtaInterface->os.mem.free( pFileContext->pCertFilepath );
|
|
pFileContext->pCertFilepath = NULL;
|
|
}
|
|
}
|
|
|
|
/* Free or clear the streamname buffer.*/
|
|
if( pFileContext->pStreamName != NULL )
|
|
{
|
|
if( pFileContext->streamNameMaxSize > 0u )
|
|
{
|
|
( void ) memset( pFileContext->pStreamName, 0, pFileContext->streamNameMaxSize );
|
|
}
|
|
else
|
|
{
|
|
otaAgent.pOtaInterface->os.mem.free( pFileContext->pStreamName );
|
|
pFileContext->pStreamName = NULL;
|
|
}
|
|
}
|
|
|
|
/* Free or clear the bitmap buffer.*/
|
|
if( pFileContext->pRxBlockBitmap != NULL )
|
|
{
|
|
if( pFileContext->blockBitmapMaxSize > 0u )
|
|
{
|
|
( void ) memset( pFileContext->pRxBlockBitmap, 0, pFileContext->blockBitmapMaxSize );
|
|
}
|
|
else
|
|
{
|
|
otaAgent.pOtaInterface->os.mem.free( pFileContext->pRxBlockBitmap );
|
|
pFileContext->pRxBlockBitmap = NULL;
|
|
}
|
|
}
|
|
|
|
/* Free or clear url buffer.*/
|
|
if( pFileContext->pUpdateUrlPath != NULL )
|
|
{
|
|
if( pFileContext->updateUrlMaxSize > 0u )
|
|
{
|
|
( void ) memset( pFileContext->pUpdateUrlPath, 0, pFileContext->updateUrlMaxSize );
|
|
}
|
|
else
|
|
{
|
|
otaAgent.pOtaInterface->os.mem.free( pFileContext->pUpdateUrlPath );
|
|
pFileContext->pUpdateUrlPath = NULL;
|
|
}
|
|
}
|
|
|
|
/* Initialize auth scheme buffer from application buffer.*/
|
|
if( pFileContext->pAuthScheme != NULL )
|
|
{
|
|
if( pFileContext->authSchemeMaxSize > 0u )
|
|
{
|
|
( void ) memset( pFileContext->pAuthScheme, 0, pFileContext->authSchemeMaxSize );
|
|
}
|
|
else
|
|
{
|
|
otaAgent.pOtaInterface->os.mem.free( pFileContext->pAuthScheme );
|
|
pFileContext->pAuthScheme = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Close an existing OTA file context and free its resources. */
|
|
|
|
static bool otaClose( OtaFileContext_t * const pFileContext )
|
|
{
|
|
bool result = false;
|
|
|
|
LogDebug( ( "Attempting to close OTA file context: "
|
|
"file context address=0x%p",
|
|
( void * ) pFileContext ) );
|
|
|
|
/* Cleanup related to selected protocol. */
|
|
if( otaDataInterface.cleanup != NULL )
|
|
{
|
|
( void ) otaDataInterface.cleanup( &otaAgent );
|
|
}
|
|
|
|
if( pFileContext != NULL )
|
|
{
|
|
/*
|
|
* Abort any active file access and release the file resource, if needed.
|
|
*/
|
|
( void ) otaAgent.pOtaInterface->pal.abort( pFileContext );
|
|
|
|
freeFileContextMem( &( otaAgent.fileContext ) );
|
|
|
|
result = true;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/* Validate JSON document and the DocModel*/
|
|
static DocParseErr_t validateJSON( const char * pJson,
|
|
uint32_t messageLength )
|
|
{
|
|
DocParseErr_t err = DocParseErrNone;
|
|
JSONStatus_t result;
|
|
|
|
/* Check JSON document pointer is valid.*/
|
|
if( pJson == NULL )
|
|
{
|
|
LogError( ( "Parameter check failed: pJson is NULL." ) );
|
|
err = DocParseErrNullDocPointer;
|
|
}
|
|
|
|
/* Check if the JSON document is valid*/
|
|
if( err == DocParseErrNone )
|
|
{
|
|
result = JSON_Validate( pJson, ( size_t ) messageLength );
|
|
|
|
if( result != JSONSuccess )
|
|
{
|
|
LogError( ( "Invalid JSON document: "
|
|
"JSON_Validate returned error: "
|
|
"JSONStatus_t=%d",
|
|
result ) );
|
|
err = DocParseErr_InvalidJSONBuffer;
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
/* Decode the base64 encoded file signature key from the job document and store it in file context*/
|
|
|
|
static DocParseErr_t decodeAndStoreKey( const char * pValueInJson,
|
|
size_t valueLength,
|
|
void * pParamAdd )
|
|
{
|
|
DocParseErr_t err = DocParseErrNone;
|
|
size_t actualLen = 0;
|
|
Base64Status_t base64Status = 0;
|
|
Sig256_t ** pSig256 = pParamAdd;
|
|
|
|
/* pSig256 should point to pSignature in OtaFileContext_t, which is statically allocated. */
|
|
assert( *pSig256 != NULL );
|
|
|
|
base64Status = base64Decode( ( *pSig256 )->data,
|
|
sizeof( ( *pSig256 )->data ),
|
|
&actualLen,
|
|
( const uint8_t * ) pValueInJson,
|
|
valueLength );
|
|
|
|
if( base64Status != Base64Success )
|
|
{
|
|
/* Stop processing on error. */
|
|
LogError( ( "Failed to decode Base64 data: "
|
|
"base64Decode returned error: "
|
|
"error=%d",
|
|
base64Status ) );
|
|
err = DocParseErrBase64Decode;
|
|
}
|
|
else
|
|
{
|
|
char pLogBuffer[ 33 ];
|
|
( void ) strncpy( pLogBuffer, pValueInJson, 32 );
|
|
pLogBuffer[ 32 ] = '\0';
|
|
LogInfo( ( "Extracted parameter [ %s: %s... ]",
|
|
OTA_JsonFileSignatureKey,
|
|
pLogBuffer ) );
|
|
|
|
|
|
( *pSig256 )->size = ( uint16_t ) actualLen;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
/* Extract the value from json and store it into the allocated memory. */
|
|
|
|
static DocParseErr_t extractAndStoreArray( const char * pKey,
|
|
const char * pValueInJson,
|
|
size_t valueLength,
|
|
void * pParamAdd,
|
|
uint32_t * pParamSizeAdd )
|
|
{
|
|
DocParseErr_t err = DocParseErrNone;
|
|
|
|
/* For string and array, pParamAdd should be pointing to a uint8_t pointer. */
|
|
char ** pCharPtr = pParamAdd;
|
|
|
|
( void ) pKey; /* For suppressing compiler-warning: unused variable. */
|
|
|
|
if( *pParamSizeAdd == 0U )
|
|
{
|
|
/* Free previously allocated buffer. */
|
|
if( *pCharPtr != NULL )
|
|
{
|
|
otaAgent.pOtaInterface->os.mem.free( *pCharPtr );
|
|
}
|
|
|
|
/* Malloc memory for a copy of the value string plus a zero terminator. */
|
|
*pCharPtr = otaAgent.pOtaInterface->os.mem.malloc( valueLength + 1U );
|
|
|
|
if( *pCharPtr == NULL )
|
|
{
|
|
/* Stop processing on error. */
|
|
err = DocParseErrOutOfMemory;
|
|
|
|
LogError( ( "Memory allocation failed "
|
|
"[key: valueLength]=[%s: %lu]",
|
|
pKey,
|
|
( unsigned long ) valueLength ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( *pParamSizeAdd < ( valueLength + 1U ) )
|
|
{
|
|
err = DocParseErrUserBufferInsuffcient;
|
|
|
|
LogError( ( "Insufficient user memory: "
|
|
"[key: valueLength]=[%s: %lu]",
|
|
pKey,
|
|
( unsigned long ) valueLength ) );
|
|
}
|
|
}
|
|
|
|
if( err == DocParseErrNone )
|
|
{
|
|
/* Copy parameter string into newly allocated memory. */
|
|
( void ) memcpy( *pCharPtr, pValueInJson, valueLength );
|
|
|
|
/* Zero terminate the new string. */
|
|
( *pCharPtr )[ valueLength ] = '\0';
|
|
|
|
LogInfo( ( "Extracted parameter: "
|
|
"[key: value]=[%s: %s]",
|
|
pKey,
|
|
*pCharPtr ) );
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
/* Store the parameter from the json to the offset specified by the document model. */
|
|
|
|
static DocParseErr_t extractParameter( JsonDocParam_t docParam,
|
|
void * pContextBase,
|
|
const char * pValueInJson,
|
|
size_t valueLength )
|
|
{
|
|
DocParseErr_t err = DocParseErrNone;
|
|
void * pParamAdd;
|
|
uint32_t * pParamSizeAdd;
|
|
|
|
/* Get destination offset to parameter storage location.*/
|
|
pParamAdd = ( uint8_t * ) pContextBase + docParam.pDestOffset;
|
|
|
|
/* Get destination buffer size to parameter storage location. */
|
|
pParamSizeAdd = ( void * ) ( ( uint8_t * ) pContextBase + docParam.pDestSizeOffset );
|
|
|
|
if( ( ModelParamTypeStringCopy == docParam.modelParamType ) || ( ModelParamTypeArrayCopy == docParam.modelParamType ) )
|
|
{
|
|
err = extractAndStoreArray( docParam.pSrcKey, pValueInJson, valueLength, pParamAdd, pParamSizeAdd );
|
|
}
|
|
else if( ModelParamTypeUInt32 == docParam.modelParamType )
|
|
{
|
|
uint32_t * pUint32 = pParamAdd;
|
|
char * pEnd;
|
|
const char * pStart = pValueInJson;
|
|
errno = 0;
|
|
|
|
*pUint32 = ( uint32_t ) strtoul( pStart, &pEnd, 0 );
|
|
|
|
if( ( errno == 0 ) && ( pEnd == &pValueInJson[ valueLength ] ) )
|
|
{
|
|
LogInfo( ( "Extracted parameter: [key: value]=[%s: %u]",
|
|
docParam.pSrcKey, *pUint32 ) );
|
|
}
|
|
else
|
|
{
|
|
err = DocParseErrInvalidNumChar;
|
|
}
|
|
}
|
|
else if( ModelParamTypeSigBase64 == docParam.modelParamType )
|
|
{
|
|
err = decodeAndStoreKey( pValueInJson, valueLength, pParamAdd );
|
|
}
|
|
else if( ModelParamTypeIdent == docParam.modelParamType )
|
|
{
|
|
LogDebug( ( "Identified parameter: [ %s ]",
|
|
docParam.pSrcKey ) );
|
|
|
|
*( bool * ) pParamAdd = true;
|
|
}
|
|
else
|
|
{
|
|
LogWarn( ( "Invalid parameter type: %d", docParam.modelParamType ) );
|
|
}
|
|
|
|
if( err != DocParseErrNone )
|
|
{
|
|
LogDebug( ( "Failed to extract document parameter: error=%d, paramter key=%s",
|
|
err, docParam.pSrcKey ) );
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
/* Check if all the required parameters for job document are extracted from the JSON */
|
|
|
|
static DocParseErr_t verifyRequiredParamsExtracted( const JsonDocParam_t * pModelParam,
|
|
const JsonDocModel_t * pDocModel )
|
|
{
|
|
uint32_t scanIndex = 0;
|
|
DocParseErr_t err = DocParseErrNone;
|
|
uint32_t missingParams = ( pDocModel->paramsReceivedBitmap & pDocModel->paramsRequiredBitmap )
|
|
^ pDocModel->paramsRequiredBitmap;
|
|
|
|
( void ) pModelParam; /* For suppressing compiler-warning: unused variable. */
|
|
|
|
if( missingParams != 0U )
|
|
{
|
|
/* The job document did not have all required document model parameters. */
|
|
for( scanIndex = 0U; scanIndex < pDocModel->numModelParams; scanIndex++ )
|
|
{
|
|
if( ( missingParams & ( ( uint32_t ) 1U << scanIndex ) ) != 0U )
|
|
{
|
|
LogDebug( ( "Failed job document content check: "
|
|
"Required job document parameter was not extracted: "
|
|
"parameter=%s",
|
|
pModelParam[ scanIndex ].pSrcKey ) );
|
|
}
|
|
}
|
|
|
|
err = DocParseErrMalformedDoc;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
/* Extract the desired fields from the JSON document based on the specified document model. */
|
|
|
|
static DocParseErr_t parseJSONbyModel( const char * pJson,
|
|
uint32_t messageLength,
|
|
JsonDocModel_t * pDocModel )
|
|
{
|
|
const JsonDocParam_t * pModelParam = NULL;
|
|
DocParseErr_t err;
|
|
JSONStatus_t result;
|
|
uint16_t paramIndex = 0;
|
|
const char * pFileParams = NULL;
|
|
uint32_t fileParamsLength = 0;
|
|
|
|
LogDebug( ( "JSON received: %s", pJson ) );
|
|
|
|
/* Fetch the model parameters from the DocModel*/
|
|
pModelParam = pDocModel->pBodyDef;
|
|
|
|
/* Check the validity of the JSON document */
|
|
err = validateJSON( pJson, messageLength );
|
|
|
|
/* Traverse the docModel and search the JSON if it containing the Source Key specified*/
|
|
for( paramIndex = 0; paramIndex < pDocModel->numModelParams; paramIndex++ )
|
|
{
|
|
const char * pQueryKey = pDocModel->pBodyDef[ paramIndex ].pSrcKey;
|
|
size_t queryKeyLength = strlen( pQueryKey );
|
|
const char * pValueInJson = NULL;
|
|
size_t valueLength = 0;
|
|
result = JSON_SearchConst( pJson, messageLength, pQueryKey, queryKeyLength, &pValueInJson, &valueLength, NULL );
|
|
|
|
/* If not found in pJSon search for the key in FileParameters JSON*/
|
|
if( ( result != JSONSuccess ) && ( pFileParams != NULL ) )
|
|
{
|
|
result = JSON_SearchConst( pFileParams, fileParamsLength, pQueryKey, queryKeyLength, &pValueInJson, &valueLength, NULL );
|
|
}
|
|
|
|
if( result == JSONSuccess )
|
|
{
|
|
/* Mark parameter as received in the bitmap. */
|
|
pDocModel->paramsReceivedBitmap |= ( ( uint32_t ) 1U << paramIndex );
|
|
|
|
if( OTA_DONT_STORE_PARAM == ( int32_t ) pModelParam[ paramIndex ].pDestOffset )
|
|
{
|
|
/* Do nothing if we don't need to store the parameter */
|
|
continue;
|
|
}
|
|
else if( OTA_STORE_NESTED_JSON == pModelParam[ paramIndex ].pDestOffset )
|
|
{
|
|
pFileParams = pValueInJson + 1;
|
|
fileParamsLength = ( uint32_t ) valueLength - 2U;
|
|
}
|
|
else
|
|
{
|
|
err = extractParameter( pModelParam[ paramIndex ],
|
|
pDocModel->contextBase,
|
|
pValueInJson,
|
|
valueLength );
|
|
}
|
|
|
|
if( err != DocParseErrNone )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( err == DocParseErrNone )
|
|
{
|
|
err = verifyRequiredParamsExtracted( pModelParam, pDocModel );
|
|
}
|
|
|
|
if( err != DocParseErrNone )
|
|
{
|
|
LogDebug( ( "Failed to parse JSON document as AFR_OTA job: "
|
|
"DocParseErr_t=%d",
|
|
err ) );
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
/* Prepare the document model for use by sanity checking the initialization parameters
|
|
* and detecting all required parameters. */
|
|
|
|
static DocParseErr_t initDocModel( JsonDocModel_t * pDocModel,
|
|
const JsonDocParam_t * pBodyDef,
|
|
void * contextBaseAddr,
|
|
uint32_t contextSize,
|
|
uint16_t numJobParams )
|
|
{
|
|
DocParseErr_t err = DocParseErrUnknown;
|
|
uint32_t scanIndex;
|
|
|
|
/* Sanity check the model pointers and parameter count. Exclude the context base address and size since
|
|
* it is technically possible to create a model that writes entirely into absolute memory locations.
|
|
*/
|
|
if( pDocModel == NULL )
|
|
{
|
|
LogError( ( "Parameter check failed: pDocModel is NULL." ) );
|
|
err = DocParseErrNullModelPointer;
|
|
}
|
|
else if( pBodyDef == NULL )
|
|
{
|
|
LogError( ( "Parameter check failed: pBodyDef is NULL." ) );
|
|
err = DocParseErrNullBodyPointer;
|
|
}
|
|
else if( numJobParams > OTA_DOC_MODEL_MAX_PARAMS )
|
|
{
|
|
LogError( ( "Parameter check failed: "
|
|
"Document model has %u parameters: "
|
|
"Document model should have <= %u parameters.",
|
|
numJobParams,
|
|
OTA_DOC_MODEL_MAX_PARAMS ) );
|
|
err = DocParseErrTooManyParams;
|
|
}
|
|
else
|
|
{
|
|
pDocModel->contextBase = contextBaseAddr;
|
|
pDocModel->contextSize = contextSize;
|
|
pDocModel->pBodyDef = pBodyDef;
|
|
pDocModel->numModelParams = numJobParams;
|
|
pDocModel->paramsReceivedBitmap = 0;
|
|
pDocModel->paramsRequiredBitmap = 0;
|
|
|
|
/* Scan the model and detect all required parameters (i.e. not optional). */
|
|
for( scanIndex = 0; scanIndex < pDocModel->numModelParams; scanIndex++ )
|
|
{
|
|
if( pDocModel->pBodyDef[ scanIndex ].required == true )
|
|
{
|
|
/* Add parameter to the required bitmap. */
|
|
pDocModel->paramsRequiredBitmap |= ( ( uint32_t ) 1U << scanIndex );
|
|
}
|
|
}
|
|
|
|
err = DocParseErrNone;
|
|
}
|
|
|
|
if( err != DocParseErrNone )
|
|
{
|
|
LogError( ( "Failed to initialize document model: "
|
|
"DocParseErr_t=%d", err ) );
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* Validate the version of the update received.
|
|
*/
|
|
static OtaErr_t validateUpdateVersion( const OtaFileContext_t * pFileContext )
|
|
{
|
|
OtaErr_t err = OtaErrNone;
|
|
AppVersion32_t previousVersion;
|
|
|
|
( void ) previousVersion; /* For suppressing compiler-warning: unused variable. */
|
|
|
|
/* Only check for versions if the target is self */
|
|
if( ( otaAgent.serverFileID == 0U ) && ( otaAgent.fileContext.fileType == configOTA_FIRMWARE_UPDATE_FILE_TYPE_ID ) )
|
|
{
|
|
/* Check if version reported is the same as the running version. */
|
|
if( pFileContext->updaterVersion == appFirmwareVersion.u.unsignedVersion32 )
|
|
{
|
|
/* The version is the same so either we're not actually the new firmware or
|
|
* someone messed up and sent firmware with the same version. In either case,
|
|
* this is a failure of the OTA update so reject the job.
|
|
*/
|
|
LogWarn( ( "Application version of the new image is identical to the current image: "
|
|
"New images are expected to have a higher version number: " ) );
|
|
|
|
err = OtaErrSameFirmwareVersion;
|
|
}
|
|
/* Check if update version received is older than current version.*/
|
|
else if( pFileContext->updaterVersion > appFirmwareVersion.u.unsignedVersion32 )
|
|
{
|
|
LogWarn( ( "Application version of the new image is lower than the current image: "
|
|
"New images are expected to have a higher version number." ) );
|
|
err = OtaErrDowngradeNotAllowed;
|
|
}
|
|
|
|
/* pFileContext->updaterVersion < appFirmwareVersion.u.unsignedVersion32 is true.
|
|
* Update version received is newer than current version. */
|
|
else
|
|
{
|
|
previousVersion.u.unsignedVersion32 = pFileContext->updaterVersion;
|
|
LogInfo( ( "New image has a higher version number than the current image: "
|
|
"New image version=%u.%u.%u"
|
|
", Previous image version=%u.%u.%u",
|
|
appFirmwareVersion.u.x.major, appFirmwareVersion.u.x.minor, appFirmwareVersion.u.x.build,
|
|
previousVersion.u.x.major, previousVersion.u.x.minor, previousVersion.u.x.build ) );
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
/* If there is an error in parsing the json, check if it can be handled by external callback. */
|
|
static OtaJobParseErr_t handleCustomJob( const char * pJson,
|
|
uint32_t messageLength )
|
|
{
|
|
OtaErr_t otaErr = OtaErrNone;
|
|
OtaJobParseErr_t err = OtaJobParseErrUnknown;
|
|
OtaJobDocument_t jobDoc = { 0 };
|
|
const char * jobDocValue = NULL;
|
|
const char * jobIdValue = NULL;
|
|
|
|
jobDoc.parseErr = OtaJobParseErrUnknown;
|
|
|
|
/* If this is a valid custom job, extract job document and job ID data from the JSON payload.*/
|
|
if( ( JSONSuccess == JSON_SearchConst( pJson,
|
|
messageLength,
|
|
OTA_JSON_JOB_DOC_KEY,
|
|
strlen( OTA_JSON_JOB_DOC_KEY ),
|
|
&jobDocValue,
|
|
&jobDoc.jobDocLength,
|
|
NULL ) ) &&
|
|
|
|
( JSONSuccess == JSON_SearchConst( pJson,
|
|
messageLength,
|
|
OTA_JSON_JOB_ID_KEY,
|
|
strlen( OTA_JSON_JOB_ID_KEY ),
|
|
&jobIdValue,
|
|
&jobDoc.jobIdLength,
|
|
NULL ) ) )
|
|
{
|
|
if( ( jobDoc.jobIdLength > 0U ) && ( jobDoc.jobIdLength <= OTA_JOB_ID_MAX_SIZE ) ) /* LCOV_EXCL_BR_LINE */
|
|
{
|
|
jobDoc.pJobDocJson = ( const uint8_t * ) jobDocValue;
|
|
jobDoc.pJobId = ( const uint8_t * ) jobIdValue;
|
|
( void ) memcpy( otaAgent.pActiveJobName, jobDoc.pJobId, jobDoc.jobIdLength );
|
|
}
|
|
else
|
|
{
|
|
jobDoc.parseErr = OtaJobParseErrNonConformingJobDoc;
|
|
}
|
|
|
|
/* We have an unknown job parser error. Check to see if we can pass control
|
|
* to a callback for parsing */
|
|
otaAgent.OtaAppCallback( OtaJobEventParseCustomJob, &jobDoc );
|
|
|
|
if( jobDoc.parseErr == OtaJobParseErrNone )
|
|
{
|
|
otaErr = otaControlInterface.updateJobStatus( &otaAgent, jobDoc.status, jobDoc.reason, jobDoc.subReason );
|
|
|
|
LogInfo( ( "Job document parsed from external callback" ) );
|
|
err = jobDoc.parseErr;
|
|
|
|
/* We don't need the job name memory anymore since we're done with this job. */
|
|
( void ) memset( otaAgent.pActiveJobName, 0, OTA_JOB_ID_MAX_SIZE );
|
|
}
|
|
else
|
|
{
|
|
/* Job is malformed - return an error */
|
|
err = OtaJobParseErrNonConformingJobDoc;
|
|
|
|
LogDebug( ( "Failed to parse custom job document: OtaJobParseErr_t=%s, jobIdLength=%lu",
|
|
OTA_JobParse_strerror( jobDoc.parseErr ), ( unsigned long ) jobDoc.jobIdLength ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* No active jobs present.*/
|
|
err = OtaJobParseErrNoActiveJobs;
|
|
}
|
|
|
|
/* Log the error.*/
|
|
if( otaErr != OtaErrNone )
|
|
{
|
|
LogError( ( "Failed to update job status: updateJobStatus returned error: OtaErr_t=%s",
|
|
OTA_Err_strerror( otaErr ) ) );
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
/* Check if the incoming job document is not conflicting with current job status. */
|
|
|
|
static OtaJobParseErr_t verifyActiveJobStatus( OtaFileContext_t * pFileContext,
|
|
OtaFileContext_t ** pFinalFile,
|
|
bool * pUpdateJob )
|
|
{
|
|
OtaJobParseErr_t err = OtaJobParseErrNone;
|
|
|
|
if( pFileContext->pJobName != NULL )
|
|
{
|
|
/* pFileContext->pJobName is guaranteed to be zero terminated. */
|
|
if( strcmp( ( char * ) otaAgent.pActiveJobName, ( char * ) pFileContext->pJobName ) != 0 )
|
|
{
|
|
LogInfo( ( "New job document received, aborting current job." ) );
|
|
|
|
/* Abort the current job. */
|
|
( void ) otaAgent.pOtaInterface->pal.setPlatformImageState( &( otaAgent.fileContext ), OtaImageStateAborted );
|
|
|
|
/*
|
|
* Abort any active file access and release the file resource, if needed.
|
|
*/
|
|
( void ) otaAgent.pOtaInterface->pal.abort( pFileContext );
|
|
|
|
/* Cleanup related to selected protocol. */
|
|
if( otaDataInterface.cleanup != NULL )
|
|
{
|
|
( void ) otaDataInterface.cleanup( &otaAgent );
|
|
}
|
|
|
|
/* Set new active job name. */
|
|
( void ) memcpy( otaAgent.pActiveJobName, pFileContext->pJobName, strlen( ( const char * ) pFileContext->pJobName ) );
|
|
|
|
err = OtaJobParseErrNone;
|
|
}
|
|
else
|
|
{
|
|
/* The same job is being reported so update the url. */
|
|
LogInfo( ( "New job document ID is identical to the current job: "
|
|
"Updating the URL based on the new job document." ) );
|
|
|
|
if( otaAgent.fileContext.pUpdateUrlPath != NULL )
|
|
{
|
|
if( otaAgent.fileContext.updateUrlMaxSize == 0u )
|
|
{
|
|
/* The buffer is allocated by us, free first then update. */
|
|
otaAgent.pOtaInterface->os.mem.free( otaAgent.fileContext.pUpdateUrlPath );
|
|
otaAgent.fileContext.pUpdateUrlPath = pFileContext->pUpdateUrlPath;
|
|
pFileContext->pUpdateUrlPath = NULL;
|
|
}
|
|
else
|
|
{
|
|
/* The buffer is provided by user, directly copy the new url to it. */
|
|
( void ) memcpy( otaAgent.fileContext.pUpdateUrlPath, pFileContext->pUpdateUrlPath, otaAgent.fileContext.updateUrlMaxSize );
|
|
}
|
|
}
|
|
|
|
*pFinalFile = &( otaAgent.fileContext );
|
|
*pUpdateJob = true;
|
|
|
|
err = OtaJobParseErrUpdateCurrentJob;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LogDebug( ( "Parameter check failed: "
|
|
"pJobName is NULL while the OTA Agent is busy: "
|
|
"Ignoring parameter check failure." ) );
|
|
err = OtaJobParseErrNullJob;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
/* Validate update version when receiving job doc in self test state. */
|
|
static void handleSelfTestJobDoc( OtaFileContext_t * pFileContext )
|
|
{
|
|
OtaErr_t otaErr = OtaErrNone;
|
|
OtaErr_t errVersionCheck = OtaErrUninitialized;
|
|
|
|
LogInfo( ( "In self test mode." ) );
|
|
|
|
/* Validate version of the update received.*/
|
|
errVersionCheck = validateUpdateVersion( pFileContext );
|
|
|
|
/* MISRA rule 14.3 requires controlling expressions to be not invariant. otaconfigAllowDowngrade is
|
|
* one of the OTA library configuration and it's set to 0 when running the static analysis. But
|
|
* users can change it when they build their application. So this is a false positive. */
|
|
/* coverity[misra_c_2012_rule_14_3_violation] */
|
|
if( ( otaconfigAllowDowngrade == 1U ) || ( errVersionCheck == OtaErrNone ) )
|
|
{
|
|
/* The running firmware version is newer than the firmware that performed
|
|
* the update or downgrade is allowed so this means we're ready to start
|
|
* the self test phase.
|
|
*
|
|
* Set image state accordingly and update job status with self test identifier.
|
|
*/
|
|
LogInfo( ( "Image version is valid: Begin testing file: File ID=%d",
|
|
otaAgent.serverFileID ) );
|
|
|
|
otaErr = setImageStateWithReason( OtaImageStateTesting, ( uint32_t ) errVersionCheck );
|
|
|
|
if( otaErr != OtaErrNone )
|
|
{
|
|
LogError( ( "Failed to set image state to testing: OtaErr_t=%s", OTA_Err_strerror( otaErr ) ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LogWarn( ( "New image is being rejected: Application version of the new image is invalid: "
|
|
"OtaErr_t=%s", OTA_Err_strerror( errVersionCheck ) ) );
|
|
|
|
otaErr = setImageStateWithReason( OtaImageStateRejected, ( uint32_t ) errVersionCheck );
|
|
|
|
if( otaErr != OtaErrNone )
|
|
{
|
|
LogError( ( "Failed to set image state to rejected: OtaErr_t=%s", OTA_Err_strerror( otaErr ) ) );
|
|
}
|
|
|
|
/* Application callback for self-test failure.*/
|
|
otaAgent.OtaAppCallback( OtaJobEventSelfTestFailed, NULL );
|
|
|
|
/* Handle self-test failure in the platform specific implementation,
|
|
* example, reset the device in case of firmware upgrade. */
|
|
( void ) otaAgent.pOtaInterface->pal.reset( &( otaAgent.fileContext ) );
|
|
}
|
|
}
|
|
|
|
/* Check if all the file context params are valid and initialize resources for the job transfer */
|
|
|
|
static OtaJobParseErr_t validateAndStartJob( OtaFileContext_t * pFileContext,
|
|
OtaFileContext_t ** pFinalFile,
|
|
bool * pUpdateJob )
|
|
{
|
|
OtaJobParseErr_t err = OtaJobParseErrNone;
|
|
|
|
/* Validate the job document parameters. */
|
|
if( pFileContext->fileSize == 0U )
|
|
{
|
|
LogError( ( "Parameter check failed: pFileContext->fileSize is 0: File size should be > 0." ) );
|
|
err = OtaJobParseErrZeroFileSize;
|
|
}
|
|
/* If there's an active job, verify that it's the same as what's being reported now. */
|
|
/* We already checked for missing parameters so we SHOULD have a job name in the context. */
|
|
else if( strlen( ( const char * ) otaAgent.pActiveJobName ) > 0u )
|
|
{
|
|
err = verifyActiveJobStatus( pFileContext, pFinalFile, pUpdateJob );
|
|
}
|
|
else
|
|
{
|
|
/* Assume control of the job name from the context. */
|
|
( void ) memcpy( otaAgent.pActiveJobName, pFileContext->pJobName, strlen( ( const char * ) pFileContext->pJobName ) );
|
|
}
|
|
|
|
/* Store the File ID received in the job. */
|
|
otaAgent.serverFileID = pFileContext->serverFileID;
|
|
|
|
if( err == OtaJobParseErrNone )
|
|
{
|
|
/* If the job is in self test mode, don't start an OTA update but instead do the following:
|
|
*
|
|
* If the firmware that performed the update was older than the currently running firmware,
|
|
* set the image state to "Testing." This is the success path.
|
|
*
|
|
* If it's the same or newer, reject the job since either the firmware was not accepted
|
|
* during self test or an incorrect image was sent by the OTA operator.
|
|
*/
|
|
if( pFileContext->isInSelfTest == true )
|
|
{
|
|
handleSelfTestJobDoc( pFileContext );
|
|
}
|
|
else
|
|
{
|
|
*pFinalFile = pFileContext;
|
|
**pFinalFile = *pFileContext;
|
|
|
|
/* Everything looks OK. Set final context structure to start OTA. */
|
|
LogInfo( ( "Job document was accepted. Attempting to begin the update." ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LogDebug( ( "Failed to validate and start the job: OtaJobParseErr_t=%s", OTA_JobParse_strerror( err ) ) );
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
/* This function is called only if there is an error with the job parsing. */
|
|
static void handleJobParsingError( const OtaFileContext_t * pFileContext,
|
|
OtaJobParseErr_t err )
|
|
{
|
|
OtaErr_t otaErr = OtaErrNone;
|
|
|
|
assert( pFileContext != NULL );
|
|
assert( err != OtaJobParseErrNone );
|
|
|
|
switch( err )
|
|
{
|
|
case OtaJobParseErrBadModelInitParams:
|
|
|
|
LogInfo( ( "Unable to initialize Job Parsing: "
|
|
"OtaJobParseErr_t=%s",
|
|
OTA_JobParse_strerror( err ) ) );
|
|
|
|
break;
|
|
|
|
case OtaJobParseErrUpdateCurrentJob:
|
|
|
|
LogInfo( ( "Update received for current job: "
|
|
"OtaJobParseErr_t=%s, Job name=%s",
|
|
OTA_JobParse_strerror( err ), ( const char * ) pFileContext->pJobName ) );
|
|
|
|
break;
|
|
|
|
case OtaJobParseErrNoActiveJobs:
|
|
LogInfo( ( "No active job available in received job document: "
|
|
"OtaJobParseErr_t=%s",
|
|
OTA_JobParse_strerror( err ) ) );
|
|
break;
|
|
|
|
default:
|
|
|
|
/* If job parsing failed AND there's a job ID, update the job state to FAILED with
|
|
* a reason code. Without a job ID, we can't update the status in the job service. */
|
|
LogError( ( "Failed to parse the job document after parsing the job name: "
|
|
"OtaJobParseErr_t=%s, Job name=%s",
|
|
OTA_JobParse_strerror( err ), ( const char * ) pFileContext->pJobName ) );
|
|
|
|
if( strlen( ( const char * ) otaAgent.pActiveJobName ) > 0u )
|
|
{
|
|
/* Assume control of the job name from the context. */
|
|
( void ) memcpy( otaAgent.pActiveJobName, pFileContext->pJobName, OTA_JOB_ID_MAX_SIZE );
|
|
|
|
otaErr = otaControlInterface.updateJobStatus( &otaAgent,
|
|
JobStatusFailedWithVal,
|
|
( int32_t ) OtaErrJobParserError,
|
|
( int32_t ) err );
|
|
|
|
if( otaErr != OtaErrNone )
|
|
{
|
|
LogError( ( "Failed to update job status: updateJobStatus returned error: OtaErr_t=%s",
|
|
OTA_Err_strerror( otaErr ) ) );
|
|
}
|
|
|
|
/* We don't need the job name memory anymore since we're done with this job. */
|
|
( void ) memset( otaAgent.pActiveJobName, 0, OTA_JOB_ID_MAX_SIZE );
|
|
|
|
/* Close any open files. */
|
|
( void ) otaClose( &( otaAgent.fileContext ) );
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Parse the OTA job document and validate. Return the populated
|
|
* OTA context if valid otherwise return NULL.
|
|
*/
|
|
|
|
static OtaFileContext_t * parseJobDoc( const JsonDocParam_t * pJsonExpectedParams,
|
|
uint16_t numJobParams,
|
|
const char * pJson,
|
|
uint32_t messageLength,
|
|
bool * pUpdateJob )
|
|
{
|
|
OtaJobParseErr_t err = OtaJobParseErrUnknown;
|
|
DocParseErr_t parseError = DocParseErrNone;
|
|
OtaFileContext_t * pFinalFile = NULL;
|
|
OtaFileContext_t * pFileContext = &( otaAgent.fileContext );
|
|
JsonDocModel_t otaJobDocModel;
|
|
OtaJobDocument_t jobDoc = { 0 };
|
|
|
|
parseError = initDocModel( &otaJobDocModel,
|
|
pJsonExpectedParams,
|
|
( void * ) pFileContext,
|
|
( uint32_t ) sizeof( OtaFileContext_t ),
|
|
numJobParams );
|
|
|
|
if( parseError != DocParseErrNone )
|
|
{
|
|
err = OtaJobParseErrBadModelInitParams;
|
|
}
|
|
else
|
|
{
|
|
parseError = parseJSONbyModel( pJson, messageLength, &otaJobDocModel );
|
|
|
|
if( parseError == DocParseErrNone )
|
|
{
|
|
err = validateAndStartJob( pFileContext, &pFinalFile, pUpdateJob );
|
|
}
|
|
else
|
|
{
|
|
err = handleCustomJob( pJson, messageLength );
|
|
}
|
|
}
|
|
|
|
if( err == OtaJobParseErrNone )
|
|
{
|
|
LogInfo( ( "Job parsing success: "
|
|
"OtaJobParseErr_t=%s, Job name=%s",
|
|
OTA_JobParse_strerror( err ), ( const char * ) pFileContext->pJobName ) );
|
|
|
|
/* Set the job id and length from OTA context. */
|
|
jobDoc.pJobId = otaAgent.pActiveJobName;
|
|
jobDoc.jobIdLength = strlen( ( const char * ) otaAgent.pActiveJobName ) + 1U;
|
|
jobDoc.pJobDocJson = ( const uint8_t * ) pJson;
|
|
jobDoc.jobDocLength = messageLength;
|
|
jobDoc.fileTypeId = otaAgent.fileContext.fileType;
|
|
|
|
/* Let the application know to release buffer.*/
|
|
otaAgent.OtaAppCallback( OtaJobEventReceivedJob, ( const void * ) &jobDoc );
|
|
}
|
|
else
|
|
{
|
|
/* Handle job parsing error. */
|
|
handleJobParsingError( pFileContext, err );
|
|
}
|
|
|
|
/* Return pointer to populated file context or NULL if it failed. */
|
|
return pFinalFile;
|
|
}
|
|
|
|
/* Called to update the filecontext structure from the job. */
|
|
static OtaFileContext_t * getFileContextFromJob( const char * pRawMsg,
|
|
uint32_t messageLength )
|
|
{
|
|
uint32_t index;
|
|
uint32_t numBlocks; /* How many data pages are in the expected update image. */
|
|
uint32_t bitmapLen; /* Length of the file block bitmap in bytes. */
|
|
OtaFileContext_t * pUpdateFile; /* Pointer to an OTA update context. */
|
|
OtaErr_t err = OtaErrNone;
|
|
OtaPalStatus_t palStatus;
|
|
|
|
bool updateJob = false;
|
|
|
|
/* Populate an OTA file context from the OTA job document. */
|
|
|
|
pUpdateFile = parseJobDoc( otaJobDocModelParamStructure, OTA_NUM_JOB_PARAMS, pRawMsg, messageLength, &updateJob );
|
|
|
|
if( updateJob == true )
|
|
{
|
|
LogInfo( ( "Job document for receiving an update received." ) );
|
|
}
|
|
|
|
if( ( updateJob == false ) && ( pUpdateFile != NULL ) && ( platformInSelftest() == false ) )
|
|
{
|
|
/* Calculate how many bytes we need in our bitmap for tracking received blocks.
|
|
* The below calculation requires power of 2 page sizes. */
|
|
numBlocks = ( pUpdateFile->fileSize + ( OTA_FILE_BLOCK_SIZE - 1U ) ) >> otaconfigLOG2_FILE_BLOCK_SIZE;
|
|
bitmapLen = ( numBlocks + ( BITS_PER_BYTE - 1U ) ) >> LOG2_BITS_PER_BYTE;
|
|
|
|
if( pUpdateFile->blockBitmapMaxSize == 0u )
|
|
{
|
|
/* LCOV_EXCL_START */
|
|
if( pUpdateFile->pRxBlockBitmap != NULL )
|
|
{
|
|
/* Free any previously allocated bitmap. */
|
|
otaAgent.pOtaInterface->os.mem.free( pUpdateFile->pRxBlockBitmap );
|
|
}
|
|
|
|
/* LCOV_EXCL_STOP */
|
|
|
|
pUpdateFile->pRxBlockBitmap = ( uint8_t * ) otaAgent.pOtaInterface->os.mem.malloc( bitmapLen );
|
|
}
|
|
else
|
|
{
|
|
assert( pUpdateFile->pRxBlockBitmap != NULL );
|
|
( void ) memset( pUpdateFile->pRxBlockBitmap, 0, pUpdateFile->blockBitmapMaxSize );
|
|
}
|
|
|
|
if( pUpdateFile->pRxBlockBitmap != NULL )
|
|
{
|
|
/* Mark as used any pages in the bitmap that are out of range, based on the file size.
|
|
* This keeps us from requesting those pages during retry processing or if using a windowed
|
|
* block request. It also avoids erroneously accepting an out of range data block should it
|
|
* get past any safety checks.
|
|
* Files are not always a multiple of 8 pages (8 bits/pages per byte) so some bits of the
|
|
* last byte may be out of range and those are the bits we want to clear. */
|
|
|
|
uint8_t bit = 1U << ( BITS_PER_BYTE - 1U );
|
|
uint32_t numOutOfRange = ( bitmapLen * BITS_PER_BYTE ) - numBlocks;
|
|
|
|
/* Set all bits in the bitmap to the erased state (we use 1 for erased just like flash memory). */
|
|
( void ) memset( pUpdateFile->pRxBlockBitmap, ( int32_t ) OTA_ERASED_BLOCKS_VAL, bitmapLen );
|
|
|
|
for( index = 0U; index < numOutOfRange; index++ )
|
|
{
|
|
pUpdateFile->pRxBlockBitmap[ bitmapLen - 1U ] &= ( uint8_t ) ~bit;
|
|
bit >>= 1U;
|
|
}
|
|
|
|
pUpdateFile->blocksRemaining = numBlocks; /* Initialize our blocks remaining counter. */
|
|
|
|
/* Create/Open the OTA file on the file system. */
|
|
palStatus = otaAgent.pOtaInterface->pal.createFile( pUpdateFile );
|
|
|
|
if( OTA_PAL_MAIN_ERR( palStatus ) != OtaPalSuccess )
|
|
{
|
|
err = setImageStateWithReason( OtaImageStateAborted, palStatus );
|
|
( void ) otaClose( pUpdateFile ); /* Ignore false result since we're setting the pointer to null on the next line. */
|
|
pUpdateFile = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Can't receive the image without enough memory. */
|
|
( void ) otaClose( pUpdateFile );
|
|
pUpdateFile = NULL;
|
|
}
|
|
}
|
|
|
|
if( err != OtaErrNone )
|
|
{
|
|
LogDebug( ( "Failed to parse the file context from the job document: OtaErr_t=%s",
|
|
OTA_Err_strerror( err ) ) );
|
|
}
|
|
|
|
return pUpdateFile; /* Return the OTA file context. */
|
|
}
|
|
|
|
/*
|
|
* validateDataBlock
|
|
*
|
|
* Validate the block index and size. If it is NOT the last block, it MUST be equal to a full block size.
|
|
* If it IS the last block, it MUST be equal to the expected remainder. If the block ID is out of range,
|
|
* that's an error.
|
|
*/
|
|
static bool validateDataBlock( const OtaFileContext_t * pFileContext,
|
|
uint32_t blockIndex,
|
|
uint32_t blockSize )
|
|
{
|
|
bool ret = false;
|
|
uint32_t lastBlock = 0;
|
|
|
|
lastBlock = ( ( pFileContext->fileSize + ( OTA_FILE_BLOCK_SIZE - 1U ) ) >> otaconfigLOG2_FILE_BLOCK_SIZE ) - 1U;
|
|
|
|
if( ( ( blockIndex < lastBlock ) && ( blockSize == OTA_FILE_BLOCK_SIZE ) ) ||
|
|
( ( blockIndex == lastBlock ) && ( blockSize == ( pFileContext->fileSize - ( lastBlock * OTA_FILE_BLOCK_SIZE ) ) ) ) )
|
|
{
|
|
ret = true;
|
|
LogInfo( ( "Received valid file block: Block index=%u, Size=%u",
|
|
blockIndex, blockSize ) );
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Validate the incoming data block and store it in the file context. */
|
|
|
|
static IngestResult_t processDataBlock( OtaFileContext_t * pFileContext,
|
|
uint32_t uBlockIndex,
|
|
uint32_t uBlockSize,
|
|
OtaPalStatus_t * pCloseResult,
|
|
uint8_t * pPayload )
|
|
{
|
|
IngestResult_t eIngestResult = IngestResultUninitialized;
|
|
uint32_t byte = 0;
|
|
uint8_t bitMask = 0;
|
|
|
|
if( validateDataBlock( pFileContext, uBlockIndex, uBlockSize ) == true )
|
|
{
|
|
/* Create bit mask for use in our bitmap. BITS_PER_BYTE is 8 so it will never overflow. */
|
|
bitMask = ( uint8_t ) ( 1U << ( uBlockIndex % BITS_PER_BYTE ) );
|
|
|
|
/* Calculate byte offset into bitmap. */
|
|
byte = uBlockIndex >> LOG2_BITS_PER_BYTE;
|
|
|
|
/* Check if we have already received this block. */
|
|
if( ( ( pFileContext->pRxBlockBitmap[ byte ] ) & bitMask ) == 0U )
|
|
{
|
|
LogWarn( ( "Received a duplicate block: Block index=%u, Block size=%u",
|
|
uBlockIndex, uBlockSize ) );
|
|
LogDebug( ( "Number of blocks remaining: %u",
|
|
pFileContext->blocksRemaining ) );
|
|
|
|
eIngestResult = IngestResultDuplicate_Continue;
|
|
*pCloseResult = OTA_PAL_COMBINE_ERR( OtaPalSuccess, 0 ); /* This is a success path. */
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LogError( ( "Block range check failed: Received a block outside of the expected range: "
|
|
"Block index=%u, Block size=%u",
|
|
uBlockIndex, uBlockSize ) );
|
|
eIngestResult = IngestResultBlockOutOfRange;
|
|
}
|
|
|
|
/* Process the received data block. */
|
|
if( eIngestResult == IngestResultUninitialized )
|
|
{
|
|
if( pFileContext->pFile != NULL )
|
|
{
|
|
int32_t iBytesWritten = otaAgent.pOtaInterface->pal.writeBlock( pFileContext,
|
|
( uBlockIndex * OTA_FILE_BLOCK_SIZE ),
|
|
pPayload,
|
|
uBlockSize );
|
|
|
|
if( iBytesWritten < 0 )
|
|
{
|
|
eIngestResult = IngestResultWriteBlockFailed;
|
|
LogError( ( "Failed to ingest received block: IngestResult_t=%d",
|
|
eIngestResult ) );
|
|
}
|
|
else
|
|
{
|
|
/* Mark this block as received in our bitmap. */
|
|
pFileContext->pRxBlockBitmap[ byte ] &= ( uint8_t ) ~bitMask;
|
|
pFileContext->blocksRemaining--;
|
|
eIngestResult = IngestResultAccepted_Continue;
|
|
*pCloseResult = OTA_PAL_COMBINE_ERR( OtaPalSuccess, 0 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LogError( ( "Parameter check failed: pFileContext->pFile is NULL." ) );
|
|
eIngestResult = IngestResultBadFileHandle;
|
|
}
|
|
}
|
|
|
|
return eIngestResult;
|
|
}
|
|
|
|
/* Decode and store the incoming data block. */
|
|
static IngestResult_t decodeAndStoreDataBlock( OtaFileContext_t * pFileContext,
|
|
const uint8_t * pRawMsg,
|
|
uint32_t messageSize,
|
|
uint8_t ** pPayload,
|
|
uint32_t * pBlockSize,
|
|
uint32_t * pBlockIndex )
|
|
{
|
|
IngestResult_t eIngestResult = IngestResultUninitialized;
|
|
int32_t lFileId = 0;
|
|
int32_t sBlockSize = 0;
|
|
int32_t sBlockIndex = 0;
|
|
size_t payloadSize = 0;
|
|
|
|
/* If we are expecting a data block, allocate space for it. */
|
|
if( ( pFileContext->pRxBlockBitmap != NULL ) && ( pFileContext->blocksRemaining > 0U ) )
|
|
{
|
|
( void ) otaAgent.pOtaInterface->os.timer.start( OtaRequestTimer,
|
|
"OtaRequestTimer",
|
|
otaconfigFILE_REQUEST_WAIT_MS,
|
|
otaTimerCallback );
|
|
|
|
if( otaAgent.fileContext.decodeMemMaxSize != 0U )
|
|
{
|
|
*pPayload = otaAgent.fileContext.pDecodeMem;
|
|
payloadSize = otaAgent.fileContext.decodeMemMaxSize;
|
|
}
|
|
else
|
|
{
|
|
*pPayload = otaAgent.pOtaInterface->os.mem.malloc( 1UL << otaconfigLOG2_FILE_BLOCK_SIZE );
|
|
|
|
if( *pPayload != NULL )
|
|
{
|
|
payloadSize = ( 1UL << otaconfigLOG2_FILE_BLOCK_SIZE );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
eIngestResult = IngestResultUnexpectedBlock;
|
|
}
|
|
|
|
/* Decode the file block if space is allocated. */
|
|
if( payloadSize > 0u )
|
|
{
|
|
/* Decode the file block received. */
|
|
if( OtaErrNone != otaDataInterface.decodeFileBlock(
|
|
pRawMsg,
|
|
messageSize,
|
|
&lFileId,
|
|
&sBlockIndex,
|
|
&sBlockSize,
|
|
pPayload,
|
|
&payloadSize ) )
|
|
{
|
|
eIngestResult = IngestResultBadData;
|
|
}
|
|
else
|
|
{
|
|
*pBlockIndex = ( uint32_t ) sBlockIndex;
|
|
*pBlockSize = ( uint32_t ) sBlockSize;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* If the block is expected, but we could not allocate space. */
|
|
if( eIngestResult == IngestResultUninitialized )
|
|
{
|
|
eIngestResult = IngestResultNoDecodeMemory;
|
|
}
|
|
}
|
|
|
|
return eIngestResult;
|
|
}
|
|
|
|
/* Free the resources allocated for data ingestion and close the file handle. */
|
|
|
|
static IngestResult_t ingestDataBlockCleanup( OtaFileContext_t * pFileContext,
|
|
OtaPalStatus_t * pCloseResult )
|
|
{
|
|
IngestResult_t eIngestResult = IngestResultAccepted_Continue;
|
|
OtaPalMainStatus_t otaPalMainErr;
|
|
OtaPalSubStatus_t otaPalSubErr;
|
|
|
|
( void ) otaPalSubErr; /* For suppressing compiler-warning: unused variable. */
|
|
|
|
if( pFileContext->blocksRemaining == 0U )
|
|
{
|
|
LogInfo( ( "Received final block of the update." ) );
|
|
|
|
/* Stop the request timer. */
|
|
( void ) otaAgent.pOtaInterface->os.timer.stop( OtaRequestTimer );
|
|
|
|
/* Free the bitmap now that we're done with the download. */
|
|
if( ( pFileContext->pRxBlockBitmap != NULL ) && ( pFileContext->blockBitmapMaxSize == 0u ) )
|
|
{
|
|
/* Free any previously allocated bitmap. */
|
|
otaAgent.pOtaInterface->os.mem.free( pFileContext->pRxBlockBitmap );
|
|
pFileContext->pRxBlockBitmap = NULL;
|
|
}
|
|
|
|
if( pFileContext->pFile != NULL )
|
|
{
|
|
*pCloseResult = otaAgent.pOtaInterface->pal.closeFile( pFileContext );
|
|
otaPalMainErr = OTA_PAL_MAIN_ERR( *pCloseResult );
|
|
otaPalSubErr = OTA_PAL_SUB_ERR( *pCloseResult );
|
|
|
|
if( otaPalMainErr == OtaPalSuccess )
|
|
{
|
|
LogInfo( ( "Received entire update and validated the signature." ) );
|
|
eIngestResult = IngestResultFileComplete;
|
|
}
|
|
else
|
|
{
|
|
LogError( ( "Failed to close the OTA file: Error=(%s:0x%06x)",
|
|
OTA_PalStatus_strerror( otaPalMainErr ), otaPalSubErr ) );
|
|
|
|
if( otaPalMainErr == OtaPalSignatureCheckFailed )
|
|
{
|
|
eIngestResult = IngestResultSigCheckFail;
|
|
}
|
|
else
|
|
{
|
|
eIngestResult = IngestResultFileCloseFail;
|
|
}
|
|
}
|
|
|
|
/* File is now closed so clear the file handle in the context. */
|
|
pFileContext->pFile = NULL;
|
|
}
|
|
else
|
|
{
|
|
LogError( ( "Parameter check failed: pFileContext->pFile is NULL." ) );
|
|
eIngestResult = IngestResultBadFileHandle;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LogInfo( ( "Number of blocks remaining: %u", pFileContext->blocksRemaining ) );
|
|
}
|
|
|
|
return eIngestResult;
|
|
}
|
|
|
|
/* Called when the OTA agent receives a file data block message. */
|
|
|
|
static IngestResult_t ingestDataBlock( OtaFileContext_t * pFileContext,
|
|
const uint8_t * pRawMsg,
|
|
uint32_t messageSize,
|
|
OtaPalStatus_t * pCloseResult )
|
|
{
|
|
IngestResult_t eIngestResult = IngestResultUninitialized;
|
|
uint32_t uBlockSize = 0;
|
|
uint32_t uBlockIndex = 0;
|
|
uint8_t * pPayload = NULL;
|
|
|
|
/* Assume the file context and result pointers are not NULL. This function
|
|
* is only intended to be called by processDataHandler, which always passes
|
|
* them in as pointers to static variables. */
|
|
assert( pFileContext != NULL );
|
|
assert( pCloseResult != NULL );
|
|
|
|
/* Decode the received data block. */
|
|
/* If we have a block bitmap available then process the message. */
|
|
eIngestResult = decodeAndStoreDataBlock( pFileContext, pRawMsg, messageSize, &pPayload, &uBlockSize, &uBlockIndex );
|
|
|
|
/* Validate the data block and process it to store the information.*/
|
|
if( eIngestResult == IngestResultUninitialized )
|
|
{
|
|
eIngestResult = processDataBlock( pFileContext, uBlockIndex, uBlockSize, pCloseResult, pPayload );
|
|
}
|
|
|
|
/* If the ingestion is complete close the file and cleanup.*/
|
|
if( eIngestResult == IngestResultAccepted_Continue )
|
|
{
|
|
eIngestResult = ingestDataBlockCleanup( pFileContext, pCloseResult );
|
|
}
|
|
|
|
/* Free the payload if it's dynamically allocated by us. */
|
|
if( ( otaAgent.fileContext.decodeMemMaxSize == 0u ) &&
|
|
( pPayload != NULL ) )
|
|
{
|
|
otaAgent.pOtaInterface->os.mem.free( pPayload );
|
|
}
|
|
|
|
return eIngestResult;
|
|
}
|
|
|
|
/*
|
|
* Clean up after the OTA process is done. Possibly free memory for re-use.
|
|
*/
|
|
static void agentShutdownCleanup( void )
|
|
{
|
|
uint32_t index;
|
|
|
|
otaAgent.state = OtaAgentStateShuttingDown;
|
|
|
|
/* Control plane cleanup related to selected protocol. */
|
|
if( otaControlInterface.cleanup != NULL )
|
|
{
|
|
( void ) otaControlInterface.cleanup( &otaAgent );
|
|
}
|
|
|
|
/* Data plane cleanup related to selected protocol. */
|
|
if( otaDataInterface.cleanup != NULL )
|
|
{
|
|
( void ) otaDataInterface.cleanup( &otaAgent );
|
|
}
|
|
|
|
/*
|
|
* Close any open OTA transfers.
|
|
*/
|
|
for( index = 0; index < OTA_MAX_FILES; index++ )
|
|
{
|
|
( void ) otaClose( &( otaAgent.fileContext ) );
|
|
}
|
|
|
|
/*
|
|
* Clear active job name.
|
|
*/
|
|
( void ) memset( otaAgent.pActiveJobName, 0, OTA_JOB_ID_MAX_SIZE );
|
|
}
|
|
|
|
/*
|
|
* Handle any events that were unexpected in the current state.
|
|
*/
|
|
static void handleUnexpectedEvents( const OtaEventMsg_t * pEventMsg )
|
|
{
|
|
LogError( ( "Received unexpected event: "
|
|
"Current state=[%s]"
|
|
", Event received=[%s]",
|
|
pOtaAgentStateStrings[ otaAgent.state ],
|
|
pOtaEventStrings[ pEventMsg->eventId ] ) );
|
|
|
|
/* Perform any cleanup operations required for specific unhandled events.*/
|
|
switch( pEventMsg->eventId )
|
|
{
|
|
case OtaAgentEventReceivedJobDocument:
|
|
|
|
/* Let the application know to release buffer.*/
|
|
otaAgent.OtaAppCallback( OtaJobEventProcessed, ( const void * ) pEventMsg->pEventData );
|
|
|
|
break;
|
|
|
|
case OtaAgentEventReceivedFileBlock:
|
|
|
|
/* Let the application know to release buffer.*/
|
|
otaAgent.OtaAppCallback( OtaJobEventProcessed, ( const void * ) pEventMsg->pEventData );
|
|
|
|
/* File block was not processed, increment the statistics. */
|
|
otaAgent.statistics.otaPacketsDropped++;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
/* Nothing to do here.*/
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Execute the handler for selected index from the transition table.
|
|
*/
|
|
static void executeHandler( uint32_t index,
|
|
const OtaEventMsg_t * const pEventMsg )
|
|
{
|
|
OtaErr_t err = OtaErrNone;
|
|
|
|
assert( otaTransitionTable[ index ].handler != NULL );
|
|
|
|
err = otaTransitionTable[ index ].handler( pEventMsg->pEventData );
|
|
|
|
if( err == OtaErrNone )
|
|
{
|
|
LogDebug( ( "Executing handler for state transition: " ) );
|
|
|
|
/*
|
|
* Update the current state in OTA agent context.
|
|
*/
|
|
otaAgent.state = otaTransitionTable[ index ].nextState;
|
|
}
|
|
else
|
|
{
|
|
LogDebug( ( "Failed to execute state transition handler: "
|
|
"Handler returned error: OtaErr_t=%s",
|
|
OTA_Err_strerror( err ) ) );
|
|
}
|
|
|
|
LogInfo( ( "Current State=[%s]"
|
|
", Event=[%s]"
|
|
", New state=[%s]",
|
|
pOtaAgentStateStrings[ otaAgent.state ],
|
|
pOtaEventStrings[ pEventMsg->eventId ],
|
|
pOtaAgentStateStrings[ otaTransitionTable[ index ].nextState ] ) );
|
|
}
|
|
|
|
static uint32_t searchTransition( const OtaEventMsg_t * pEventMsg )
|
|
{
|
|
uint32_t transitionTableLen = ( uint32_t ) ( sizeof( otaTransitionTable ) / sizeof( otaTransitionTable[ 0 ] ) );
|
|
uint32_t i = 0;
|
|
|
|
for( i = 0; i < transitionTableLen; i++ )
|
|
{
|
|
if( ( ( otaTransitionTable[ i ].currentState == otaAgent.state ) ||
|
|
( otaTransitionTable[ i ].currentState == OtaAgentStateAll ) ) &&
|
|
( otaTransitionTable[ i ].eventId == pEventMsg->eventId ) )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
static void receiveAndProcessOtaEvent( void )
|
|
{
|
|
OtaEventMsg_t eventMsg = { 0 };
|
|
uint32_t i = 0;
|
|
uint32_t transitionTableLen = ( uint32_t ) ( sizeof( otaTransitionTable ) / sizeof( otaTransitionTable[ 0 ] ) );
|
|
|
|
if( otaAgent.pOtaInterface == NULL )
|
|
{
|
|
LogError( ( "Failed to receive event: OS Interface not set" ) );
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Receive the next event from the OTA event queue to process.
|
|
*/
|
|
if( otaAgent.pOtaInterface->os.event.recv( NULL, &eventMsg, 0 ) == OtaOsSuccess )
|
|
{
|
|
/*
|
|
* Search transition index if available in the table.
|
|
*/
|
|
i = searchTransition( &eventMsg );
|
|
|
|
if( i < transitionTableLen )
|
|
{
|
|
LogDebug( ( "Found valid event handler for state transition: "
|
|
"State=[%s], "
|
|
"Event=[%s]",
|
|
pOtaAgentStateStrings[ otaAgent.state ],
|
|
pOtaEventStrings[ eventMsg.eventId ] ) );
|
|
|
|
/*
|
|
* Execute the handler function.
|
|
*/
|
|
executeHandler( i, &eventMsg );
|
|
}
|
|
|
|
if( i == transitionTableLen )
|
|
{
|
|
/*
|
|
* Handle unexpected events.
|
|
*/
|
|
handleUnexpectedEvents( &eventMsg );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void OTA_EventProcessingTask( void * pUnused )
|
|
{
|
|
( void ) pUnused;
|
|
|
|
/*
|
|
* OTA Agent is ready to receive and process events so update the state to ready.
|
|
*/
|
|
otaAgent.state = OtaAgentStateReady;
|
|
|
|
while( otaAgent.state != OtaAgentStateStopped )
|
|
{
|
|
receiveAndProcessOtaEvent();
|
|
}
|
|
}
|
|
|
|
bool OTA_SignalEvent( const OtaEventMsg_t * const pEventMsg )
|
|
{
|
|
bool retVal = false;
|
|
OtaOsStatus_t err = OtaOsSuccess;
|
|
|
|
/* Check if file block received and update statistics.*/
|
|
if( pEventMsg->eventId == OtaAgentEventReceivedFileBlock )
|
|
{
|
|
otaAgent.statistics.otaPacketsReceived++;
|
|
}
|
|
|
|
err = otaAgent.pOtaInterface->os.event.send( NULL, pEventMsg, 0 );
|
|
|
|
if( err == OtaOsSuccess )
|
|
{
|
|
retVal = true;
|
|
LogDebug( ( "Added event message to OTA event queue." ) );
|
|
|
|
if( pEventMsg->eventId == OtaAgentEventReceivedFileBlock )
|
|
{
|
|
otaAgent.statistics.otaPacketsQueued++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
retVal = false;
|
|
LogError( ( "Failed to add even message to OTA event queue: "
|
|
"send returned error: "
|
|
"OtaOsStatus_t=%s",
|
|
OTA_OsStatus_strerror( err ) ) );
|
|
|
|
if( pEventMsg->eventId == OtaAgentEventReceivedFileBlock )
|
|
{
|
|
otaAgent.statistics.otaPacketsDropped++;
|
|
}
|
|
}
|
|
|
|
return retVal;
|
|
}
|
|
|
|
static void initializeAppBuffers( OtaAppBuffer_t * pOtaBuffer )
|
|
{
|
|
/* Initialize update file path buffer from application buffer.*/
|
|
if( ( pOtaBuffer->pUpdateFilePath != NULL ) && ( pOtaBuffer->updateFilePathsize > 0u ) )
|
|
{
|
|
otaAgent.fileContext.pFilePath = pOtaBuffer->pUpdateFilePath;
|
|
otaAgent.fileContext.filePathMaxSize = pOtaBuffer->updateFilePathsize;
|
|
}
|
|
else
|
|
{
|
|
otaAgent.fileContext.filePathMaxSize = 0;
|
|
}
|
|
|
|
/* Initialize certificate file path buffer from application buffer.*/
|
|
if( ( pOtaBuffer->pCertFilePath != NULL ) && ( pOtaBuffer->certFilePathSize > 0u ) )
|
|
{
|
|
otaAgent.fileContext.pCertFilepath = pOtaBuffer->pCertFilePath;
|
|
otaAgent.fileContext.certFilePathMaxSize = pOtaBuffer->certFilePathSize;
|
|
}
|
|
else
|
|
{
|
|
otaAgent.fileContext.certFilePathMaxSize = 0;
|
|
}
|
|
|
|
/* Initialize stream name buffer from application buffer.*/
|
|
if( ( pOtaBuffer->pStreamName != NULL ) && ( pOtaBuffer->streamNameSize > 0u ) )
|
|
{
|
|
otaAgent.fileContext.pStreamName = pOtaBuffer->pStreamName;
|
|
otaAgent.fileContext.streamNameMaxSize = pOtaBuffer->streamNameSize;
|
|
}
|
|
else
|
|
{
|
|
otaAgent.fileContext.streamNameMaxSize = 0;
|
|
}
|
|
|
|
/* Initialize file bitmap buffer from application buffer.*/
|
|
if( ( pOtaBuffer->pDecodeMemory != NULL ) && ( pOtaBuffer->decodeMemorySize > 0u ) )
|
|
{
|
|
otaAgent.fileContext.pDecodeMem = pOtaBuffer->pDecodeMemory;
|
|
otaAgent.fileContext.decodeMemMaxSize = pOtaBuffer->decodeMemorySize;
|
|
}
|
|
else
|
|
{
|
|
otaAgent.fileContext.decodeMemMaxSize = 0;
|
|
}
|
|
|
|
/* Initialize file bitmap buffer from application buffer.*/
|
|
if( ( pOtaBuffer->pFileBitmap != NULL ) && ( pOtaBuffer->fileBitmapSize > 0u ) )
|
|
{
|
|
otaAgent.fileContext.pRxBlockBitmap = pOtaBuffer->pFileBitmap;
|
|
otaAgent.fileContext.blockBitmapMaxSize = pOtaBuffer->fileBitmapSize;
|
|
}
|
|
else
|
|
{
|
|
otaAgent.fileContext.blockBitmapMaxSize = 0;
|
|
}
|
|
|
|
/* Initialize url buffer from application buffer.*/
|
|
if( ( pOtaBuffer->pUrl != NULL ) && ( pOtaBuffer->urlSize > 0u ) )
|
|
{
|
|
otaAgent.fileContext.pUpdateUrlPath = pOtaBuffer->pUrl;
|
|
otaAgent.fileContext.updateUrlMaxSize = pOtaBuffer->urlSize;
|
|
}
|
|
else
|
|
{
|
|
otaAgent.fileContext.updateUrlMaxSize = 0;
|
|
}
|
|
|
|
/* Initialize auth scheme buffer from application buffer.*/
|
|
if( ( pOtaBuffer->pAuthScheme != NULL ) && ( pOtaBuffer->authSchemeSize > 0u ) )
|
|
{
|
|
otaAgent.fileContext.pAuthScheme = pOtaBuffer->pAuthScheme;
|
|
otaAgent.fileContext.authSchemeMaxSize = pOtaBuffer->authSchemeSize;
|
|
}
|
|
else
|
|
{
|
|
otaAgent.fileContext.authSchemeMaxSize = 0;
|
|
}
|
|
}
|
|
|
|
static void initializeLocalBuffers( void )
|
|
{
|
|
/* Initialize JOB Id buffer .*/
|
|
otaAgent.fileContext.pJobName = pJobNameBuffer;
|
|
otaAgent.fileContext.jobNameMaxSize = ( uint16_t ) sizeof( pJobNameBuffer );
|
|
|
|
/* Initialize protocol buffers .*/
|
|
otaAgent.fileContext.pProtocols = pProtocolBuffer;
|
|
otaAgent.fileContext.protocolMaxSize = ( uint16_t ) sizeof( pProtocolBuffer );
|
|
|
|
otaAgent.fileContext.pSignature = &sig256Buffer;
|
|
}
|
|
|
|
/*
|
|
* Public API to initialize the OTA Agent.
|
|
*
|
|
* If the Application calls OTA_Init() after it is already initialized, we will
|
|
* only reset the statistics counters and set the job complete callback but will not
|
|
* modify the existing OTA agent context. You must first call OTA_Shutdown()
|
|
* successfully.
|
|
*/
|
|
OtaErr_t OTA_Init( OtaAppBuffer_t * pOtaBuffer,
|
|
const OtaInterfaces_t * pOtaInterfaces,
|
|
const uint8_t * pThingName,
|
|
OtaAppCallback_t OtaAppCallback )
|
|
{
|
|
/* Return value from this function */
|
|
OtaErr_t returnStatus = OtaErrUninitialized;
|
|
|
|
( void ) pOtaEventStrings; /* For suppressing compiler-warning: unused variable. */
|
|
( void ) pOtaAgentStateStrings; /* For suppressing compiler-warning: unused variable. */
|
|
|
|
/* If OTA agent is stopped then start running. */
|
|
if( otaAgent.state == OtaAgentStateStopped )
|
|
{
|
|
/*
|
|
* Initialize the OTA control interface based on the application protocol
|
|
* selected in library configuration.
|
|
*/
|
|
setControlInterface( &otaControlInterface );
|
|
|
|
/*
|
|
* Reset all the statistics counters.
|
|
*/
|
|
otaAgent.statistics.otaPacketsReceived = 0;
|
|
otaAgent.statistics.otaPacketsDropped = 0;
|
|
otaAgent.statistics.otaPacketsQueued = 0;
|
|
otaAgent.statistics.otaPacketsProcessed = 0;
|
|
|
|
/*
|
|
* Initialize OTA interfaces in OTA Agent context..
|
|
*/
|
|
otaAgent.pOtaInterface = pOtaInterfaces;
|
|
|
|
/* Initialize application buffers. */
|
|
initializeAppBuffers( pOtaBuffer );
|
|
|
|
/* Initialize local buffers. */
|
|
initializeLocalBuffers();
|
|
|
|
/* Initialize ota application callback.*/
|
|
otaAgent.OtaAppCallback = OtaAppCallback;
|
|
|
|
/*
|
|
* The current OTA image state as set by the OTA agent.
|
|
*/
|
|
otaAgent.imageState = OtaImageStateUnknown;
|
|
|
|
/*
|
|
* Initialize OTA event interface.
|
|
*/
|
|
( void ) otaAgent.pOtaInterface->os.event.init( NULL );
|
|
|
|
if( pThingName == NULL )
|
|
{
|
|
LogError( ( "Error: Thing name is NULL.\r\n" ) );
|
|
}
|
|
else
|
|
{
|
|
uint32_t strLength = ( uint32_t ) ( strlen( ( const char * ) pThingName ) );
|
|
|
|
if( strLength <= otaconfigMAX_THINGNAME_LEN )
|
|
{
|
|
/*
|
|
* Store the Thing name to be used for topics later. Include zero terminator
|
|
* when saving the Thing name.
|
|
*/
|
|
( void ) memcpy( otaAgent.pThingName, pThingName, strLength + 1UL );
|
|
returnStatus = OtaErrNone;
|
|
}
|
|
else
|
|
{
|
|
LogError( ( "Error: Thing name is too long.\r\n" ) );
|
|
}
|
|
}
|
|
|
|
if( returnStatus == OtaErrNone )
|
|
{
|
|
/* OTA Task is not running yet so update the state to init directly in OTA context. */
|
|
otaAgent.state = OtaAgentStateInit;
|
|
}
|
|
}
|
|
/* If OTA agent is already running, just reset the statistics. */
|
|
else
|
|
{
|
|
( void ) memset( &otaAgent.statistics, 0, sizeof( otaAgent.statistics ) );
|
|
returnStatus = OtaErrNone;
|
|
}
|
|
|
|
return returnStatus;
|
|
}
|
|
|
|
/*
|
|
* Public API to shutdown the OTA Agent.
|
|
*/
|
|
OtaState_t OTA_Shutdown( uint32_t ticksToWait,
|
|
uint8_t unsubscribeFlag )
|
|
{
|
|
OtaEventMsg_t eventMsg = { 0 };
|
|
uint32_t ticks = ticksToWait;
|
|
|
|
LogDebug( ( "Number of ticks to idle while the OTA Agent shuts down: "
|
|
"ticks=%u",
|
|
ticks ) );
|
|
|
|
if( otaAgent.state == OtaAgentStateInit )
|
|
{
|
|
/* When in init state, the OTA state machine is not running yet. So directly set state to
|
|
* stopped. */
|
|
otaAgent.state = OtaAgentStateStopped;
|
|
}
|
|
else if( ( otaAgent.state != OtaAgentStateStopped ) && ( otaAgent.state != OtaAgentStateShuttingDown ) ) /* LCOV_EXCL_BR_LINE */
|
|
{
|
|
otaAgent.unsubscribeOnShutdown = unsubscribeFlag;
|
|
|
|
/*
|
|
* Send shutdown signal to OTA Agent task.
|
|
*/
|
|
eventMsg.eventId = OtaAgentEventShutdown;
|
|
|
|
/* Send signal to OTA task. */
|
|
if( OTA_SignalEvent( &eventMsg ) == false )
|
|
{
|
|
LogError( ( "Failed to signal the OTA Agent to shutdown: "
|
|
"OTA_SignalEvent returned false." ) );
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Wait for the OTA agent to complete shutdown, if requested.
|
|
*/
|
|
while( ( ticks > 0U ) && ( otaAgent.state != OtaAgentStateStopped ) ) /* LCOV_EXCL_BR_LINE */
|
|
{
|
|
ticks--;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LogDebug( ( "Ignoring request to shutdown OTA Agent: "
|
|
"OTA Agent is already in state [%s]",
|
|
pOtaAgentStateStrings[ otaAgent.state ] ) );
|
|
}
|
|
|
|
LogDebug( ( "Number of ticks remaining when OTA Agent shutdown: "
|
|
"ticks=%u",
|
|
ticks ) );
|
|
|
|
return otaAgent.state;
|
|
}
|
|
|
|
/*
|
|
* Return the current state of the OTA agent.
|
|
*/
|
|
OtaState_t OTA_GetState( void )
|
|
{
|
|
return otaAgent.state;
|
|
}
|
|
|
|
/*
|
|
* Return the details of the packets received.
|
|
*/
|
|
OtaErr_t OTA_GetStatistics( OtaAgentStatistics_t * pStatistics )
|
|
{
|
|
OtaErr_t err = OtaErrInvalidArg;
|
|
|
|
if( pStatistics != NULL )
|
|
{
|
|
*pStatistics = otaAgent.statistics;
|
|
err = OtaErrNone;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
OtaErr_t OTA_CheckForUpdate( void )
|
|
{
|
|
OtaErr_t retVal = OtaErrNone;
|
|
OtaEventMsg_t eventMsg = { 0 };
|
|
|
|
LogInfo( ( "Sending event to trigger checking for and update." ) );
|
|
|
|
/*
|
|
* Send event to get OTA job document.
|
|
*/
|
|
eventMsg.eventId = OtaAgentEventRequestJobDocument;
|
|
|
|
if( OTA_SignalEvent( &eventMsg ) == false )
|
|
{
|
|
retVal = OtaErrSignalEventFailed;
|
|
}
|
|
|
|
/*
|
|
* The event will be processed later so return OtaErrNone.
|
|
*/
|
|
return retVal;
|
|
}
|
|
|
|
/*
|
|
* This should be called by the user application or the default OTA callback handler
|
|
* after an OTA update is considered accepted. It simply calls the platform specific
|
|
* code required to activate the received OTA update (usually just a device reset).
|
|
*/
|
|
OtaErr_t OTA_ActivateNewImage( void )
|
|
{
|
|
OtaPalStatus_t palStatus = OTA_PAL_COMBINE_ERR( OtaPalActivateFailed, 0 );
|
|
|
|
/*
|
|
* Call platform specific code to activate the image. This should reset the device
|
|
* and not return unless there is a problem within the PAL layer. If it does return,
|
|
* output an error message. The device may need to be reset manually.
|
|
*/
|
|
if( ( otaAgent.pOtaInterface != NULL ) && ( otaAgent.pOtaInterface->pal.activate != NULL ) )
|
|
{
|
|
palStatus = otaAgent.pOtaInterface->pal.activate( &( otaAgent.fileContext ) );
|
|
}
|
|
|
|
LogError( ( "Failed to activate new image: "
|
|
"activateNewImage returned error: "
|
|
"Manual reset required: "
|
|
"OtaPalStatus_t=%s",
|
|
OTA_PalStatus_strerror( OTA_PAL_MAIN_ERR( palStatus ) ) ) );
|
|
|
|
return OTA_PAL_MAIN_ERR( palStatus ) == OtaPalSuccess ? OtaErrNone : OtaErrActivateFailed;
|
|
}
|
|
|
|
/*
|
|
* Accept, reject or abort the OTA image transfer.
|
|
*
|
|
* If accepting or rejecting an image transfer, this API
|
|
* will set the OTA image state and update the job status
|
|
* appropriately.
|
|
* If aborting a transfer, it will trigger the OTA task to
|
|
* abort via an RTOS event asynchronously and therefore do
|
|
* not set the image state here.
|
|
*
|
|
* NOTE: This call may block due to the status update message.
|
|
*/
|
|
|
|
OtaErr_t OTA_SetImageState( OtaImageState_t state )
|
|
{
|
|
OtaErr_t err = OtaErrUninitialized;
|
|
OtaEventMsg_t eventMsg = { 0 };
|
|
|
|
switch( state )
|
|
{
|
|
case OtaImageStateAborted:
|
|
|
|
eventMsg.eventId = OtaAgentEventUserAbort;
|
|
|
|
/*
|
|
* Send the event, otaAgent.imageState will be set later when the event is processed.
|
|
*/
|
|
err = ( OTA_SignalEvent( &eventMsg ) == true ) ? OtaErrNone : OtaErrSignalEventFailed;
|
|
|
|
break;
|
|
|
|
case OtaImageStateRejected:
|
|
|
|
/*
|
|
* Set the image state as rejected.
|
|
*/
|
|
err = setImageStateWithReason( state, 0U );
|
|
|
|
break;
|
|
|
|
case OtaImageStateAccepted:
|
|
|
|
/*
|
|
* Set the image state as accepted.
|
|
*/
|
|
err = setImageStateWithReason( state, 0U );
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
/* We are catching unused states here which is not possible. */
|
|
err = OtaErrInvalidArg;
|
|
|
|
break;
|
|
}
|
|
|
|
if( err != OtaErrNone )
|
|
{
|
|
LogDebug( ( "Failed to update the image state: "
|
|
"OtaErr_t=%s",
|
|
OTA_Err_strerror( err ) ) );
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
OtaImageState_t OTA_GetImageState( void )
|
|
{
|
|
/*
|
|
* Return the current OTA image state.
|
|
*/
|
|
return otaAgent.imageState;
|
|
}
|
|
|
|
/*
|
|
* Suspend OTA Agent task.
|
|
*/
|
|
OtaErr_t OTA_Suspend( void )
|
|
{
|
|
OtaErr_t err = OtaErrUninitialized;
|
|
OtaEventMsg_t eventMsg = { 0 };
|
|
|
|
/* Check if OTA Agent is running. */
|
|
if( otaAgent.state != OtaAgentStateStopped )
|
|
{
|
|
/* Stop the request timer. */
|
|
( void ) otaAgent.pOtaInterface->os.timer.stop( OtaRequestTimer );
|
|
|
|
/*
|
|
* Send event to OTA agent task.
|
|
*/
|
|
eventMsg.eventId = OtaAgentEventSuspend;
|
|
err = ( OTA_SignalEvent( &eventMsg ) == true ) ? OtaErrNone : OtaErrSignalEventFailed;
|
|
}
|
|
else
|
|
{
|
|
err = OtaErrAgentStopped;
|
|
|
|
LogWarn( ( "Failed to suspend OTA Agent: "
|
|
"OTA Agent is stopped: "
|
|
"OtaErr_t=%s",
|
|
OTA_Err_strerror( err ) ) );
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* Resume OTA Agent task.
|
|
*/
|
|
OtaErr_t OTA_Resume( void )
|
|
{
|
|
OtaErr_t err = OtaErrUninitialized;
|
|
OtaEventMsg_t eventMsg = { 0 };
|
|
|
|
/* Check if OTA Agent is running. */
|
|
if( otaAgent.state != OtaAgentStateStopped )
|
|
{
|
|
/*
|
|
* Send event to OTA agent task.
|
|
*/
|
|
eventMsg.eventId = OtaAgentEventResume;
|
|
err = ( OTA_SignalEvent( &eventMsg ) == true ) ? OtaErrNone : OtaErrSignalEventFailed;
|
|
}
|
|
else
|
|
{
|
|
err = OtaErrAgentStopped;
|
|
|
|
LogWarn( ( "Failed to resume OTA Agent: "
|
|
"OTA Agent is stopped: "
|
|
"OtaErr_t=%s",
|
|
OTA_Err_strerror( err ) ) );
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
/*-----------------------------------------------------------*/
|
|
|
|
const char * OTA_Err_strerror( OtaErr_t err )
|
|
{
|
|
const char * str = NULL;
|
|
|
|
switch( err )
|
|
{
|
|
case OtaErrNone:
|
|
str = "OtaErrNone";
|
|
break;
|
|
|
|
case OtaErrUninitialized:
|
|
str = "OtaErrUninitialized";
|
|
break;
|
|
|
|
case OtaErrPanic:
|
|
str = "OtaErrPanic";
|
|
break;
|
|
|
|
case OtaErrInvalidArg:
|
|
str = "OtaErrInvalidArg";
|
|
break;
|
|
|
|
case OtaErrAgentStopped:
|
|
str = "OtaErrAgentStopped";
|
|
break;
|
|
|
|
case OtaErrSignalEventFailed:
|
|
str = "OtaErrSignalEventFailed";
|
|
break;
|
|
|
|
case OtaErrRequestJobFailed:
|
|
str = "OtaErrRequestJobFailed";
|
|
break;
|
|
|
|
case OtaErrInitFileTransferFailed:
|
|
str = "OtaErrInitFileTransferFailed";
|
|
break;
|
|
|
|
case OtaErrRequestFileBlockFailed:
|
|
str = "OtaErrRequestFileBlockFailed";
|
|
break;
|
|
|
|
case OtaErrCleanupControlFailed:
|
|
str = "OtaErrCleanupControlFailed";
|
|
break;
|
|
|
|
case OtaErrCleanupDataFailed:
|
|
str = "OtaErrCleanupDataFailed";
|
|
break;
|
|
|
|
case OtaErrUpdateJobStatusFailed:
|
|
str = "OtaErrUpdateJobStatusFailed";
|
|
break;
|
|
|
|
case OtaErrJobParserError:
|
|
str = "OtaErrJobParserError";
|
|
break;
|
|
|
|
case OtaErrInvalidDataProtocol:
|
|
str = "OtaErrInvalidDataProtocol";
|
|
break;
|
|
|
|
case OtaErrMomentumAbort:
|
|
str = "OtaErrMomentumAbort";
|
|
break;
|
|
|
|
case OtaErrDowngradeNotAllowed:
|
|
str = "OtaErrDowngradeNotAllowed";
|
|
break;
|
|
|
|
case OtaErrSameFirmwareVersion:
|
|
str = "OtaErrSameFirmwareVersion";
|
|
break;
|
|
|
|
case OtaErrImageStateMismatch:
|
|
str = "OtaErrImageStateMismatch";
|
|
break;
|
|
|
|
case OtaErrNoActiveJob:
|
|
str = "OtaErrNoActiveJob";
|
|
break;
|
|
|
|
case OtaErrUserAbort:
|
|
str = "OtaErrUserAbort";
|
|
break;
|
|
|
|
case OtaErrFailedToEncodeCbor:
|
|
str = "OtaErrFailedToEncodeCbor";
|
|
break;
|
|
|
|
case OtaErrFailedToDecodeCbor:
|
|
str = "OtaErrFailedToDecodeCbor";
|
|
break;
|
|
|
|
case OtaErrActivateFailed:
|
|
str = "OtaErrActivateFailed";
|
|
break;
|
|
|
|
default:
|
|
str = "InvalidErrorCode";
|
|
break;
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
const char * OTA_JobParse_strerror( OtaJobParseErr_t err )
|
|
{
|
|
const char * str = NULL;
|
|
|
|
switch( err )
|
|
{
|
|
case OtaJobParseErrUnknown:
|
|
str = "OtaJobParseErrUnknown";
|
|
break;
|
|
|
|
case OtaJobParseErrNone:
|
|
str = "OtaJobParseErrNone";
|
|
break;
|
|
|
|
case OtaJobParseErrNullJob:
|
|
str = "OtaJobParseErrNullJob";
|
|
break;
|
|
|
|
case OtaJobParseErrUpdateCurrentJob:
|
|
str = "OtaJobParseErrUpdateCurrentJob";
|
|
break;
|
|
|
|
case OtaJobParseErrZeroFileSize:
|
|
str = "OtaJobParseErrZeroFileSize";
|
|
break;
|
|
|
|
case OtaJobParseErrNonConformingJobDoc:
|
|
str = "OtaJobParseErrNonConformingJobDoc";
|
|
break;
|
|
|
|
case OtaJobParseErrBadModelInitParams:
|
|
str = "OtaJobParseErrBadModelInitParams";
|
|
break;
|
|
|
|
case OtaJobParseErrNoContextAvailable:
|
|
str = "OtaJobParseErrNoContextAvailable";
|
|
break;
|
|
|
|
case OtaJobParseErrNoActiveJobs:
|
|
str = "OtaJobParseErrNoActiveJobs";
|
|
break;
|
|
|
|
default:
|
|
str = "InvalidErrorCode";
|
|
break;
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
const char * OTA_OsStatus_strerror( OtaOsStatus_t status )
|
|
{
|
|
const char * str = NULL;
|
|
|
|
switch( status )
|
|
{
|
|
case OtaOsSuccess:
|
|
str = "OtaOsSuccess";
|
|
break;
|
|
|
|
case OtaOsEventQueueCreateFailed:
|
|
str = "OtaOsEventQueueCreateFailed";
|
|
break;
|
|
|
|
case OtaOsEventQueueSendFailed:
|
|
str = "OtaOsEventQueueSendFailed";
|
|
break;
|
|
|
|
case OtaOsEventQueueReceiveFailed:
|
|
str = "OtaOsEventQueueReceiveFailed";
|
|
break;
|
|
|
|
case OtaOsEventQueueDeleteFailed:
|
|
str = "OtaOsEventQueueDeleteFailed";
|
|
break;
|
|
|
|
case OtaOsTimerCreateFailed:
|
|
str = "OtaOsTimerCreateFailed";
|
|
break;
|
|
|
|
case OtaOsTimerStartFailed:
|
|
str = "OtaOsTimerStartFailed";
|
|
break;
|
|
|
|
case OtaOsTimerRestartFailed:
|
|
str = "OtaOsTimerRestartFailed";
|
|
break;
|
|
|
|
case OtaOsTimerStopFailed:
|
|
str = "OtaOsTimerStopFailed";
|
|
break;
|
|
|
|
case OtaOsTimerDeleteFailed:
|
|
str = "OtaOsTimerDeleteFailed";
|
|
break;
|
|
|
|
default:
|
|
str = "InvalidErrorCode";
|
|
break;
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
const char * OTA_PalStatus_strerror( OtaPalMainStatus_t status )
|
|
{
|
|
const char * str = NULL;
|
|
|
|
switch( status )
|
|
{
|
|
case OtaPalSuccess:
|
|
str = "OtaPalSuccess";
|
|
break;
|
|
|
|
case OtaPalUninitialized:
|
|
str = "OtaPalUninitialized";
|
|
break;
|
|
|
|
case OtaPalOutOfMemory:
|
|
str = "OtaPalOutOfMemory";
|
|
break;
|
|
|
|
case OtaPalNullFileContext:
|
|
str = "OtaPalNullFileContext";
|
|
break;
|
|
|
|
case OtaPalSignatureCheckFailed:
|
|
str = "OtaPalSignatureCheckFailed";
|
|
break;
|
|
|
|
case OtaPalRxFileCreateFailed:
|
|
str = "OtaPalRxFileCreateFailed";
|
|
break;
|
|
|
|
case OtaPalRxFileTooLarge:
|
|
str = "OtaPalRxFileTooLarge";
|
|
break;
|
|
|
|
case OtaPalBootInfoCreateFailed:
|
|
str = "OtaPalBootInfoCreateFailed";
|
|
break;
|
|
|
|
case OtaPalBadSignerCert:
|
|
str = "OtaPalBadSignerCert";
|
|
break;
|
|
|
|
case OtaPalBadImageState:
|
|
str = "OtaPalBadImageState";
|
|
break;
|
|
|
|
case OtaPalAbortFailed:
|
|
str = "OtaPalAbortFailed";
|
|
break;
|
|
|
|
case OtaPalRejectFailed:
|
|
str = "OtaPalRejectFailed";
|
|
break;
|
|
|
|
case OtaPalCommitFailed:
|
|
str = "OtaPalCommitFailed";
|
|
break;
|
|
|
|
case OtaPalActivateFailed:
|
|
str = "OtaPalActivateFailed";
|
|
break;
|
|
|
|
case OtaPalFileAbort:
|
|
str = "OtaPalFileAbort";
|
|
break;
|
|
|
|
case OtaPalFileClose:
|
|
str = "OtaPalFileClose";
|
|
break;
|
|
|
|
default:
|
|
str = "InvalidErrorCode";
|
|
break;
|
|
}
|
|
|
|
return str;
|
|
}
|
|
/*-----------------------------------------------------------*/
|