source/jobs.c (640 lines of code) (raw):

/* * AWS IoT Jobs v2.0.0 * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * SPDX-License-Identifier: MIT * * 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 jobs.c * @brief Implementation of the APIs from jobs.h. */ #include <assert.h> #include <string.h> #include <stdbool.h> /* Internal Includes */ #include "jobs.h" /* External Dependencies */ #include "core_json.h" /** @cond DO_NOT_DOCUMENT */ /** * @brief Get the length of a string literal. */ #define CONST_STRLEN( x ) ( sizeof( ( x ) ) - 1U ) /** * @brief Get the length on an array. */ #define ARRAY_LENGTH( x ) ( sizeof( ( x ) ) / sizeof( ( x )[ 0 ] ) ) /** * @brief Table of topic API strings in JobsTopic_t order. */ static const char * const apiTopic[] = { JOBS_API_JOBSCHANGED, JOBS_API_NEXTJOBCHANGED, JOBS_API_GETPENDING JOBS_API_SUCCESS, JOBS_API_GETPENDING JOBS_API_FAILURE, JOBS_API_STARTNEXT JOBS_API_SUCCESS, JOBS_API_STARTNEXT JOBS_API_FAILURE, JOBS_API_DESCRIBE JOBS_API_SUCCESS, JOBS_API_DESCRIBE JOBS_API_FAILURE, JOBS_API_UPDATE JOBS_API_SUCCESS, JOBS_API_UPDATE JOBS_API_FAILURE, }; /** * @brief Table of topic API string lengths in JobsTopic_t order. */ static const size_t apiTopicLength[] = { JOBS_API_JOBSCHANGED_LENGTH, JOBS_API_NEXTJOBCHANGED_LENGTH, JOBS_API_GETPENDING_LENGTH + JOBS_API_SUCCESS_LENGTH, JOBS_API_GETPENDING_LENGTH + JOBS_API_FAILURE_LENGTH, JOBS_API_STARTNEXT_LENGTH + JOBS_API_SUCCESS_LENGTH, JOBS_API_STARTNEXT_LENGTH + JOBS_API_FAILURE_LENGTH, JOBS_API_DESCRIBE_LENGTH + JOBS_API_SUCCESS_LENGTH, JOBS_API_DESCRIBE_LENGTH + JOBS_API_FAILURE_LENGTH, JOBS_API_UPDATE_LENGTH + JOBS_API_SUCCESS_LENGTH, JOBS_API_UPDATE_LENGTH + JOBS_API_FAILURE_LENGTH, }; static const char * const jobStatusString[] = { "QUEUED", "IN_PROGRESS", "FAILED", "SUCCEEDED", "REJECTED" }; /** * @brief Predicate returns true for a valid thing name or job ID character. * * @param[in] a character to check * @param[in] allowColon set to true for thing names * * @return true if the character is valid; * false otherwise */ static bool isValidChar( char a, bool allowColon ) { bool ret; if( ( a == '-' ) || ( a == '_' ) ) { ret = true; } else if( ( a >= '0' ) && ( a <= '9' ) ) { ret = true; } else if( ( a >= 'A' ) && ( a <= 'Z' ) ) { ret = true; } else if( ( a >= 'a' ) && ( a <= 'z' ) ) { ret = true; } else if( a == ':' ) { ret = allowColon; } else { ret = false; } return ret; } /** * @brief Predicate returns true for a valid identifier. * * The identifier may be a thing name or a job ID. * * @param[in] id character sequence to check * @param[in] length length of the character sequence * @param[in] max maximum length of a valid identifier * @param[in] allowColon set to true for thing names * * @return true if the identifier is valid; * false otherwise */ static bool isValidID( const char * id, uint16_t length, uint16_t max, bool allowColon ) { bool ret = false; if( ( id != NULL ) && ( length > 0U ) && ( length <= max ) ) { size_t i; for( i = 0; i < length; i++ ) { if( isValidChar( id[ i ], allowColon ) == false ) { break; } } ret = ( i == length ) ? true : false; } return ret; } /** * @brief Predicate returns true for a valid thing name string. * * @param[in] thingName character sequence to check * @param[in] thingNameLength length of the character sequence * * @return true if the thing name is valid; * false otherwise */ static bool isValidThingName( const char * thingName, uint16_t thingNameLength ) { return isValidID( thingName, thingNameLength, THINGNAME_MAX_LENGTH, true ); } /** * @brief Predicate returns true for a valid job ID string. * * @param[in] jobId character sequence to check * @param[in] jobIdLength length of the character sequence * * @return true if the job ID is valid; * false otherwise */ static bool isValidJobId( const char * jobId, uint16_t jobIdLength ) { return isValidID( jobId, jobIdLength, JOBID_MAX_LENGTH, false ); } /** * @brief A strncpy replacement based on lengths only. * * @param[in] buffer The buffer to be written. * @param[in,out] start The index at which to begin. * @param[in] max The size of the buffer. * @param[in] value The characters to copy. * @param[in] valueLength How many characters to copy. * * @return #JobsSuccess if the value was written to the buffer; * #JobsBufferTooSmall if the buffer cannot hold the entire value. * * @note There is no harm from calling this function when * start >= max. This allows for sequential calls to * strnAppend() where only the final call's return value * is needed. */ static JobsStatus_t strnAppend( char * buffer, size_t * start, size_t max, const char * value, size_t valueLength ) { size_t i, j = 0; assert( ( buffer != NULL ) && ( start != NULL ) && ( value != NULL ) ); i = *start; while( ( i < max ) && ( j < valueLength ) ) { buffer[ i ] = value[ j ]; i++; j++; } *start = i; return ( i < max ) ? JobsSuccess : JobsBufferTooSmall; } /** * @brief Populate the common leading portion of a topic string. * * @param[in] buffer The buffer to contain the topic string. * @param[in,out] start The index at which to begin. * @param[in] length The size of the buffer. * @param[in] thingName The device's thingName as registered with AWS IoT. * @param[in] thingNameLength The length of the thingName. */ static void writePreamble( char * buffer, size_t * start, size_t length, const char * thingName, uint16_t thingNameLength ) { ( void ) strnAppend( buffer, start, length, JOBS_API_PREFIX, JOBS_API_PREFIX_LENGTH ); ( void ) strnAppend( buffer, start, length, thingName, thingNameLength ); ( void ) strnAppend( buffer, start, length, JOBS_API_BRIDGE, JOBS_API_BRIDGE_LENGTH ); } #define checkThingParams() \ ( isValidThingName( thingName, thingNameLength ) == true ) #define checkCommonParams() \ ( ( buffer != NULL ) && ( length > 0UL ) && checkThingParams() ) /** @endcond */ /** * See jobs.h for docs. * * @brief Populate a topic string for a subscription request. */ JobsStatus_t Jobs_GetTopic( char * buffer, size_t length, const char * thingName, uint16_t thingNameLength, JobsTopic_t api, size_t * outLength ) { JobsStatus_t ret = JobsBadParameter; size_t start = 0U; if( checkCommonParams() && ( api > JobsInvalidTopic ) && ( api < JobsMaxTopic ) ) { writePreamble( buffer, &start, length, thingName, thingNameLength ); if( api >= JobsDescribeSuccess ) { ( void ) strnAppend( buffer, &start, length, "+/", ( CONST_STRLEN( "+/" ) ) ); } ret = strnAppend( buffer, &start, length, apiTopic[ api ], apiTopicLength[ api ] ); if( start == length ) { start--; } buffer[ start ] = '\0'; if( outLength != NULL ) { *outLength = start; } } return ret; } /** @cond DO_NOT_DOCUMENT */ /** * @brief Compare the leading n bytes of two character sequences. * * @param[in] a first character sequence * @param[in] b second character sequence * @param[in] n number of bytes * * @return JobsSuccess if the sequences are the same; * JobsNoMatch otherwise */ static JobsStatus_t strnEquals( const char * a, const char * b, size_t n ) { size_t i; assert( ( a != NULL ) && ( b != NULL ) ); for( i = 0U; i < n; i++ ) { if( a[ i ] != b[ i ] ) { break; } } return ( i == n ) ? JobsSuccess : JobsNoMatch; } /** * @brief Wrap strnEquals() with a check to compare two lengths. * * @param[in] a first character sequence * @param[in] aLength Length of a * @param[in] b second character sequence * @param[in] bLength Length of b * * @return JobsSuccess if the sequences are the same; * JobsNoMatch otherwise */ static JobsStatus_t strnnEq( const char * a, size_t aLength, const char * b, size_t bLength ) { JobsStatus_t ret = JobsNoMatch; if( aLength == bLength ) { ret = strnEquals( a, b, aLength ); } return ret; } /** * @brief Predicate returns true for a match to JOBS_API_JOBID_NEXT * * @param[in] jobId character sequence to check * @param[in] jobIdLength length of the character sequence * * @return true if the job ID matches; * false otherwise */ static bool isNextJobId( const char * jobId, uint16_t jobIdLength ) { bool ret = false; if( ( jobId != NULL ) && ( strnnEq( JOBS_API_JOBID_NEXT, JOBS_API_JOBID_NEXT_LENGTH, jobId, jobIdLength ) == JobsSuccess ) ) { ret = true; } return ret; } /** * @brief Parse a job ID and search for the API portion of a topic string in a table. * * @param[in] topic The topic string to check. * @param[in] topicLength The length of the topic string. * @param[out] outApi The jobs topic API value if present, e.g., #JobsUpdateSuccess. * @param[out] outJobId The beginning of the jobID in the topic string. * @param[out] outJobIdLength The length of the jobID in the topic string. * * @return #JobsSuccess if a matching topic was found; * #JobsNoMatch if a matching topic was NOT found * (parameter outApi gets #JobsInvalidTopic ). */ static JobsStatus_t matchIdApi( char * topic, size_t topicLength, JobsTopic_t * outApi, char ** outJobId, uint16_t * outJobIdLength ) { JobsStatus_t ret = JobsNoMatch; size_t i; char * p = topic; size_t length = topicLength; char * jobId = NULL; uint16_t jobIdLength = 0U; assert( ( topic != NULL ) && ( outApi != NULL ) && ( outJobId != NULL ) && ( outJobIdLength != NULL ) ); for( i = 0U; i < length; i++ ) { if( ( i > 0U ) && ( p[ i ] == '/' ) ) { /* Save the leading job ID and its length. */ jobId = p; jobIdLength = ( uint16_t ) i; break; } } /* Advance p to after the '/' and reduce buffer length * for the remaining API search. */ p = &p[ jobIdLength + 1U ]; length = length - jobIdLength - 1U; if( ( isNextJobId( jobId, jobIdLength ) == true ) || ( isValidJobId( jobId, jobIdLength ) == true ) ) { if( JobsSuccess == strnnEq( p, length, apiTopic[ JobsDescribeSuccess ], apiTopicLength[ JobsDescribeSuccess ] ) ) { ret = JobsSuccess; *outApi = JobsDescribeSuccess; } else if( JobsSuccess == strnnEq( p, length, apiTopic[ JobsDescribeFailed ], apiTopicLength[ JobsDescribeFailed ] ) ) { ret = JobsSuccess; *outApi = JobsDescribeFailed; } else if( JobsSuccess == strnnEq( p, length, apiTopic[ JobsUpdateSuccess ], apiTopicLength[ JobsUpdateSuccess ] ) ) { ret = JobsSuccess; *outApi = JobsUpdateSuccess; } else if( JobsSuccess == strnnEq( p, length, apiTopic[ JobsUpdateFailed ], apiTopicLength[ JobsUpdateFailed ] ) ) { ret = JobsSuccess; *outApi = JobsUpdateFailed; } else { /* MISRA Empty Body */ } if( ret == JobsSuccess ) { *outJobId = jobId; *outJobIdLength = jobIdLength; } } return ret; } /** * @brief Search for the API portion of a topic string in a table. * * @param[in] topic The topic string to check. * @param[in] topicLength The length of the topic string. * @param[out] outApi The jobs topic API value if present, e.g., #JobsUpdateSuccess. * @param[out] outJobId The beginning of the jobID in the topic string. * @param[out] outJobIdLength The length of the jobID in the topic string. * * @return #JobsSuccess if a matching topic was found; * #JobsNoMatch if a matching topic was NOT found * (parameter outApi gets #JobsInvalidTopic ). */ static JobsStatus_t matchApi( char * topic, size_t topicLength, JobsTopic_t * outApi, char ** outJobId, uint16_t * outJobIdLength ) { JobsStatus_t ret = JobsNoMatch; assert( ( topic != NULL ) && ( outApi != NULL ) && ( outJobId != NULL ) && ( outJobIdLength != NULL ) ); /* The first set of APIs do not have job IDs. */ if( JobsSuccess == strnnEq( topic, topicLength, apiTopic[ JobsJobsChanged ], apiTopicLength[ JobsJobsChanged ] ) ) { ret = JobsSuccess; *outApi = JobsJobsChanged; } else if( JobsSuccess == strnnEq( topic, topicLength, apiTopic[ JobsNextJobChanged ], apiTopicLength[ JobsNextJobChanged ] ) ) { ret = JobsSuccess; *outApi = JobsNextJobChanged; } else if( JobsSuccess == strnnEq( topic, topicLength, apiTopic[ JobsGetPendingSuccess ], apiTopicLength[ JobsGetPendingSuccess ] ) ) { ret = JobsSuccess; *outApi = JobsGetPendingSuccess; } else if( JobsSuccess == strnnEq( topic, topicLength, apiTopic[ JobsGetPendingFailed ], apiTopicLength[ JobsGetPendingFailed ] ) ) { ret = JobsSuccess; *outApi = JobsGetPendingFailed; } else if( JobsSuccess == strnnEq( topic, topicLength, apiTopic[ JobsStartNextSuccess ], apiTopicLength[ JobsStartNextSuccess ] ) ) { ret = JobsSuccess; *outApi = JobsStartNextSuccess; } else if( JobsSuccess == strnnEq( topic, topicLength, apiTopic[ JobsStartNextFailed ], apiTopicLength[ JobsStartNextFailed ] ) ) { ret = JobsSuccess; *outApi = JobsStartNextFailed; } else { /* MISRA Empty Body */ } /* The remaining APIs must have a job ID. */ if( ret == JobsNoMatch ) { ret = matchIdApi( topic, topicLength, outApi, outJobId, outJobIdLength ); } return ret; } static bool isThingnameTopicMatch( const char * topic, const size_t topicLength, const char * topicSuffix, const size_t topicSuffixLength, const char * thingName, const size_t thingNameLength ) { char expectedTopicBuffer[ TOPIC_BUFFER_SIZE + 1 ] = { '\0' }; bool isMatch = true; size_t start = 0U; if( ( topic == NULL ) || ( topicLength == 0U ) ) { isMatch = false; } else if( ( thingName == NULL ) || ( thingNameLength == 0U ) ) { isMatch = false; } else { /* Empty MISRA body */ } if( isMatch ) { writePreamble( expectedTopicBuffer, &start, TOPIC_BUFFER_SIZE, thingName, ( uint16_t ) thingNameLength ); ( void ) strnAppend( expectedTopicBuffer, &start, TOPIC_BUFFER_SIZE, topicSuffix, topicSuffixLength ); isMatch = ( size_t ) strnlen( expectedTopicBuffer, TOPIC_BUFFER_SIZE ) == topicLength; isMatch = isMatch && ( strncmp( expectedTopicBuffer, topic, topicLength ) == 0 ); } else { /* Empty MISRA body */ } return isMatch; } /** @endcond */ /** * See jobs.h for docs. * * @brief Output a topic value if a Jobs API topic string is present. */ JobsStatus_t Jobs_MatchTopic( char * topic, size_t length, const char * thingName, uint16_t thingNameLength, JobsTopic_t * outApi, char ** outJobId, uint16_t * outJobIdLength ) { JobsStatus_t ret = JobsBadParameter; JobsTopic_t api = JobsInvalidTopic; char * jobId = NULL; uint16_t jobIdLength = 0U; if( ( topic != NULL ) && ( outApi != NULL ) && checkThingParams() && ( length > 0U ) ) { ret = JobsNoMatch; if( ( length > JOBS_API_COMMON_LENGTH( thingNameLength ) ) && ( length < JOBS_API_MAX_LENGTH( thingNameLength ) ) ) { char * prefix = topic; char * name = &prefix[ JOBS_API_PREFIX_LENGTH ]; char * bridge = &name[ thingNameLength ]; /* check the shortest match first */ if( ( strnEquals( bridge, JOBS_API_BRIDGE, JOBS_API_BRIDGE_LENGTH ) == JobsSuccess ) && ( strnEquals( prefix, JOBS_API_PREFIX, JOBS_API_PREFIX_LENGTH ) == JobsSuccess ) && ( strnEquals( name, thingName, thingNameLength ) == JobsSuccess ) ) { char * tail = &bridge[ JOBS_API_BRIDGE_LENGTH ]; size_t tailLength = length - JOBS_API_COMMON_LENGTH( thingNameLength ); ret = matchApi( tail, tailLength, &api, &jobId, &jobIdLength ); } } } if( outApi != NULL ) { *outApi = api; } if( outJobId != NULL ) { *outJobId = jobId; } if( outJobIdLength != NULL ) { *outJobIdLength = jobIdLength; } return ret; } /** * See jobs.h for docs. * * @brief Populate a topic string for a GetPendingJobExecutions request. */ JobsStatus_t Jobs_GetPending( char * buffer, size_t length, const char * thingName, uint16_t thingNameLength, size_t * outLength ) { JobsStatus_t ret = JobsBadParameter; size_t start = 0U; if( checkCommonParams() ) { writePreamble( buffer, &start, length, thingName, thingNameLength ); ret = strnAppend( buffer, &start, length, JOBS_API_GETPENDING, JOBS_API_GETPENDING_LENGTH ); start = ( start >= length ) ? ( length - 1U ) : start; buffer[ start ] = '\0'; if( outLength != NULL ) { *outLength = start; } } return ret; } /** * See jobs.h for docs. * * @brief Populate a topic string for a StartNextPendingJobExecution request. */ JobsStatus_t Jobs_StartNext( char * buffer, size_t length, const char * thingName, uint16_t thingNameLength, size_t * outLength ) { JobsStatus_t ret = JobsBadParameter; size_t start = 0U; if( checkCommonParams() ) { writePreamble( buffer, &start, length, thingName, thingNameLength ); ret = strnAppend( buffer, &start, length, JOBS_API_STARTNEXT, JOBS_API_STARTNEXT_LENGTH ); start = ( start >= length ) ? ( length - 1U ) : start; buffer[ start ] = '\0'; if( outLength != NULL ) { *outLength = start; } } return ret; } size_t Jobs_StartNextMsg( const char * clientToken, size_t clientTokenLength, char * buffer, size_t bufferSize ) { size_t start = 0U; if( ( clientToken != NULL ) && ( clientTokenLength > 0U ) && ( bufferSize >= ( 18U + clientTokenLength ) ) ) { ( void ) strnAppend( buffer, &start, bufferSize, JOBS_API_CLIENTTOKEN, JOBS_API_CLIENTTOKEN_LENGTH ); ( void ) strnAppend( buffer, &start, bufferSize, clientToken, clientTokenLength ); ( void ) strnAppend( buffer, &start, bufferSize, "\"}", ( CONST_STRLEN( "\"}" ) ) ); } return start; } /** * See jobs.h for docs. * * @brief Populate a topic string for a DescribeJobExecution request. */ JobsStatus_t Jobs_Describe( char * buffer, size_t length, const char * thingName, uint16_t thingNameLength, const char * jobId, uint16_t jobIdLength, size_t * outLength ) { JobsStatus_t ret = JobsBadParameter; size_t start = 0U; if( checkCommonParams() && ( ( isNextJobId( jobId, jobIdLength ) == true ) || ( isValidJobId( jobId, jobIdLength ) == true ) ) ) { writePreamble( buffer, &start, length, thingName, thingNameLength ); ( void ) strnAppend( buffer, &start, length, jobId, jobIdLength ); ( void ) strnAppend( buffer, &start, length, "/", ( CONST_STRLEN( "/" ) ) ); ret = strnAppend( buffer, &start, length, JOBS_API_DESCRIBE, JOBS_API_DESCRIBE_LENGTH ); start = ( start >= length ) ? ( length - 1U ) : start; buffer[ start ] = '\0'; if( outLength != NULL ) { *outLength = start; } } return ret; } /** * See jobs.h for docs. * * @brief Populate a topic string for an UpdateJobExecution request. */ JobsStatus_t Jobs_Update( char * buffer, size_t length, const char * thingName, uint16_t thingNameLength, const char * jobId, uint16_t jobIdLength, size_t * outLength ) { JobsStatus_t ret = JobsBadParameter; size_t start = 0U; if( checkCommonParams() && ( isValidJobId( jobId, jobIdLength ) == true ) ) { writePreamble( buffer, &start, length, thingName, thingNameLength ); ( void ) strnAppend( buffer, &start, length, jobId, jobIdLength ); ( void ) strnAppend( buffer, &start, length, "/", ( CONST_STRLEN( "/" ) ) ); ret = strnAppend( buffer, &start, length, JOBS_API_UPDATE, JOBS_API_UPDATE_LENGTH ); start = ( start >= length ) ? ( length - 1U ) : start; buffer[ start ] = '\0'; if( outLength != NULL ) { *outLength = start; } } return ret; } /** * @brief Get the total length of optional fields provided for * the Jobs_UpdateMsg. These optional fields, if provided, require * additional buffer space. * * @param request A JobsUpdateRequest_t containing the optional fields. * @return size_t The buffer space required for the optional fields. */ static size_t getOptionalFieldsLength( JobsUpdateRequest_t request ) { size_t minimumOptionalFieldsBufferSize = 0U; if( ( request.expectedVersion != NULL ) && ( request.expectedVersionLength > 0U ) ) { minimumOptionalFieldsBufferSize += JOBS_API_EXPECTED_VERSION_LENGTH + request.expectedVersionLength; } if( ( request.statusDetails != NULL ) && ( request.statusDetailsLength > 0U ) ) { minimumOptionalFieldsBufferSize += JOBS_API_STATUS_DETAILS_LENGTH + request.statusDetailsLength; } return minimumOptionalFieldsBufferSize; } /** * @brief Get the total length of the required fields in the * Jobs_UpdateMsg request. * * @param request A JobsUpdateRequest_t containing the optional fields. * @return size_t The buffer space required for the optional fields. */ static size_t getRequiredFieldsLength( JobsUpdateRequest_t request ) { return JOBS_API_STATUS_LENGTH + strlen( jobStatusString[ request.status ] ) + CONST_STRLEN( "\"}" ); } /** * @brief Check non-null optional fields in the Jobs_UpdateMsg request * for validity. * * @param request A JobsUpdateRequest_t containing the optional fields. * @return true Optional fields appear valid. * @return false Optional fields are invalid. */ static bool areOptionalFieldsValid( JobsUpdateRequest_t request ) { bool optionalFieldsValid = true; if( ( request.statusDetails != NULL ) && ( request.statusDetailsLength > 0U ) ) { optionalFieldsValid = ( JSONSuccess == JSON_Validate( request.statusDetails, request.statusDetailsLength ) ); } return optionalFieldsValid; } size_t Jobs_UpdateMsg( JobsUpdateRequest_t request, char * buffer, size_t bufferSize ) { assert( ( ( size_t ) request.status ) < ARRAY_LENGTH( jobStatusString ) ); size_t start = 0U; size_t minimumBufferSize = getRequiredFieldsLength( request ) + getOptionalFieldsLength( request ); bool writeFailed = ( bufferSize < minimumBufferSize ) || !areOptionalFieldsValid( request ); if( !writeFailed ) { ( void ) strnAppend( buffer, &start, bufferSize, JOBS_API_STATUS, JOBS_API_STATUS_LENGTH ); ( void ) strnAppend( buffer, &start, bufferSize, jobStatusString[ request.status ], strlen( jobStatusString[ request.status ] ) ); /* This is an optional field so do not fail if expected version is missing.*/ if( ( request.expectedVersion != NULL ) && ( request.expectedVersionLength > 0U ) ) { ( void ) strnAppend( buffer, &start, bufferSize, JOBS_API_EXPECTED_VERSION, JOBS_API_EXPECTED_VERSION_LENGTH ); ( void ) strnAppend( buffer, &start, bufferSize, request.expectedVersion, request.expectedVersionLength ); } /* This is an optional field so do not fail if status details is missing.*/ if( ( request.statusDetails != NULL ) && ( request.statusDetailsLength > 0U ) ) { ( void ) strnAppend( buffer, &start, bufferSize, JOBS_API_STATUS_DETAILS, JOBS_API_STATUS_DETAILS_LENGTH ); ( void ) strnAppend( buffer, &start, bufferSize, request.statusDetails, request.statusDetailsLength ); ( void ) strnAppend( buffer, &start, bufferSize, "}", ( CONST_STRLEN( "}" ) ) ); } else { ( void ) strnAppend( buffer, &start, bufferSize, "\"}", ( CONST_STRLEN( "\"}" ) ) ); } } return start; } bool Jobs_IsStartNextAccepted( const char * topic, const size_t topicLength, const char * thingName, const size_t thingNameLength ) { return isThingnameTopicMatch( topic, topicLength, "start-next/accepted", strlen( "start-next/accepted" ), thingName, thingNameLength ); } bool Jobs_IsJobUpdateStatus( const char * topic, const size_t topicLength, const char * jobId, const size_t jobIdLength, const char * thingName, const size_t thingNameLength, JobUpdateStatus_t expectedStatus ) { static const char * const jobUpdateStatusString[] = { "accepted", "rejected" }; static const size_t jobUpdateStatusStringLengths[] = { CONST_STRLEN( "accepted" ), CONST_STRLEN( "rejected" ) }; assert( ( ( size_t ) expectedStatus ) < ARRAY_LENGTH( jobUpdateStatusString ) ); /* Max suffix size = max topic size - "$aws/<thingname>" prefix */ size_t suffixBufferLength = ( TOPIC_BUFFER_SIZE - CONST_STRLEN( "$aws/<thingname>" ) ); char suffixBuffer[ TOPIC_BUFFER_SIZE - CONST_STRLEN( "$aws/<thingname>" ) ] = { '\0' }; size_t start = 0U; ( void ) strnAppend( suffixBuffer, &start, suffixBufferLength, jobId, jobIdLength ); ( void ) strnAppend( suffixBuffer, &start, suffixBufferLength, "/update/", ( CONST_STRLEN( "/update/" ) ) ); ( void ) strnAppend( suffixBuffer, &start, suffixBufferLength, jobUpdateStatusString[ expectedStatus ], jobUpdateStatusStringLengths[ expectedStatus ] ); return isThingnameTopicMatch( topic, topicLength, suffixBuffer, strnlen( suffixBuffer, suffixBufferLength ), thingName, thingNameLength ); } size_t Jobs_GetJobId( const char * message, size_t messageLength, const char ** jobId ) { size_t jobIdLength = 0U; JSONStatus_t jsonResult = JSONNotFound; jsonResult = JSON_Validate( message, messageLength ); if( jsonResult == JSONSuccess ) { jsonResult = JSON_SearchConst( message, messageLength, "execution.jobId", CONST_STRLEN( "execution.jobId" ), jobId, &jobIdLength, NULL ); } return jobIdLength; } size_t Jobs_GetJobDocument( const char * message, size_t messageLength, const char ** jobDoc ) { size_t jobDocLength = 0U; JSONStatus_t jsonResult = JSONNotFound; jsonResult = JSON_Validate( message, messageLength ); if( jsonResult == JSONSuccess ) { jsonResult = JSON_SearchConst( message, messageLength, "execution.jobDocument", CONST_STRLEN( "execution.jobDocument" ), jobDoc, &jobDocLength, NULL ); } return jobDocLength; }