/* * AWS IoT Over-the-air Update v3.3.0 * Copyright (C) 2021 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 #include #include #include #include #include /* 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 = JobReasonReceiving; 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(); if( otaAgent.statistics.otaPacketsProcessed < UINT32_MAX ) { /* 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 ) OTA_PAL_MAIN_ERR( closeResult ); jobDoc.subReason = ( int32_t ) OTA_PAL_SUB_ERR( closeResult ); /* Update the job status with the with failure code. */ err = otaControlInterface.updateJobStatus( &otaAgent, JobStatusFailedWithVal, ( int32_t ) OTA_PAL_MAIN_ERR( closeResult ), ( int32_t ) OTA_PAL_SUB_ERR( closeResult ) ); dataHandlerCleanup(); /* Let main application know activate event. */ otaAgent.OtaAppCallback( OtaJobEventFail, &jobDoc ); } else { if( result == IngestResultAccepted_Continue ) { if( otaAgent.statistics.otaPacketsProcessed < UINT32_MAX ) { /* Last 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 = Base64Success; 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; if( ( ModelParamTypeStringCopy == docParam.modelParamType ) || ( ModelParamTypeArrayCopy == docParam.modelParamType ) ) { /* Get destination buffer size to parameter storage location. */ pParamSizeAdd = ( void * ) ( ( uint8_t * ) pContextBase + docParam.pDestSizeOffset ); 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; /* Suppress warning about use of uninitialized variable. */ previousVersion.u.unsignedVersion32 = 0; ( void ) previousVersion; /* 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( ( pUpdateFile != NULL ) && ( pUpdateFile->fileSize > OTA_MAX_FILE_SIZE ) ) { err = OtaErrFileSizeOverflow; } if( ( err == OtaErrNone ) && ( updateJob == false ) && ( platformInSelftest() == false ) && ( pUpdateFile != NULL ) ) { /* 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; /* This conditional statement has been excluded from the coverage report because one of branches in the * if conditions cannot be reached because it is not possible for the code to have * pUpdateFile->blockBitmapMaxSize not equal to 0 and pUpdateFile->pRxBlockBitmap equal to NULL. */ /* LCOV_EXCL_START */ if( ( pUpdateFile->pRxBlockBitmap != NULL ) && ( pUpdateFile->blockBitmapMaxSize == 0u ) ) { otaAgent.pOtaInterface->os.mem.free( pUpdateFile->pRxBlockBitmap ); } /* LCOV_EXCL_STOP */ if( pUpdateFile->blockBitmapMaxSize == 0u ) { 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 ) ( 0xFF & ( ~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; } } } if( ( err != OtaErrNone ) || ( ( pUpdateFile != NULL ) && ( pUpdateFile->pRxBlockBitmap == NULL ) ) ) { otaClose( pUpdateFile ); pUpdateFile = NULL; 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 ) ( 0xFF & ( ~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 = 0; ( 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; } /*-----------------------------------------------------------*/