2 * AWS IoT Jobs V1.0.0
\r
3 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
\r
5 * Permission is hereby granted, free of charge, to any person obtaining a copy of
\r
6 * this software and associated documentation files (the "Software"), to deal in
\r
7 * the Software without restriction, including without limitation the rights to
\r
8 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
\r
9 * the Software, and to permit persons to whom the Software is furnished to do so,
\r
10 * subject to the following conditions:
\r
12 * The above copyright notice and this permission notice shall be included in all
\r
13 * copies or substantial portions of the Software.
\r
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
\r
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
\r
17 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
\r
18 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
\r
19 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
\r
20 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\r
24 * @file aws_iot_jobs_serialize.c
\r
25 * @brief Implements functions that generate and parse Jobs JSON documents.
\r
28 /* The config header is always included first. */
\r
29 #include "iot_config.h"
\r
31 /* Standard includes. */
\r
35 /* Jobs internal include. */
\r
36 #include "private/aws_iot_jobs_internal.h"
\r
38 /* Error handling include. */
\r
39 #include "iot_error.h"
\r
41 /* JSON utilities include. */
\r
42 #include "aws_iot_doc_parser.h"
\r
45 * @brief Minimum length of a Jobs request.
\r
47 * At the very least, the request will contain: {"clientToken":""}
\r
49 #define MINIMUM_REQUEST_LENGTH ( AWS_IOT_CLIENT_TOKEN_KEY_LENGTH + 7 )
\r
52 * @brief The length of client tokens generated by this library.
\r
54 #define CLIENT_TOKEN_AUTOGENERATE_LENGTH ( 8 )
\r
57 * @brief JSON key representing Jobs status.
\r
59 #define STATUS_KEY "status"
\r
62 * @brief Length of #STATUS_KEY.
\r
64 #define STATUS_KEY_LENGTH ( sizeof( STATUS_KEY ) - 1 )
\r
67 * @brief JSON key representing Jobs status details.
\r
69 #define STATUS_DETAILS_KEY "statusDetails"
\r
72 * @brief Length of #STATUS_DETAILS_KEY.
\r
74 #define STATUS_DETAILS_KEY_LENGTH ( sizeof( STATUS_DETAILS_KEY ) - 1 )
\r
77 * @brief JSON key representing Jobs expected version.
\r
79 #define EXPECTED_VERSION_KEY "expectedVersion"
\r
82 * @brief Length of #EXPECTED_VERSION_KEY.
\r
84 #define EXPECTED_VERSION_KEY_LENGTH ( sizeof( EXPECTED_VERSION_KEY ) - 1 )
\r
87 * @brief Maximum length of the expected version when represented as a string.
\r
89 * The expected version is a 32-bit unsigned integer. This can be represented in
\r
90 * 10 digits plus a NULL-terminator.
\r
92 #define EXPECTED_VERSION_STRING_LENGTH ( 11 )
\r
95 * @brief JSON key representing Jobs step timeout.
\r
97 #define STEP_TIMEOUT_KEY "stepTimeoutInMinutes"
\r
100 * @brief Length of #STEP_TIMEOUT_KEY.
\r
102 #define STEP_TIMEOUT_KEY_LENGTH ( sizeof( STEP_TIMEOUT_KEY ) - 1 )
\r
105 * @brief Maximum length of the step timeout when represented as a string.
\r
107 * The step timeout is in the range of [-1,10080]. This can be represented as
\r
108 * 5 digits plus a NULL-terminator.
\r
110 #define STEP_TIMEOUT_STRING_LENGTH ( 6 )
\r
113 * @brief JSON key representing the "include Job document" flag.
\r
115 #define INCLUDE_JOB_DOCUMENT_KEY "includeJobDocument"
\r
118 * @brief JSON key representing the "include Job Execution state" flag.
\r
120 #define INCLUDE_JOB_EXECUTION_STATE_KEY "includeJobExecutionState"
\r
123 * @brief Length of #INCLUDE_JOB_EXECUTION_STATE_KEY.
\r
125 #define INCLUDE_JOB_EXECUTION_STATE_KEY_LENGTH ( sizeof( INCLUDE_JOB_EXECUTION_STATE_KEY ) - 1 )
\r
128 * @brief Length of #INCLUDE_JOB_DOCUMENT_KEY.
\r
130 #define INCLUDE_JOB_DOCUMENT_KEY_LENGTH ( sizeof( INCLUDE_JOB_DOCUMENT_KEY ) - 1 )
\r
133 * @brief JSON key representing the Jobs execution number.
\r
135 #define EXECUTION_NUMBER_KEY "executionNumber"
\r
138 * @brief Length of #EXECUTION_NUMBER_KEY.
\r
140 #define EXECUTION_NUMBER_KEY_LENGTH ( sizeof( EXECUTION_NUMBER_KEY ) - 1 )
\r
143 * @brief Maximum length of the execution number when represented as a string.
\r
145 * The execution number is a 32-bit integer. This can be represented in 10 digits,
\r
146 * plus 1 for a possible negative sign, plus a NULL-terminator.
\r
148 #define EXECUTION_NUMBER_STRING_LENGTH ( 12 )
\r
151 * @brief JSON key representing Jobs error code in error responses.
\r
153 #define CODE_KEY "code"
\r
156 * @brief Length of #CODE_KEY.
\r
158 #define CODE_KEY_LENGTH ( sizeof( CODE_KEY ) - 1 )
\r
161 * @brief Append a string to a buffer.
\r
163 * Also updates `copyOffset` with `stringLength`.
\r
165 * @param[in] pBuffer Start of a buffer.
\r
166 * @param[in] copyOffset Offset in `pBuffer` where `pString` will be placed.
\r
167 * @param[in] pString The string to append.
\r
168 * @param[in] stringLength Length of `pString`.
\r
170 #define APPEND_STRING( pBuffer, copyOffset, pString, stringLength ) \
\r
171 ( void ) memcpy( pBuffer + copyOffset, pString, stringLength ); \
\r
172 copyOffset += ( size_t ) stringLength;
\r
174 /*-----------------------------------------------------------*/
\r
177 * @brief Place a JSON boolean flag in the given buffer.
\r
179 * @param[in] pBuffer The buffer where the flag is placed.
\r
180 * @param[in] copyOffset Offset in `pBuffer` where the flag is placed.
\r
181 * @param[in] pFlagName Either #INCLUDE_JOB_DOCUMENT_KEY or #INCLUDE_JOB_EXECUTION_STATE_KEY.
\r
182 * @param[in] flagNameLength Either #INCLUDE_JOB_EXECUTION_STATE_KEY_LENGTH or
\r
183 * #INCLUDE_JOB_EXECUTION_STATE_KEY_LENGTH
\r
184 * @param[in] value Either `true` or `false`.
\r
186 * @warning This function does not check the length of `pBuffer`! Any provided
\r
187 * buffer must be large enough to accommodate the flag and value.
\r
189 * @return A value of `copyOffset` after the flag.
\r
191 static size_t _appendFlag( char * pBuffer,
\r
193 const char * pFlagName,
\r
194 size_t flagNameLength,
\r
198 * @brief Place Job status details in the given buffer.
\r
200 * @param[in] pBuffer The buffer where the status details are placed.
\r
201 * @param[in] copyOffset Offset in `pBuffer` where the status details are placed.
\r
202 * @param[in] pStatusDetails The status details to place in the buffer.
\r
203 * @param[in] statusDetailsLength Length of `pStatusDetails`.
\r
205 * @warning This function does not check the length of `pBuffer`! Any provided
\r
206 * buffer must be large enough to accommodate the status details.
\r
208 * @return A value of `copyOffset` after the status details.
\r
210 static size_t _appendStatusDetails( char * pBuffer,
\r
212 const char * pStatusDetails,
\r
213 size_t statusDetailsLength );
\r
216 * @brief Place Job execution number in the given buffer.
\r
218 * @param[in] pBuffer The buffer where the execution number is placed.
\r
219 * @param[in] copyOffset Offset in `pBuffer` where the execution number is placed.
\r
220 * @param[in] pExecutionNumber The execution number to place in the buffer.
\r
221 * @param[in] executionNumberLength Length of `pExecutionNumber`.
\r
223 * @warning This function does not check the length of `pBuffer`! Any provided
\r
224 * buffer must be large enough to accommodate the execution number.
\r
226 * @return A value of `copyOffset` after the execution number.
\r
228 static size_t _appendExecutionNumber( char * pBuffer,
\r
230 const char * pExecutionNumber,
\r
231 size_t executionNumberLength );
\r
234 * @brief Place Job step timeout in the given buffer.
\r
236 * @param[in] pBuffer The buffer where the step timeout is placed.
\r
237 * @param[in] copyOffset Offset in `pBuffer` where the step timeout is placed.
\r
238 * @param[in] pStepTimeout The step timeout to place in the buffer.
\r
239 * @param[in] stepTimeoutLength Length of `pStepTimeout`.
\r
241 * @warning This function does not check the length of `pBuffer`! Any provided
\r
242 * buffer must be large enough to accommodate the step timeout.
\r
244 * @return A value of `copyOffset` after the step timeout.
\r
246 static size_t _appendStepTimeout( char * pBuffer,
\r
248 const char * pStepTimeout,
\r
249 size_t stepTimeoutLength );
\r
252 * @brief Place a client token in the given buffer.
\r
254 * @param[in] pBuffer The buffer where the client token is placed.
\r
255 * @param[in] copyOffset Offset in `pBuffer` where client token is placed.
\r
256 * @param[in] pRequestInfo Contains information on a client token to place.
\r
257 * @param[out] pOperation Location and length of client token are written here.
\r
259 * @warning This function does not check the length of `pBuffer`! Any provided
\r
260 * buffer must be large enough to accommodate #CLIENT_TOKEN_AUTOGENERATE_LENGTH
\r
263 * @return A value of `copyOffset` after the client token.
\r
265 static size_t _appendClientToken( char * pBuffer,
\r
267 const AwsIotJobsRequestInfo_t * pRequestInfo,
\r
268 _jobsOperation_t * pOperation );
\r
271 * @brief Generates a request JSON for a GET PENDING operation.
\r
273 * @param[in] pRequestInfo Common Jobs request parameters.
\r
274 * @param[in] pOperation Operation associated with the Jobs request.
\r
276 * @return #AWS_IOT_JOBS_SUCCESS or #AWS_IOT_JOBS_NO_MEMORY
\r
278 static AwsIotJobsError_t _generateGetPendingRequest( const AwsIotJobsRequestInfo_t * pRequestInfo,
\r
279 _jobsOperation_t * pOperation );
\r
282 * @brief Generates a request JSON for a START NEXT operation.
\r
284 * @param[in] pRequestInfo Common Jobs request parameters.
\r
285 * @param[in] pUpdateInfo Jobs update parameters.
\r
286 * @param[in] pOperation Operation associated with the Jobs request.
\r
288 * @return #AWS_IOT_JOBS_SUCCESS or #AWS_IOT_JOBS_NO_MEMORY
\r
290 static AwsIotJobsError_t _generateStartNextRequest( const AwsIotJobsRequestInfo_t * pRequestInfo,
\r
291 const AwsIotJobsUpdateInfo_t * pUpdateInfo,
\r
292 _jobsOperation_t * pOperation );
\r
295 * @brief Generates a request JSON for a DESCRIBE operation.
\r
297 * @param[in] pRequestInfo Common jobs request parameters.
\r
298 * @param[in] executionNumber Job execution number to include in request.
\r
299 * @param[in] includeJobDocument Whether the response should include the Job document.
\r
300 * @param[in] pOperation Operation associated with the Jobs request.
\r
302 * @return #AWS_IOT_JOBS_SUCCESS or #AWS_IOT_JOBS_NO_MEMORY.
\r
304 static AwsIotJobsError_t _generateDescribeRequest( const AwsIotJobsRequestInfo_t * pRequestInfo,
\r
305 int32_t executionNumber,
\r
306 bool includeJobDocument,
\r
307 _jobsOperation_t * pOperation );
\r
310 * @brief Generates a request JSON for an UPDATE operation.
\r
312 * @param[in] pRequestInfo Common Jobs request parameters.
\r
313 * @param[in] pUpdateInfo Jobs update parameters.
\r
314 * @param[in] pOperation Operation associated with the Jobs request.
\r
316 static AwsIotJobsError_t _generateUpdateRequest( const AwsIotJobsRequestInfo_t * pRequestInfo,
\r
317 const AwsIotJobsUpdateInfo_t * pUpdateInfo,
\r
318 _jobsOperation_t * pOperation );
\r
321 * @brief Parse an error from a Jobs error document.
\r
323 * @param[in] pErrorDocument Jobs error document.
\r
324 * @param[in] errorDocumentLength Length of `pErrorDocument`.
\r
326 * @return A Jobs error code between #AWS_IOT_JOBS_INVALID_TOPIC and
\r
327 * #AWS_IOT_JOBS_TERMINAL_STATE.
\r
329 static AwsIotJobsError_t _parseErrorDocument( const char * pErrorDocument,
\r
330 size_t errorDocumentLength );
\r
332 /*-----------------------------------------------------------*/
\r
334 static size_t _appendFlag( char * pBuffer,
\r
336 const char * pFlagName,
\r
337 size_t flagNameLength,
\r
340 if( value == true )
\r
342 APPEND_STRING( pBuffer,
\r
346 APPEND_STRING( pBuffer, copyOffset, "\":true,\"", 8 );
\r
350 APPEND_STRING( pBuffer,
\r
354 APPEND_STRING( pBuffer, copyOffset, "\":false,\"", 9 );
\r
360 /*-----------------------------------------------------------*/
\r
362 static size_t _appendStatusDetails( char * pBuffer,
\r
364 const char * pStatusDetails,
\r
365 size_t statusDetailsLength )
\r
367 APPEND_STRING( pBuffer, copyOffset, STATUS_DETAILS_KEY, STATUS_DETAILS_KEY_LENGTH );
\r
368 APPEND_STRING( pBuffer, copyOffset, "\":", 2 );
\r
369 APPEND_STRING( pBuffer,
\r
372 statusDetailsLength );
\r
373 APPEND_STRING( pBuffer, copyOffset, ",\"", 2 );
\r
378 /*-----------------------------------------------------------*/
\r
380 static size_t _appendExecutionNumber( char * pBuffer,
\r
382 const char * pExecutionNumber,
\r
383 size_t executionNumberLength )
\r
385 APPEND_STRING( pBuffer,
\r
387 EXECUTION_NUMBER_KEY,
\r
388 EXECUTION_NUMBER_KEY_LENGTH );
\r
389 APPEND_STRING( pBuffer,
\r
393 APPEND_STRING( pBuffer,
\r
396 executionNumberLength );
\r
397 APPEND_STRING( pBuffer, copyOffset, ",\"", 2 );
\r
402 /*-----------------------------------------------------------*/
\r
404 static size_t _appendStepTimeout( char * pBuffer,
\r
406 const char * pStepTimeout,
\r
407 size_t stepTimeoutLength )
\r
409 APPEND_STRING( pBuffer,
\r
412 STEP_TIMEOUT_KEY_LENGTH );
\r
413 APPEND_STRING( pBuffer, copyOffset, "\":", 2 );
\r
414 APPEND_STRING( pBuffer, copyOffset, pStepTimeout, stepTimeoutLength );
\r
415 APPEND_STRING( pBuffer, copyOffset, ",\"", 2 );
\r
420 /*-----------------------------------------------------------*/
\r
422 static size_t _appendClientToken( char * pBuffer,
\r
424 const AwsIotJobsRequestInfo_t * pRequestInfo,
\r
425 _jobsOperation_t * pOperation )
\r
427 int clientTokenLength = 0;
\r
428 uint32_t clientToken = 0;
\r
430 /* Place the client token key in the buffer. */
\r
431 APPEND_STRING( pBuffer,
\r
433 AWS_IOT_CLIENT_TOKEN_KEY,
\r
434 AWS_IOT_CLIENT_TOKEN_KEY_LENGTH );
\r
435 APPEND_STRING( pBuffer, copyOffset, "\":\"", 3 );
\r
437 /* Set the pointer to the client token. */
\r
438 pOperation->pClientToken = pBuffer + copyOffset - 1;
\r
440 if( pRequestInfo->pClientToken == AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE )
\r
442 /* Take the address of the given buffer, truncated to 8 characters. This
\r
443 * provides a client token that is very likely to be unique while in use. */
\r
444 clientToken = ( uint32_t ) ( ( uint64_t ) pBuffer % 100000000ULL );
\r
446 clientTokenLength = snprintf( pBuffer + copyOffset,
\r
447 CLIENT_TOKEN_AUTOGENERATE_LENGTH + 1,
\r
448 "%08u", clientToken );
\r
449 AwsIotJobs_Assert( clientTokenLength == CLIENT_TOKEN_AUTOGENERATE_LENGTH );
\r
451 copyOffset += ( size_t ) clientTokenLength;
\r
452 pOperation->clientTokenLength = CLIENT_TOKEN_AUTOGENERATE_LENGTH + 2;
\r
456 APPEND_STRING( pBuffer,
\r
458 pRequestInfo->pClientToken,
\r
459 pRequestInfo->clientTokenLength );
\r
461 pOperation->clientTokenLength = pRequestInfo->clientTokenLength + 2;
\r
467 /*-----------------------------------------------------------*/
\r
469 static AwsIotJobsError_t _generateGetPendingRequest( const AwsIotJobsRequestInfo_t * pRequestInfo,
\r
470 _jobsOperation_t * pOperation )
\r
472 IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_SUCCESS );
\r
473 char * pJobsRequest = NULL;
\r
474 size_t copyOffset = 0;
\r
475 size_t requestLength = MINIMUM_REQUEST_LENGTH;
\r
477 /* Add the length of the client token. */
\r
478 if( pRequestInfo->pClientToken != AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE )
\r
480 AwsIotJobs_Assert( pRequestInfo->clientTokenLength > 0 );
\r
482 requestLength += pRequestInfo->clientTokenLength;
\r
486 requestLength += CLIENT_TOKEN_AUTOGENERATE_LENGTH;
\r
489 /* Allocate memory for the request JSON. */
\r
490 pJobsRequest = AwsIotJobs_MallocString( requestLength );
\r
492 if( pJobsRequest == NULL )
\r
494 IotLogError( "No memory for Jobs GET PENDING request." );
\r
496 IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NO_MEMORY );
\r
499 /* Clear the request JSON. */
\r
500 ( void ) memset( pJobsRequest, 0x00, requestLength );
\r
502 /* Construct the request JSON, which consists of just a clientToken key. */
\r
503 APPEND_STRING( pJobsRequest, copyOffset, "{\"", 2 );
\r
504 copyOffset = _appendClientToken( pJobsRequest, copyOffset, pRequestInfo, pOperation );
\r
505 APPEND_STRING( pJobsRequest, copyOffset, "\"}", 2 );
\r
507 /* Set the output parameters. */
\r
508 pOperation->pJobsRequest = pJobsRequest;
\r
509 pOperation->jobsRequestLength = requestLength;
\r
511 /* Ensure offsets are valid. */
\r
512 AwsIotJobs_Assert( copyOffset == requestLength );
\r
513 AwsIotJobs_Assert( pOperation->pClientToken > pOperation->pJobsRequest );
\r
514 AwsIotJobs_Assert( pOperation->pClientToken <
\r
515 pOperation->pJobsRequest + pOperation->jobsRequestLength );
\r
517 IotLogDebug( "Jobs GET PENDING request: %.*s",
\r
518 pOperation->jobsRequestLength,
\r
519 pOperation->pJobsRequest );
\r
521 IOT_FUNCTION_EXIT_NO_CLEANUP();
\r
524 /*-----------------------------------------------------------*/
\r
526 static AwsIotJobsError_t _generateStartNextRequest( const AwsIotJobsRequestInfo_t * pRequestInfo,
\r
527 const AwsIotJobsUpdateInfo_t * pUpdateInfo,
\r
528 _jobsOperation_t * pOperation )
\r
530 IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_SUCCESS );
\r
531 char * pJobsRequest = NULL;
\r
532 size_t copyOffset = 0;
\r
533 size_t requestLength = MINIMUM_REQUEST_LENGTH;
\r
534 char pStepTimeout[ STEP_TIMEOUT_STRING_LENGTH ] = { 0 };
\r
535 int stepTimeoutLength = 0;
\r
537 /* Add the length of status details if provided. */
\r
538 if( pUpdateInfo->pStatusDetails != AWS_IOT_JOBS_NO_STATUS_DETAILS )
\r
540 /* Add 4 for the 2 quotes, colon, and comma. */
\r
541 requestLength += STATUS_DETAILS_KEY_LENGTH + 4;
\r
542 requestLength += pUpdateInfo->statusDetailsLength;
\r
545 if( pUpdateInfo->stepTimeoutInMinutes != AWS_IOT_JOBS_NO_TIMEOUT )
\r
547 /* Calculate the length of the step timeout. Add 4 for the 2 quotes, colon, and comma. */
\r
548 requestLength += STEP_TIMEOUT_KEY_LENGTH + 4;
\r
550 if( pUpdateInfo->stepTimeoutInMinutes == AWS_IOT_JOBS_CANCEL_TIMEOUT )
\r
552 /* Step timeout will be set to -1. */
\r
553 pStepTimeout[ 0 ] = '-';
\r
554 pStepTimeout[ 1 ] = '1';
\r
555 stepTimeoutLength = 2;
\r
559 /* Convert the step timeout to a string. */
\r
560 stepTimeoutLength = snprintf( pStepTimeout,
\r
561 STEP_TIMEOUT_STRING_LENGTH,
\r
563 pUpdateInfo->stepTimeoutInMinutes );
\r
564 AwsIotJobs_Assert( stepTimeoutLength > 0 );
\r
565 AwsIotJobs_Assert( stepTimeoutLength < STEP_TIMEOUT_STRING_LENGTH );
\r
568 requestLength += ( size_t ) stepTimeoutLength;
\r
571 /* Add the length of the client token. */
\r
572 if( pRequestInfo->pClientToken != AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE )
\r
574 AwsIotJobs_Assert( pRequestInfo->clientTokenLength > 0 );
\r
576 requestLength += pRequestInfo->clientTokenLength;
\r
580 requestLength += CLIENT_TOKEN_AUTOGENERATE_LENGTH;
\r
583 /* Allocate memory for the request JSON. */
\r
584 pJobsRequest = AwsIotJobs_MallocString( requestLength );
\r
586 if( pJobsRequest == NULL )
\r
588 IotLogError( "No memory for Jobs START NEXT request." );
\r
590 IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NO_MEMORY );
\r
593 /* Clear the request JSON. */
\r
594 ( void ) memset( pJobsRequest, 0x00, requestLength );
\r
596 /* Construct the request JSON. */
\r
597 APPEND_STRING( pJobsRequest, copyOffset, "{\"", 2 );
\r
599 /* Add status details if present. */
\r
600 if( pUpdateInfo->pStatusDetails != AWS_IOT_JOBS_NO_STATUS_DETAILS )
\r
602 copyOffset = _appendStatusDetails( pJobsRequest,
\r
604 pUpdateInfo->pStatusDetails,
\r
605 pUpdateInfo->statusDetailsLength );
\r
608 /* Add step timeout if present. */
\r
609 if( pUpdateInfo->stepTimeoutInMinutes != AWS_IOT_JOBS_NO_TIMEOUT )
\r
611 copyOffset = _appendStepTimeout( pJobsRequest,
\r
614 stepTimeoutLength );
\r
617 /* Add client token. */
\r
618 copyOffset = _appendClientToken( pJobsRequest, copyOffset, pRequestInfo, pOperation );
\r
620 APPEND_STRING( pJobsRequest, copyOffset, "\"}", 2 );
\r
622 /* Set the output parameters. */
\r
623 pOperation->pJobsRequest = pJobsRequest;
\r
624 pOperation->jobsRequestLength = requestLength;
\r
626 /* Ensure offsets are valid. */
\r
627 AwsIotJobs_Assert( copyOffset == requestLength );
\r
628 AwsIotJobs_Assert( pOperation->pClientToken > pOperation->pJobsRequest );
\r
629 AwsIotJobs_Assert( pOperation->pClientToken <
\r
630 pOperation->pJobsRequest + pOperation->jobsRequestLength );
\r
632 IotLogDebug( "Jobs START NEXT request: %.*s",
\r
633 pOperation->jobsRequestLength,
\r
634 pOperation->pJobsRequest );
\r
636 IOT_FUNCTION_EXIT_NO_CLEANUP();
\r
639 /*-----------------------------------------------------------*/
\r
641 static AwsIotJobsError_t _generateDescribeRequest( const AwsIotJobsRequestInfo_t * pRequestInfo,
\r
642 int32_t executionNumber,
\r
643 bool includeJobDocument,
\r
644 _jobsOperation_t * pOperation )
\r
646 IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_SUCCESS );
\r
647 char * pJobsRequest = NULL;
\r
648 size_t copyOffset = 0;
\r
649 size_t requestLength = MINIMUM_REQUEST_LENGTH;
\r
650 char pExecutionNumber[ EXECUTION_NUMBER_STRING_LENGTH ] = { 0 };
\r
651 int executionNumberLength = 0;
\r
653 /* Add the "include job document" flag if false. The default value is true,
\r
654 * so the flag is not needed if true. */
\r
655 if( includeJobDocument == false )
\r
657 /* Add the length of "includeJobDocument" plus 4 for 2 quotes, a colon,
\r
659 requestLength += INCLUDE_JOB_DOCUMENT_KEY_LENGTH + 4;
\r
661 /* Add the length of "false". */
\r
662 requestLength += 5;
\r
665 /* Add the length of the execution number if present. */
\r
666 if( executionNumber != AWS_IOT_JOBS_NO_EXECUTION_NUMBER )
\r
668 /* Convert the execution number to a string. */
\r
669 executionNumberLength = snprintf( pExecutionNumber,
\r
670 EXECUTION_NUMBER_STRING_LENGTH,
\r
673 AwsIotJobs_Assert( executionNumberLength > 0 );
\r
674 AwsIotJobs_Assert( executionNumberLength < EXECUTION_NUMBER_STRING_LENGTH );
\r
676 requestLength += EXECUTION_NUMBER_KEY_LENGTH + 4;
\r
677 requestLength += ( size_t ) executionNumberLength;
\r
680 /* Add the length of the client token. */
\r
681 if( pRequestInfo->pClientToken != AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE )
\r
683 AwsIotJobs_Assert( pRequestInfo->clientTokenLength > 0 );
\r
685 requestLength += pRequestInfo->clientTokenLength;
\r
689 requestLength += CLIENT_TOKEN_AUTOGENERATE_LENGTH;
\r
692 /* Allocate memory for the request JSON. */
\r
693 pJobsRequest = AwsIotJobs_MallocString( requestLength );
\r
695 if( pJobsRequest == NULL )
\r
697 IotLogError( "No memory for Jobs DESCRIBE request." );
\r
699 IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NO_MEMORY );
\r
702 /* Clear the request JSON. */
\r
703 ( void ) memset( pJobsRequest, 0x00, requestLength );
\r
705 /* Construct the request JSON. */
\r
706 APPEND_STRING( pJobsRequest, copyOffset, "{\"", 2 );
\r
708 /* Add the "include job document" flag if false. */
\r
709 if( includeJobDocument == false )
\r
711 copyOffset = _appendFlag( pJobsRequest,
\r
713 INCLUDE_JOB_DOCUMENT_KEY,
\r
714 INCLUDE_JOB_DOCUMENT_KEY_LENGTH,
\r
718 /* Add the length of the execution number if present. */
\r
719 if( executionNumber != AWS_IOT_JOBS_NO_EXECUTION_NUMBER )
\r
721 copyOffset = _appendExecutionNumber( pJobsRequest,
\r
724 ( size_t ) executionNumberLength );
\r
727 /* Add client token. */
\r
728 copyOffset = _appendClientToken( pJobsRequest, copyOffset, pRequestInfo, pOperation );
\r
730 APPEND_STRING( pJobsRequest, copyOffset, "\"}", 2 );
\r
732 /* Set the output parameters. */
\r
733 pOperation->pJobsRequest = pJobsRequest;
\r
734 pOperation->jobsRequestLength = requestLength;
\r
736 /* Ensure offsets are valid. */
\r
737 AwsIotJobs_Assert( copyOffset == requestLength );
\r
738 AwsIotJobs_Assert( pOperation->pClientToken > pOperation->pJobsRequest );
\r
739 AwsIotJobs_Assert( pOperation->pClientToken <
\r
740 pOperation->pJobsRequest + pOperation->jobsRequestLength );
\r
742 IotLogDebug( "Jobs DESCRIBE request: %.*s",
\r
743 pOperation->jobsRequestLength,
\r
744 pOperation->pJobsRequest );
\r
746 IOT_FUNCTION_EXIT_NO_CLEANUP();
\r
749 /*-----------------------------------------------------------*/
\r
751 static AwsIotJobsError_t _generateUpdateRequest( const AwsIotJobsRequestInfo_t * pRequestInfo,
\r
752 const AwsIotJobsUpdateInfo_t * pUpdateInfo,
\r
753 _jobsOperation_t * pOperation )
\r
755 IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_SUCCESS );
\r
756 char * pJobsRequest = NULL;
\r
757 size_t copyOffset = 0;
\r
758 size_t requestLength = MINIMUM_REQUEST_LENGTH;
\r
759 const char * pStatus = NULL;
\r
760 size_t statusLength = 0;
\r
761 char pExpectedVersion[ EXPECTED_VERSION_STRING_LENGTH ] = { 0 };
\r
762 char pExecutionNumber[ EXECUTION_NUMBER_STRING_LENGTH ] = { 0 };
\r
763 char pStepTimeout[ STEP_TIMEOUT_STRING_LENGTH ] = { 0 };
\r
764 int expectedVersionLength = 0, executionNumberLength = 0, stepTimeoutLength = 0;
\r
766 /* Determine the status string and length to report to the Jobs service.
\r
767 * Add 6 for the 4 quotes, colon, and comma. */
\r
768 requestLength += STATUS_KEY_LENGTH + 6;
\r
770 switch( pUpdateInfo->newStatus )
\r
772 case AWS_IOT_JOB_STATE_IN_PROGRESS:
\r
773 pStatus = "IN_PROGRESS";
\r
776 case AWS_IOT_JOB_STATE_FAILED:
\r
777 pStatus = "FAILED";
\r
780 case AWS_IOT_JOB_STATE_SUCCEEDED:
\r
781 pStatus = "SUCCEEDED";
\r
785 /* The only remaining valid state is REJECTED. */
\r
786 AwsIotJobs_Assert( pUpdateInfo->newStatus == AWS_IOT_JOB_STATE_REJECTED );
\r
787 pStatus = "REJECTED";
\r
791 statusLength = strlen( pStatus );
\r
792 requestLength += statusLength;
\r
794 /* Add the length of status details if provided. */
\r
795 if( pUpdateInfo->pStatusDetails != AWS_IOT_JOBS_NO_STATUS_DETAILS )
\r
797 /* Add 4 for the 2 quotes, colon, and comma. */
\r
798 requestLength += STATUS_DETAILS_KEY_LENGTH + 4;
\r
799 requestLength += pUpdateInfo->statusDetailsLength;
\r
802 /* Add the expected version if provided. */
\r
803 if( pUpdateInfo->expectedVersion != AWS_IOT_JOBS_NO_VERSION )
\r
805 /* Convert the expected version to a string. */
\r
806 expectedVersionLength = snprintf( pExpectedVersion,
\r
807 EXPECTED_VERSION_STRING_LENGTH,
\r
809 pUpdateInfo->expectedVersion );
\r
810 AwsIotJobs_Assert( expectedVersionLength > 0 );
\r
811 AwsIotJobs_Assert( expectedVersionLength < EXPECTED_VERSION_STRING_LENGTH );
\r
813 /* Add 6 for the 4 quotes, colon, and comma. */
\r
814 requestLength += EXPECTED_VERSION_KEY_LENGTH + 6;
\r
815 requestLength += ( size_t ) expectedVersionLength;
\r
818 /* Add the length of the execution number if present. */
\r
819 if( pUpdateInfo->executionNumber != AWS_IOT_JOBS_NO_EXECUTION_NUMBER )
\r
821 /* Convert the execution number to a string. */
\r
822 executionNumberLength = snprintf( pExecutionNumber,
\r
823 EXECUTION_NUMBER_STRING_LENGTH,
\r
825 pUpdateInfo->executionNumber );
\r
826 AwsIotJobs_Assert( executionNumberLength > 0 );
\r
827 AwsIotJobs_Assert( executionNumberLength < EXECUTION_NUMBER_STRING_LENGTH );
\r
829 requestLength += EXECUTION_NUMBER_KEY_LENGTH + 4;
\r
830 requestLength += ( size_t ) executionNumberLength;
\r
833 /* Add the flags if true. The default values are false, so the flags are not
\r
834 * needed if false. */
\r
835 if( pUpdateInfo->includeJobExecutionState == true )
\r
837 /* Add the length of "includeJobExecutionState" plus 4 for 2 quotes, a colon,
\r
839 requestLength += INCLUDE_JOB_EXECUTION_STATE_KEY_LENGTH + 4;
\r
841 /* Add the length of "true". */
\r
842 requestLength += 4;
\r
845 if( pUpdateInfo->includeJobDocument == true )
\r
847 /* Add the length of "includeJobDocument" plus 4 for 2 quotes, a colon,
\r
849 requestLength += INCLUDE_JOB_DOCUMENT_KEY_LENGTH + 4;
\r
851 /* Add the length of "true". */
\r
852 requestLength += 4;
\r
855 /* Add the step timeout if provided. */
\r
856 if( pUpdateInfo->stepTimeoutInMinutes != AWS_IOT_JOBS_NO_TIMEOUT )
\r
858 /* Calculate the length of the step timeout. Add 4 for the 2 quotes, colon, and comma. */
\r
859 requestLength += STEP_TIMEOUT_KEY_LENGTH + 4;
\r
861 if( pUpdateInfo->stepTimeoutInMinutes == AWS_IOT_JOBS_CANCEL_TIMEOUT )
\r
863 /* Step timeout will be set to -1. */
\r
864 pStepTimeout[ 0 ] = '-';
\r
865 pStepTimeout[ 1 ] = '1';
\r
866 stepTimeoutLength = 2;
\r
870 /* Convert the step timeout to a string. */
\r
871 stepTimeoutLength = snprintf( pStepTimeout,
\r
872 STEP_TIMEOUT_STRING_LENGTH,
\r
874 pUpdateInfo->stepTimeoutInMinutes );
\r
875 AwsIotJobs_Assert( stepTimeoutLength > 0 );
\r
876 AwsIotJobs_Assert( stepTimeoutLength < STEP_TIMEOUT_STRING_LENGTH );
\r
879 requestLength += ( size_t ) stepTimeoutLength;
\r
882 /* Add the length of the client token. */
\r
883 if( pRequestInfo->pClientToken != AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE )
\r
885 AwsIotJobs_Assert( pRequestInfo->clientTokenLength > 0 );
\r
887 requestLength += pRequestInfo->clientTokenLength;
\r
891 requestLength += CLIENT_TOKEN_AUTOGENERATE_LENGTH;
\r
894 /* Allocate memory for the request JSON. */
\r
895 pJobsRequest = AwsIotJobs_MallocString( requestLength );
\r
897 if( pJobsRequest == NULL )
\r
899 IotLogError( "No memory for Jobs UPDATE request." );
\r
901 IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NO_MEMORY );
\r
904 /* Clear the request JSON. */
\r
905 ( void ) memset( pJobsRequest, 0x00, requestLength );
\r
907 /* Construct the request JSON. */
\r
908 APPEND_STRING( pJobsRequest, copyOffset, "{\"", 2 );
\r
910 /* Add the status. */
\r
911 APPEND_STRING( pJobsRequest, copyOffset, STATUS_KEY, STATUS_KEY_LENGTH );
\r
912 APPEND_STRING( pJobsRequest, copyOffset, "\":\"", 3 );
\r
913 APPEND_STRING( pJobsRequest, copyOffset, pStatus, statusLength );
\r
914 APPEND_STRING( pJobsRequest, copyOffset, "\",\"", 3 );
\r
916 /* Add status details if present. */
\r
917 if( pUpdateInfo->pStatusDetails != AWS_IOT_JOBS_NO_STATUS_DETAILS )
\r
919 copyOffset = _appendStatusDetails( pJobsRequest,
\r
921 pUpdateInfo->pStatusDetails,
\r
922 pUpdateInfo->statusDetailsLength );
\r
925 /* Add expected version. */
\r
926 if( pUpdateInfo->expectedVersion != AWS_IOT_JOBS_NO_VERSION )
\r
928 APPEND_STRING( pJobsRequest,
\r
930 EXPECTED_VERSION_KEY,
\r
931 EXPECTED_VERSION_KEY_LENGTH );
\r
932 APPEND_STRING( pJobsRequest, copyOffset, "\":\"", 3 );
\r
933 APPEND_STRING( pJobsRequest, copyOffset, pExpectedVersion, expectedVersionLength );
\r
934 APPEND_STRING( pJobsRequest, copyOffset, "\",\"", 3 );
\r
937 /* Add execution number. */
\r
938 if( pUpdateInfo->executionNumber != AWS_IOT_JOBS_NO_EXECUTION_NUMBER )
\r
940 copyOffset = _appendExecutionNumber( pJobsRequest,
\r
943 executionNumberLength );
\r
946 /* Add flags if not default values. */
\r
947 if( pUpdateInfo->includeJobExecutionState == true )
\r
949 copyOffset = _appendFlag( pJobsRequest,
\r
951 INCLUDE_JOB_EXECUTION_STATE_KEY,
\r
952 INCLUDE_JOB_EXECUTION_STATE_KEY_LENGTH,
\r
956 if( pUpdateInfo->includeJobDocument == true )
\r
958 copyOffset = _appendFlag( pJobsRequest,
\r
960 INCLUDE_JOB_DOCUMENT_KEY,
\r
961 INCLUDE_JOB_DOCUMENT_KEY_LENGTH,
\r
965 /* Add step timeout if provided. */
\r
966 if( pUpdateInfo->stepTimeoutInMinutes != AWS_IOT_JOBS_NO_TIMEOUT )
\r
968 copyOffset = _appendStepTimeout( pJobsRequest,
\r
971 stepTimeoutLength );
\r
974 /* Add the client token. */
\r
975 copyOffset = _appendClientToken( pJobsRequest, copyOffset, pRequestInfo, pOperation );
\r
977 APPEND_STRING( pJobsRequest, copyOffset, "\"}", 2 );
\r
979 /* Set the output parameters. */
\r
980 pOperation->pJobsRequest = pJobsRequest;
\r
981 pOperation->jobsRequestLength = requestLength;
\r
983 /* Ensure offsets are valid. */
\r
984 AwsIotJobs_Assert( copyOffset == requestLength );
\r
985 AwsIotJobs_Assert( pOperation->pClientToken > pOperation->pJobsRequest );
\r
986 AwsIotJobs_Assert( pOperation->pClientToken <
\r
987 pOperation->pJobsRequest + pOperation->jobsRequestLength );
\r
989 IotLogDebug( "Jobs UPDATE request: %.*s",
\r
990 pOperation->jobsRequestLength,
\r
991 pOperation->pJobsRequest );
\r
993 IOT_FUNCTION_EXIT_NO_CLEANUP();
\r
996 /*-----------------------------------------------------------*/
\r
998 static AwsIotJobsError_t _parseErrorDocument( const char * pErrorDocument,
\r
999 size_t errorDocumentLength )
\r
1001 IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_STATUS_PENDING );
\r
1002 const char * pCode = NULL;
\r
1003 size_t codeLength = 0;
\r
1005 /* Find the error code. */
\r
1006 if( AwsIotDocParser_FindValue( pErrorDocument,
\r
1007 errorDocumentLength,
\r
1011 &codeLength ) == false )
\r
1013 IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_RESPONSE );
\r
1016 /* Match the JSON error code to a Jobs return value. Assume invalid status
\r
1017 * unless matched.*/
\r
1018 status = AWS_IOT_JOBS_BAD_RESPONSE;
\r
1020 switch( codeLength )
\r
1025 if( strncmp( "\"InvalidJson\"", pCode, codeLength ) == 0 )
\r
1027 status = AWS_IOT_JOBS_INVALID_JSON;
\r
1032 /* InvalidTopic */
\r
1035 if( strncmp( "\"InvalidTopic\"", pCode, codeLength ) == 0 )
\r
1037 status = AWS_IOT_JOBS_INVALID_TOPIC;
\r
1042 /* InternalError */
\r
1045 if( strncmp( "\"InternalError\"", pCode, codeLength ) == 0 )
\r
1047 status = AWS_IOT_JOBS_INTERNAL_ERROR;
\r
1052 /* InvalidRequest */
\r
1055 if( strncmp( "\"InvalidRequest\"", pCode, codeLength ) == 0 )
\r
1057 status = AWS_IOT_JOBS_INVALID_REQUEST;
\r
1062 /* VersionMismatch */
\r
1065 if( strncmp( "\"VersionMismatch\"", pCode, codeLength ) == 0 )
\r
1067 status = AWS_IOT_JOBS_VERSION_MISMATCH;
\r
1072 /* ResourceNotFound, RequestThrottled */
\r
1075 if( strncmp( "\"ResourceNotFound\"", pCode, codeLength ) == 0 )
\r
1077 status = AWS_IOT_JOBS_NOT_FOUND;
\r
1079 else if( strncmp( "\"RequestThrottled\"", pCode, codeLength ) == 0 )
\r
1081 status = AWS_IOT_JOBS_THROTTLED;
\r
1086 /* TerminalStateReached */
\r
1089 if( strncmp( "\"TerminalStateReached\"", pCode, codeLength ) == 0 )
\r
1091 status = AWS_IOT_JOBS_TERMINAL_STATE;
\r
1096 /* InvalidStateTransition */
\r
1099 if( strncmp( "\"InvalidStateTransition\"", pCode, codeLength ) == 0 )
\r
1101 status = AWS_IOT_JOBS_INVALID_STATE;
\r
1110 IOT_FUNCTION_EXIT_NO_CLEANUP();
\r
1113 /*-----------------------------------------------------------*/
\r
1115 AwsIotJobsError_t _AwsIotJobs_GenerateJsonRequest( _jobsOperationType_t type,
\r
1116 const AwsIotJobsRequestInfo_t * pRequestInfo,
\r
1117 const _jsonRequestContents_t * pRequestContents,
\r
1118 _jobsOperation_t * pOperation )
\r
1120 AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING;
\r
1122 /* Generate request based on the Job operation type. */
\r
1125 case JOBS_GET_PENDING:
\r
1126 status = _generateGetPendingRequest( pRequestInfo, pOperation );
\r
1129 case JOBS_START_NEXT:
\r
1130 status = _generateStartNextRequest( pRequestInfo,
\r
1131 pRequestContents->pUpdateInfo,
\r
1135 case JOBS_DESCRIBE:
\r
1136 status = _generateDescribeRequest( pRequestInfo,
\r
1137 pRequestContents->describe.executionNumber,
\r
1138 pRequestContents->describe.includeJobDocument,
\r
1143 /* The only remaining valid type is UPDATE. */
\r
1144 AwsIotJobs_Assert( type == JOBS_UPDATE );
\r
1146 status = _generateUpdateRequest( pRequestInfo,
\r
1147 pRequestContents->pUpdateInfo,
\r
1155 /*-----------------------------------------------------------*/
\r
1157 void _AwsIotJobs_ParseResponse( AwsIotStatus_t status,
\r
1158 const char * pResponse,
\r
1159 size_t responseLength,
\r
1160 _jobsOperation_t * pOperation )
\r
1162 AwsIotJobs_Assert( pOperation->status == AWS_IOT_JOBS_STATUS_PENDING );
\r
1164 /* A non-waitable operation can re-use the pointers from the publish info,
\r
1165 * since those are guaranteed to be in-scope throughout the user callback.
\r
1166 * But a waitable operation must copy the data from the publish info because
\r
1167 * AwsIotJobs_Wait may be called after the MQTT library frees the publish
\r
1169 if( ( pOperation->flags & AWS_IOT_JOBS_FLAG_WAITABLE ) == 0 )
\r
1171 pOperation->pJobsResponse = pResponse;
\r
1172 pOperation->jobsResponseLength = responseLength;
\r
1176 IotLogDebug( "Allocating new buffer for waitable Jobs %s.",
\r
1177 _pAwsIotJobsOperationNames[ pOperation->type ] );
\r
1179 /* Parameter validation should not have allowed a NULL malloc function. */
\r
1180 AwsIotJobs_Assert( pOperation->mallocResponse != NULL );
\r
1182 /* Allocate a buffer for the retrieved document. */
\r
1183 pOperation->pJobsResponse = pOperation->mallocResponse( responseLength );
\r
1185 if( pOperation->pJobsResponse == NULL )
\r
1187 IotLogError( "Failed to allocate buffer for retrieved Jobs %s response.",
\r
1188 _pAwsIotJobsOperationNames[ pOperation->type ] );
\r
1190 pOperation->status = AWS_IOT_JOBS_NO_MEMORY;
\r
1194 /* Copy the response. */
\r
1195 ( void ) memcpy( ( void * ) pOperation->pJobsResponse, pResponse, responseLength );
\r
1196 pOperation->jobsResponseLength = responseLength;
\r
1200 /* Set the status of the Jobs operation. */
\r
1201 if( pOperation->status == AWS_IOT_JOBS_STATUS_PENDING )
\r
1203 if( status == AWS_IOT_ACCEPTED )
\r
1205 pOperation->status = AWS_IOT_JOBS_SUCCESS;
\r
1209 pOperation->status = _parseErrorDocument( pResponse, responseLength );
\r
1214 /*-----------------------------------------------------------*/
\r