source/otaJobParser/job_parser.c (365 lines of code) (raw):

/* * AWS IoT Jobs v2.0.0 * Copyright (C) 2023 Amazon.com, Inc. and its affiliates. All Rights Reserved. * SPDX-License-Identifier: MIT * * Licensed under the MIT License. See the LICENSE accompanying this file * for the specific language governing permissions and limitations under * the License. */ #include <stdbool.h> #include <stdint.h> #include <stdlib.h> #include <string.h> #include "core_json.h" #include "job_parser.h" /** * @brief Populates common job document fields in result * * @param jobDoc FreeRTOS OTA job document * @param jobDocLength OTA job document length * @param fileIndex The index of the file to use * @param result Job document structure to populate * @return JSONStatus_t JSON parsing status */ static JSONStatus_t populateCommonFields( const char * jobDoc, const size_t jobDocLength, int32_t fileIndex, AfrOtaJobDocumentFields_t * result ); /** * @brief Populates optional, common job document fields in result * * @param jobDoc FreeRTOS OTA job document * @param jobDocLength OTA job document length * @param fileIndex The index of the file to use * @param result Job document structure to populate * @return JSONStatus_t JSON parsing status */ static JSONStatus_t populateOptionalCommonFields( const char * jobDoc, const size_t jobDocLength, int32_t fileIndex, AfrOtaJobDocumentFields_t * result ); /** * @brief Populates MQTT job document fields in result * * @param jobDoc FreeRTOS OTA job document * @param jobDocLength OTA job document length * @param result Job document structure to populate * @return JSONStatus_t JSON parsing status */ static JSONStatus_t populateMqttStreamingFields( const char * jobDoc, const size_t jobDocLength, AfrOtaJobDocumentFields_t * result ); /** * @brief Populates HTTP job document fields in result * * @param jobDoc FreeRTOS OTA job document * @param jobDocLength OTA job document length * @param fileIndex The index of the file to use * @param result Job document structure to populate * @return JSONStatus_t JSON parsing status */ static JSONStatus_t populateHttpStreamingFields( const char * jobDoc, const size_t jobDocLength, int32_t fileIndex, AfrOtaJobDocumentFields_t * result ); /** * @brief Assembles an indexed OTA file query * * @param fileIndex The file index * @param queryString The JSON element inside of the File JSON structure to * search for * @param queryStringLength The length of the query * @param result The resulting value of the query key * @param resultLength The length of the value */ static void buildIndexedFileQueryString( int32_t fileIndex, const char * queryString, size_t queryStringLength, char * result, size_t * resultLength ); /** * @brief Searches the JSON document for the uint32_t value * * @param jobDoc FreeRTOS OTA job document * @param jobDocLength OTA job document length * @param query The JSON path to query * @param queryLength The length of the JSON path * @param value Pointer to set uint32_t value * @return JSONStatus_t JSON parsing status */ static JSONStatus_t searchUintValue( const char * jobDoc, const size_t jobDocLength, const char * query, const size_t queryLength, uint32_t * value ); /** * @brief Convert a non-null terminated string to a unsigned 32-bit integer * * @param string String representation of 32-bit unsigned integer * @param length Length of the integer when represented as a string * @param value Unsigned 32-bit integer representation of the value * @return true Successfully converted to uint32 * @return false Unsuccessfully converted to uint32 */ static bool uintFromString( const char * string, const uint32_t length, uint32_t * value ); /** * @brief Check if a character is a digit * * @param c Character to validate * @return true Character is a 0-9 digit * @return false Character is not a digit */ static bool charIsDigit( const char c ); /** * @brief Check for multiplication overflow between two uint32 values * * @param a First uint32 value * @param b Second uint32 value * @return true If overflow will occur * @return false If overflow will not occur */ static bool multOverflowUnit32( const uint32_t a, const uint32_t b ); /** * @brief Check for addition overflow between two uint32 values * * @param a First uint32 value * @param b Second uint32 value * @return true If overflow will occur * @return false If overflow will not occur */ static bool addOverflowUint32( const uint32_t a, const uint32_t b ); bool populateJobDocFields( const char * jobDoc, const size_t jobDocLength, int32_t fileIndex, AfrOtaJobDocumentFields_t * result ) { bool populatedJobDocFields = false; JSONStatus_t jsonResult = JSONNotFound; const char * protocol = NULL; size_t protocolLength = 0U; /* TODO - Add assertions for NULL job docs or 0 length documents*/ jsonResult = populateCommonFields( jobDoc, jobDocLength, fileIndex, result ); if( jsonResult == JSONSuccess ) { jsonResult = JSON_SearchConst( jobDoc, jobDocLength, "afr_ota.protocols[0]", 20U, &protocol, &protocolLength, NULL ); } /* Determine if the supported protocol is MQTT or HTTP */ if( ( jsonResult == JSONSuccess ) && ( protocolLength == 4U ) ) { if( strncmp( "MQTT", protocol, protocolLength ) == 0 ) { jsonResult = populateMqttStreamingFields( jobDoc, jobDocLength, result ); } else { jsonResult = populateHttpStreamingFields( jobDoc, jobDocLength, fileIndex, result ); } } populatedJobDocFields = ( jsonResult == JSONSuccess ); /* Should this nullify the fields which have been populated before * returning? */ return populatedJobDocFields; } static JSONStatus_t populateCommonFields( const char * jobDoc, const size_t jobDocLength, int32_t fileIndex, AfrOtaJobDocumentFields_t * result ) { JSONStatus_t jsonResult = JSONNotFound; const char * jsonValue = NULL; size_t jsonValueLength = 0U; char queryString[ 33 ]; size_t queryStringLength; if( fileIndex <= 9 ) { buildIndexedFileQueryString( fileIndex, "filesize", 8U, queryString, &queryStringLength ); jsonResult = searchUintValue( jobDoc, jobDocLength, queryString, queryStringLength, &( result->fileSize ) ); } else { jsonResult = JSONIllegalDocument; } if( jsonResult == JSONSuccess ) { buildIndexedFileQueryString( fileIndex, "fileid", 6U, queryString, &queryStringLength ); jsonResult = searchUintValue( jobDoc, jobDocLength, queryString, queryStringLength, &( result->fileId ) ); } if( jsonResult == JSONSuccess ) { buildIndexedFileQueryString( fileIndex, "filepath", 8U, queryString, &queryStringLength ); jsonResult = JSON_SearchConst( jobDoc, jobDocLength, queryString, queryStringLength, &jsonValue, &jsonValueLength, NULL ); result->filepath = jsonValue; result->filepathLen = ( uint32_t ) jsonValueLength; } if( jsonResult == JSONSuccess ) { buildIndexedFileQueryString( fileIndex, "certfile", 8U, queryString, &queryStringLength ); jsonResult = JSON_SearchConst( jobDoc, jobDocLength, queryString, queryStringLength, &jsonValue, &jsonValueLength, NULL ); result->certfile = jsonValue; result->certfileLen = ( uint32_t ) jsonValueLength; } if( jsonResult == JSONSuccess ) { buildIndexedFileQueryString( fileIndex, "sig-sha256-ecdsa", 16U, queryString, &queryStringLength ); jsonResult = JSON_SearchConst( jobDoc, jobDocLength, queryString, queryStringLength, &jsonValue, &jsonValueLength, NULL ); result->signature = jsonValue; result->signatureLen = ( uint32_t ) jsonValueLength; } if( jsonResult == JSONSuccess ) { jsonResult = populateOptionalCommonFields( jobDoc, jobDocLength, fileIndex, result ); } return jsonResult; } static JSONStatus_t populateOptionalCommonFields( const char * jobDoc, const size_t jobDocLength, int32_t fileIndex, AfrOtaJobDocumentFields_t * result ) { JSONStatus_t jsonResult = JSONNotFound; char queryString[ 33 ]; size_t queryStringLength; buildIndexedFileQueryString( fileIndex, "fileType", 8U, queryString, &queryStringLength ); jsonResult = searchUintValue( jobDoc, jobDocLength, queryString, queryStringLength, &( result->fileType ) ); return ( jsonResult == JSONBadParameter ) ? jsonResult : JSONSuccess; } static JSONStatus_t populateMqttStreamingFields( const char * jobDoc, const size_t jobDocLength, AfrOtaJobDocumentFields_t * result ) { JSONStatus_t jsonResult = JSONNotFound; const char * jsonValue = NULL; size_t jsonValueLength = 0U; jsonResult = JSON_SearchConst( jobDoc, jobDocLength, "afr_ota.streamname", 18U, &jsonValue, &jsonValueLength, NULL ); result->imageRef = jsonValue; result->imageRefLen = ( uint32_t ) jsonValueLength; /* If the stream name is empty, consider this an error */ if( jsonValueLength == 0U ) { jsonResult = JSONNotFound; } return jsonResult; } static JSONStatus_t populateHttpStreamingFields( const char * jobDoc, const size_t jobDocLength, int32_t fileIndex, AfrOtaJobDocumentFields_t * result ) { JSONStatus_t jsonResult = JSONNotFound; const char * jsonValue = NULL; size_t jsonValueLength = 0U; char queryString[ 33 ]; size_t queryStringLength; buildIndexedFileQueryString( fileIndex, "auth_scheme", 11U, queryString, &queryStringLength ); jsonResult = JSON_SearchConst( jobDoc, jobDocLength, queryString, queryStringLength, &jsonValue, &jsonValueLength, NULL ); result->authScheme = jsonValue; result->authSchemeLen = ( uint32_t ) jsonValueLength; if( jsonResult == JSONSuccess ) { buildIndexedFileQueryString( fileIndex, "update_data_url", 15U, queryString, &queryStringLength ); jsonResult = JSON_SearchConst( jobDoc, jobDocLength, queryString, queryStringLength, &jsonValue, &jsonValueLength, NULL ); result->imageRef = jsonValue; result->imageRefLen = ( uint32_t ) jsonValueLength; /* If the url is empty, consider this an error */ if( jsonValueLength == 0U ) { jsonResult = JSONNotFound; } } return jsonResult; } static void buildIndexedFileQueryString( int32_t fileIndex, const char * queryString, size_t queryStringLength, char * result, size_t * resultLength ) { /*TODO: Should there be a check on the length of the result buffer? */ ( void ) strncpy( result, ( const char * ) "afr_ota.files[", 15U ); int32_t index = ( fileIndex + ( int32_t ) '0' ); result[ 14 ] = ( char ) index; ( void ) strncpy( &result[ 15 ], ( const char * ) "].", 3U ); ( void ) memcpy( &result[ 17 ], queryString, queryStringLength ); *resultLength = 17U + queryStringLength; } static JSONStatus_t searchUintValue( const char * jobDoc, const size_t jobDocLength, const char * query, const size_t queryLength, uint32_t * value ) { bool numConversionSuccess = true; JSONStatus_t jsonResult = JSONNotFound; const char * jsonValue = NULL; size_t jsonValueLength = 0U; jsonResult = JSON_SearchConst( jobDoc, jobDocLength, query, queryLength, &jsonValue, &jsonValueLength, NULL ); if( jsonResult == JSONSuccess ) { numConversionSuccess = uintFromString( jsonValue, ( const uint32_t ) jsonValueLength, value ); } return ( numConversionSuccess ) ? jsonResult : JSONBadParameter; } static bool uintFromString( const char * string, const uint32_t length, uint32_t * value ) { bool ret = false; bool overflow = false; uint32_t retVal = 0U; size_t i; if( ( string != NULL ) && ( value != NULL ) ) { for( i = 0U; ( i < length ) && !overflow; i++ ) { char c = string[ i ]; if( !charIsDigit( c ) ) { break; } else { if( !multOverflowUnit32( retVal, 10U ) ) { retVal *= 10U; if( !addOverflowUint32( retVal, ( ( uint32_t ) c - ( uint32_t ) '0' ) ) ) { retVal += ( ( uint32_t ) c - ( uint32_t ) '0' ); } else { overflow = true; } } else { overflow = true; } } } if( ( length > 0U ) && ( i == length ) ) { *value = retVal; ret = true; } } return ret; } static bool charIsDigit( const char c ) { return ( c >= '0' ) && ( c <= '9' ); } static bool multOverflowUnit32( const uint32_t a, const uint32_t b ) { return ( b > 0U ) && ( a > ( UINT32_MAX / b ) ); } static bool addOverflowUint32( const uint32_t a, const uint32_t b ) { return a > ( UINT32_MAX - b ); }