/* * Copyright 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 #include #include #include #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, int 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, int 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 lengt of the value */ static void buildIdexedFileQueryString( int 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 Sucessfullly 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, int fileIndex, AfrOtaJobDocumentFields_t * result ) { bool populatedJobDocFields = false; JSONStatus_t jsonResult = JSONNotFound; const char * protocol; 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_Search( ( char * ) jobDoc, jobDocLength, "afr_ota.protocols[0]", 20U, ( char ** ) &protocol, &protocolLength ); } /* Determine if the supported protocol is MQTT or HTTP */ if( ( jsonResult == JSONSuccess ) && ( protocolLength == 4U ) ) { if( strncmp( "MQTT", protocol, protocolLength ) == 0U ) { 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, int fileIndex, AfrOtaJobDocumentFields_t * result ) { JSONStatus_t jsonResult = JSONNotFound; char * jsonValue; size_t jsonValueLength = 0U; char queryString[ 33 ]; size_t queryStringLength; buildIdexedFileQueryString( fileIndex, "filesize", 8U, queryString, &queryStringLength ); jsonResult = searchUintValue( jobDoc, jobDocLength, queryString, queryStringLength, &( result->fileSize ) ); if( jsonResult == JSONSuccess ) { buildIdexedFileQueryString( fileIndex, "fileid", 6U, queryString, &queryStringLength ); jsonResult = searchUintValue( jobDoc, jobDocLength, queryString, queryStringLength, &( result->fileId ) ); } if( jsonResult == JSONSuccess ) { buildIdexedFileQueryString( fileIndex, "filepath", 8U, queryString, &queryStringLength ); jsonResult = JSON_Search( ( char * ) jobDoc, jobDocLength, queryString, queryStringLength, &jsonValue, &jsonValueLength ); result->filepath = jsonValue; result->filepathLen = ( uint32_t ) jsonValueLength; } if( jsonResult == JSONSuccess ) { buildIdexedFileQueryString( fileIndex, "certfile", 8U, queryString, &queryStringLength ); jsonResult = JSON_Search( ( char * ) jobDoc, jobDocLength, queryString, queryStringLength, &jsonValue, &jsonValueLength ); result->certfile = jsonValue; result->certfileLen = ( uint32_t ) jsonValueLength; } if( jsonResult == JSONSuccess ) { buildIdexedFileQueryString( fileIndex, "sig-sha256-ecdsa", 16U, queryString, &queryStringLength ); jsonResult = JSON_Search( ( char * ) jobDoc, jobDocLength, queryString, queryStringLength, &jsonValue, &jsonValueLength ); result->signature = jsonValue; result->signatureLen = ( uint32_t ) jsonValueLength; } return jsonResult; } static JSONStatus_t populateMqttStreamingFields( const char * jobDoc, const size_t jobDocLength, AfrOtaJobDocumentFields_t * result ) { JSONStatus_t jsonResult = JSONNotFound; char * jsonValue; size_t jsonValueLength = 0U; jsonResult = JSON_Search( ( char * ) jobDoc, jobDocLength, "afr_ota.streamname", 18U, &jsonValue, &jsonValueLength ); 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, int fileIndex, AfrOtaJobDocumentFields_t * result ) { JSONStatus_t jsonResult = JSONNotFound; char * jsonValue; size_t jsonValueLength = 0U; char queryString[ 33 ]; size_t queryStringLength; buildIdexedFileQueryString( fileIndex, "fileType", 8U, queryString, &queryStringLength ); jsonResult = searchUintValue( ( char * ) jobDoc, jobDocLength, queryString, queryStringLength, &( result->fileType ) ); if( jsonResult == JSONSuccess ) { buildIdexedFileQueryString( fileIndex, "auth_scheme", 11U, queryString, &queryStringLength ); jsonResult = JSON_Search( ( char * ) jobDoc, jobDocLength, queryString, queryStringLength, &jsonValue, &jsonValueLength ); result->authScheme = jsonValue; result->authSchemeLen = ( uint32_t ) jsonValueLength; } if( jsonResult == JSONSuccess ) { buildIdexedFileQueryString( fileIndex, "update_data_url", 15U, queryString, &queryStringLength ); jsonResult = JSON_Search( ( char * ) jobDoc, jobDocLength, queryString, queryStringLength, &jsonValue, &jsonValueLength ); 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 buildIdexedFileQueryString( int fileIndex, const char * queryString, size_t queryStringLength, char * result, size_t * resultLength ) { memcpy( result, "afr_ota.files[", 14U ); result[ 14 ] = ( char ) ( fileIndex + ( int ) '0' ); memcpy( ( result + 15 ), "].", 2U ); 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; char * jsonValue; size_t jsonValueLength = 0U; jsonResult = JSON_Search( ( char * ) jobDoc, jobDocLength, query, queryLength, &jsonValue, &jsonValueLength ); 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, c - '0' ) ) { retVal += ( c - '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 ); }