From aebde4488e82a3bd7d71189aa6b1d64f072e2fa7 Mon Sep 17 00:00:00 2001 From: Sukhmani Minhas <50919130+sukhmanm@users.noreply.github.com> Date: Wed, 10 Mar 2021 13:35:49 -0500 Subject: [PATCH 01/15] ISO formatting --- source/sigv4.c | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/source/sigv4.c b/source/sigv4.c index e69de29b..99e55dde 100644 --- a/source/sigv4.c +++ b/source/sigv4.c @@ -0,0 +1,91 @@ +/* + * SigV4 Utility Library v1.0.0 + * Copyright (C) 2020 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 sigv4.c + * @brief Implements the user-facing functions in sigv4.h + */ + +#include +#include +#include + +#include "sigv4.h" + +/*-----------------------------------------------------------*/ + +SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, + size_t dateLen, + char pDateISO8601[ 17 ] ) +{ + SigV4Status_t returnStatus = SigV4Success; + size_t lenFormatted = 0U; + char * pLastChar = NULL; + struct tm tm; + + /* Check for NULL parameters. */ + if( pDate == NULL ) + { + LogError( ( "Parameter check failed: pDate is NULL." ) ); + returnStatus = SigV4InvalidParameter; + } + else if( pDateISO8601 == NULL ) + { + LogError( ( "Parameter check failed: pDateISO8601 is NULL." ) ); + returnStatus = SigV4InvalidParameter; + } + /* Check validity of the date header size provided. */ + else if( dateLen == 0U ) + { + LogError( ( "Parameter check failed: dateLen must be greater than 0." ) ); + returnStatus = SigV4InvalidParameter; + } + + if( returnStatus == SigV4Success ) + { + memset( &tm, 0, sizeof( struct tm ) ); + pLastChar = strptime( pDate, "%Y-%m-%dT%H:%M:%SZ", &tm ); + + if( pLastChar == NULL ) + { + LogError( ( "Error matching input to ISO8601 format string." ) ); + returnStatus == SigV4ISOFormattingError; + } + else if( pLastChar != '\0' ) + { + LogWarn( ( "Input contained more characters than expected." ) ); + } + } + + if( returnStatus == SigV4Success ) + { + lenFormatted = strftime( pDateISO8601, 17, "%Y%m%dT%H%M%SZ", &tm ); + + if( lenFormatted != 16 ) + { + LogError( ( "Formatted string is not of expected length 16." ) ); + returnStatus = SigV4ISOFormattingError; + } + } + + return returnStatus; +} From 68f8b59656117f764ea991bd22e9270cce92094f Mon Sep 17 00:00:00 2001 From: Sukhmani Minhas <50919130+sukhmanm@users.noreply.github.com> Date: Tue, 16 Mar 2021 18:01:31 -0400 Subject: [PATCH 02/15] Add buffer size function parameter --- source/sigv4.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/source/sigv4.c b/source/sigv4.c index 99e55dde..fddbb0f4 100644 --- a/source/sigv4.c +++ b/source/sigv4.c @@ -35,7 +35,8 @@ SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, size_t dateLen, - char pDateISO8601[ 17 ] ) + char pDateISO8601[ 17 ], + size_t dateISO8601Len ) { SigV4Status_t returnStatus = SigV4Success; size_t lenFormatted = 0U; @@ -48,15 +49,23 @@ SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, LogError( ( "Parameter check failed: pDate is NULL." ) ); returnStatus = SigV4InvalidParameter; } + /* Check validity of the date header size provided. */ + else if( dateLen == 0U ) + { + LogError( ( "Parameter check failed: dateLen must be greater than 0." ) ); + returnStatus = SigV4InvalidParameter; + } else if( pDateISO8601 == NULL ) { LogError( ( "Parameter check failed: pDateISO8601 is NULL." ) ); returnStatus = SigV4InvalidParameter; } - /* Check validity of the date header size provided. */ - else if( dateLen == 0U ) + + /* Check that the buffer provided is large enough for the formatted + * output string. */ + else if( dateISO8601Len < 17U ) { - LogError( ( "Parameter check failed: dateLen must be greater than 0." ) ); + LogError( ( "Parameter check failed: dateISO8601Len must be at least 17." ) ); returnStatus = SigV4InvalidParameter; } @@ -70,7 +79,7 @@ SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, LogError( ( "Error matching input to ISO8601 format string." ) ); returnStatus == SigV4ISOFormattingError; } - else if( pLastChar != '\0' ) + else if( pLastChar[ 0 ] != '\0' ) { LogWarn( ( "Input contained more characters than expected." ) ); } From 19efd2da08c4355dc10d687427932aeed9abdb96 Mon Sep 17 00:00:00 2001 From: Sukhmani Minhas <50919130+sukhmanm@users.noreply.github.com> Date: Thu, 18 Mar 2021 13:01:35 -0400 Subject: [PATCH 03/15] Rename struct + modify return values --- source/sigv4.c | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/source/sigv4.c b/source/sigv4.c index fddbb0f4..690e4d14 100644 --- a/source/sigv4.c +++ b/source/sigv4.c @@ -38,10 +38,10 @@ SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, char pDateISO8601[ 17 ], size_t dateISO8601Len ) { - SigV4Status_t returnStatus = SigV4Success; + SigV4Status_t returnStatus = SigV4ISOFormattingError; size_t lenFormatted = 0U; char * pLastChar = NULL; - struct tm tm; + struct tm dateInfo; /* Check for NULL parameters. */ if( pDate == NULL ) @@ -63,16 +63,16 @@ SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, /* Check that the buffer provided is large enough for the formatted * output string. */ - else if( dateISO8601Len < 17U ) + else if( dateISO8601Len < ISO_BUFFER_MIN_LEN ) { - LogError( ( "Parameter check failed: dateISO8601Len must be at least 17." ) ); + LogError( ( "Parameter check failed: dateISO8601Len must be at least %u.", + ISO_BUFFER_MIN_LEN ) ); returnStatus = SigV4InvalidParameter; } - - if( returnStatus == SigV4Success ) + else { - memset( &tm, 0, sizeof( struct tm ) ); - pLastChar = strptime( pDate, "%Y-%m-%dT%H:%M:%SZ", &tm ); + memset( &dateInfo, 0, sizeof( struct tm ) ); + pLastChar = strptime( pDate, "%Y-%m-%dT%H:%M:%SZ", &dateInfo ); if( pLastChar == NULL ) { @@ -83,16 +83,19 @@ SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, { LogWarn( ( "Input contained more characters than expected." ) ); } - } - - if( returnStatus == SigV4Success ) - { - lenFormatted = strftime( pDateISO8601, 17, "%Y%m%dT%H%M%SZ", &tm ); - - if( lenFormatted != 16 ) + else { - LogError( ( "Formatted string is not of expected length 16." ) ); - returnStatus = SigV4ISOFormattingError; + lenFormatted = strftime( pDateISO8601, ISO_BUFFER_MIN_LEN, "%Y%m%dT%H%M%SZ", &dateInfo ); + + if( lenFormatted != ISO_BUFFER_MIN_LEN - 1 ) + { + LogError( ( "Formatted string is not of expected length 16." ) ); + returnStatus = SigV4ISOFormattingError; + } + else + { + returnStatus = SigV4Success; + } } } From 2c61688eb0a103af373e25f4b626368959ac47b4 Mon Sep 17 00:00:00 2001 From: Sukhmani Minhas <50919130+sukhmanm@users.noreply.github.com> Date: Thu, 18 Mar 2021 15:46:11 -0400 Subject: [PATCH 04/15] Remove formatting failure status --- source/sigv4.c | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/source/sigv4.c b/source/sigv4.c index 690e4d14..556071b2 100644 --- a/source/sigv4.c +++ b/source/sigv4.c @@ -38,7 +38,7 @@ SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, char pDateISO8601[ 17 ], size_t dateISO8601Len ) { - SigV4Status_t returnStatus = SigV4ISOFormattingError; + SigV4Status_t returnStatus = SigV4InvalidParameter; size_t lenFormatted = 0U; char * pLastChar = NULL; struct tm dateInfo; @@ -47,50 +47,48 @@ SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, if( pDate == NULL ) { LogError( ( "Parameter check failed: pDate is NULL." ) ); - returnStatus = SigV4InvalidParameter; } /* Check validity of the date header size provided. */ else if( dateLen == 0U ) { LogError( ( "Parameter check failed: dateLen must be greater than 0." ) ); - returnStatus = SigV4InvalidParameter; } else if( pDateISO8601 == NULL ) { LogError( ( "Parameter check failed: pDateISO8601 is NULL." ) ); - returnStatus = SigV4InvalidParameter; } - - /* Check that the buffer provided is large enough for the formatted - * output string. */ + /* Check that the buffer provided is large enough for the formatted output + * string. */ else if( dateISO8601Len < ISO_BUFFER_MIN_LEN ) { LogError( ( "Parameter check failed: dateISO8601Len must be at least %u.", ISO_BUFFER_MIN_LEN ) ); - returnStatus = SigV4InvalidParameter; } else { memset( &dateInfo, 0, sizeof( struct tm ) ); + + /* Parse pDate according to the input's expected string format, and + * populate the date struct with its components. */ pLastChar = strptime( pDate, "%Y-%m-%dT%H:%M:%SZ", &dateInfo ); if( pLastChar == NULL ) { LogError( ( "Error matching input to ISO8601 format string." ) ); - returnStatus == SigV4ISOFormattingError; } else if( pLastChar[ 0 ] != '\0' ) { + /* The expected pattern was found, but additional characters remain + * unparsed in the input string. */ LogWarn( ( "Input contained more characters than expected." ) ); } else - { + /* Construct ISO 8601 string using members of populated date struct. */ lenFormatted = strftime( pDateISO8601, ISO_BUFFER_MIN_LEN, "%Y%m%dT%H%M%SZ", &dateInfo ); if( lenFormatted != ISO_BUFFER_MIN_LEN - 1 ) { LogError( ( "Formatted string is not of expected length 16." ) ); - returnStatus = SigV4ISOFormattingError; } else { From f74fce828ecc5eb2acd04042a15da0c76455abee Mon Sep 17 00:00:00 2001 From: Sukhmani Minhas <50919130+sukhmanm@users.noreply.github.com> Date: Thu, 18 Mar 2021 16:27:35 -0400 Subject: [PATCH 05/15] Undo enum deletion + allow longer inputs --- source/sigv4.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/source/sigv4.c b/source/sigv4.c index 556071b2..8f350295 100644 --- a/source/sigv4.c +++ b/source/sigv4.c @@ -57,6 +57,7 @@ SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, { LogError( ( "Parameter check failed: pDateISO8601 is NULL." ) ); } + /* Check that the buffer provided is large enough for the formatted output * string. */ else if( dateISO8601Len < ISO_BUFFER_MIN_LEN ) @@ -75,20 +76,24 @@ SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, if( pLastChar == NULL ) { LogError( ( "Error matching input to ISO8601 format string." ) ); - } - else if( pLastChar[ 0 ] != '\0' ) - { - /* The expected pattern was found, but additional characters remain - * unparsed in the input string. */ - LogWarn( ( "Input contained more characters than expected." ) ); + returnStatus = SigV4ISOFormattingError; } else + { + if( pLastChar[ 0 ] != '\0' ) + { + /* The expected pattern was found, but additional characters remain + * unparsed in the input string. */ + LogWarn( ( "Input contained more characters than expected." ) ); + } + /* Construct ISO 8601 string using members of populated date struct. */ lenFormatted = strftime( pDateISO8601, ISO_BUFFER_MIN_LEN, "%Y%m%dT%H%M%SZ", &dateInfo ); if( lenFormatted != ISO_BUFFER_MIN_LEN - 1 ) { LogError( ( "Formatted string is not of expected length 16." ) ); + returnStatus = SigV4ISOFormattingError; } else { From dcce43067681c1a34947609d56a8c9f7bab441e0 Mon Sep 17 00:00:00 2001 From: Sukhmani Minhas <50919130+sukhmanm@users.noreply.github.com> Date: Fri, 19 Mar 2021 15:20:40 -0400 Subject: [PATCH 06/15] Replace strptime() with sscanf() --- source/sigv4.c | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/source/sigv4.c b/source/sigv4.c index 8f350295..6b531c61 100644 --- a/source/sigv4.c +++ b/source/sigv4.c @@ -25,6 +25,7 @@ * @brief Implements the user-facing functions in sigv4.h */ +#include #include #include #include @@ -35,12 +36,11 @@ SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, size_t dateLen, - char pDateISO8601[ 17 ], + char * pDateISO8601, size_t dateISO8601Len ) { SigV4Status_t returnStatus = SigV4InvalidParameter; size_t lenFormatted = 0U; - char * pLastChar = NULL; struct tm dateInfo; /* Check for NULL parameters. */ @@ -60,10 +60,10 @@ SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, /* Check that the buffer provided is large enough for the formatted output * string. */ - else if( dateISO8601Len < ISO_BUFFER_MIN_LEN ) + else if( dateISO8601Len < SIV4_ISO_STRING_LEN + 1 ) { LogError( ( "Parameter check failed: dateISO8601Len must be at least %u.", - ISO_BUFFER_MIN_LEN ) ); + SIV4_ISO_STRING_LEN + 1 ) ); } else { @@ -71,28 +71,32 @@ SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, /* Parse pDate according to the input's expected string format, and * populate the date struct with its components. */ - pLastChar = strptime( pDate, "%Y-%m-%dT%H:%M:%SZ", &dateInfo ); - - if( pLastChar == NULL ) + if( sscanf( pDate, "%4d-%2d-%2dT%2d:%2d:%2dZ", + &dateInfo.tm_year, + &dateInfo.tm_mon, + &dateInfo.tm_mday, + &dateInfo.tm_hour, + &dateInfo.tm_min, + &dateInfo.tm_sec ) != SIV4_ISO_STRING_LEN - 10U ) { - LogError( ( "Error matching input to ISO8601 format string." ) ); + LogError( ( "sscanf() failed to parse the date string using the format expected." ) ); returnStatus = SigV4ISOFormattingError; } else { - if( pLastChar[ 0 ] != '\0' ) - { - /* The expected pattern was found, but additional characters remain - * unparsed in the input string. */ - LogWarn( ( "Input contained more characters than expected." ) ); - } + /* Standardize month and year values for struct tm's specifications: + * - tm_mon = "months from January" (0-11) + * - tm_year = "years since 1900" */ + dateInfo.tm_mon--; + dateInfo.tm_year -= 1900; /* Construct ISO 8601 string using members of populated date struct. */ - lenFormatted = strftime( pDateISO8601, ISO_BUFFER_MIN_LEN, "%Y%m%dT%H%M%SZ", &dateInfo ); + lenFormatted = strftime( pDateISO8601, SIV4_ISO_STRING_LEN + 1, "%Y%m%dT%H%M%SZ", &dateInfo ); - if( lenFormatted != ISO_BUFFER_MIN_LEN - 1 ) + if( lenFormatted != SIV4_ISO_STRING_LEN ) { - LogError( ( "Formatted string is not of expected length 16." ) ); + LogError( ( "Formatted string is not of expected length %u.", + SIV4_ISO_STRING_LEN ) ); returnStatus = SigV4ISOFormattingError; } else From 6672462f10a93d045e7f27f0695721651a76cc53 Mon Sep 17 00:00:00 2001 From: Sukhmani Minhas <50919130+sukhmanm@users.noreply.github.com> Date: Fri, 19 Mar 2021 19:55:23 -0400 Subject: [PATCH 07/15] Add dateLen check --- source/include/sigv4.h | 1 + source/sigv4.c | 17 ++++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/source/include/sigv4.h b/source/include/sigv4.h index dd86e208..362b5333 100644 --- a/source/include/sigv4.h +++ b/source/include/sigv4.h @@ -67,6 +67,7 @@ /**< Length of the date header in ISO 8601 format. */ #define SIGV4_ISO_STRING_LEN 16U +#define SIGV4_EXPECTED_DATE_LEN 20U /** @}*/ /** diff --git a/source/sigv4.c b/source/sigv4.c index 6b531c61..9b6d5caa 100644 --- a/source/sigv4.c +++ b/source/sigv4.c @@ -48,17 +48,20 @@ SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, { LogError( ( "Parameter check failed: pDate is NULL." ) ); } - /* Check validity of the date header size provided. */ - else if( dateLen == 0U ) - { - LogError( ( "Parameter check failed: dateLen must be greater than 0." ) ); - } else if( pDateISO8601 == NULL ) { LogError( ( "Parameter check failed: pDateISO8601 is NULL." ) ); } - /* Check that the buffer provided is large enough for the formatted output + /* Check that the date buffer provided is not shorter than the expected + * input format. */ + else if( dateLen < SIGV4_EXPECTED_DATE_LEN + 1 ) + { + LogError( ( "Parameter check failed: dateLen must be at least %u.", + SIGV4_EXPECTED_DATE_LEN + 1 ) ); + } + + /* Check that the output buffer provided is large enough for the formatted * string. */ else if( dateISO8601Len < SIV4_ISO_STRING_LEN + 1 ) { @@ -77,7 +80,7 @@ SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, &dateInfo.tm_mday, &dateInfo.tm_hour, &dateInfo.tm_min, - &dateInfo.tm_sec ) != SIV4_ISO_STRING_LEN - 10U ) + &dateInfo.tm_sec ) != 6 ) { LogError( ( "sscanf() failed to parse the date string using the format expected." ) ); returnStatus = SigV4ISOFormattingError; From 1f1db6c12dde56f5a7025446580dd6ff5d7b8517 Mon Sep 17 00:00:00 2001 From: Sukhmani Minhas <50919130+sukhmanm@users.noreply.github.com> Date: Thu, 25 Mar 2021 17:46:57 -0400 Subject: [PATCH 08/15] Addressing feedback --- source/include/sigv4.h | 8 ++++++-- source/sigv4.c | 23 ++++++++++++----------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/source/include/sigv4.h b/source/include/sigv4.h index 362b5333..0c8e484d 100644 --- a/source/include/sigv4.h +++ b/source/include/sigv4.h @@ -67,7 +67,8 @@ /**< Length of the date header in ISO 8601 format. */ #define SIGV4_ISO_STRING_LEN 16U -#define SIGV4_EXPECTED_DATE_LEN 20U +/**< Length of the date header found in AWS IoT's HTTP response. */ +#define SIGV4_EXPECTED_AWS_IOT_DATE_LEN 20U /** @}*/ /** @@ -380,9 +381,12 @@ SigV4Status_t SigV4_GenerateHTTPAuthorization( const SigV4Parameters_t * pParams /** * @brief Parse the date header value from the AWS IoT response. + * * The AWS IoT response date is of the form: 2018-01-18T09:18:06Z. + * The ISO8601-formatted date is: 20180118T091806Z. * - * @param[in] pDate The date header value. + * @param[in] pDate The RFC3339-formatted date header value, found in the HTTP + * response returned by AWS IoT (ex. "2018-01-18T09:18:06Z"). * @param[in] dateLen length of the pDate header value. * @param[out] pDateISO8601 The ISO8601 format compliant date. This buffer must * be large enough to hold both the ISO8601-formatted date (16 characters) and diff --git a/source/sigv4.c b/source/sigv4.c index 9b6d5caa..acb9f53f 100644 --- a/source/sigv4.c +++ b/source/sigv4.c @@ -40,8 +40,6 @@ SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, size_t dateISO8601Len ) { SigV4Status_t returnStatus = SigV4InvalidParameter; - size_t lenFormatted = 0U; - struct tm dateInfo; /* Check for NULL parameters. */ if( pDate == NULL ) @@ -55,22 +53,22 @@ SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, /* Check that the date buffer provided is not shorter than the expected * input format. */ - else if( dateLen < SIGV4_EXPECTED_DATE_LEN + 1 ) + else if( dateLen < SIGV4_EXPECTED_AWS_IOT_DATE_LEN ) { LogError( ( "Parameter check failed: dateLen must be at least %u.", - SIGV4_EXPECTED_DATE_LEN + 1 ) ); + SIGV4_EXPECTED_AWS_IOT_DATE_LEN ) ); } /* Check that the output buffer provided is large enough for the formatted * string. */ - else if( dateISO8601Len < SIV4_ISO_STRING_LEN + 1 ) + else if( dateISO8601Len < SIGV4_ISO_STRING_LEN + 1 ) { LogError( ( "Parameter check failed: dateISO8601Len must be at least %u.", - SIV4_ISO_STRING_LEN + 1 ) ); + SIGV4_ISO_STRING_LEN + 1 ) ); } else { - memset( &dateInfo, 0, sizeof( struct tm ) ); + struct tm dateInfo = { 0 }; /* Parse pDate according to the input's expected string format, and * populate the date struct with its components. */ @@ -87,6 +85,8 @@ SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, } else { + size_t lenFormatted = 0U; + /* Standardize month and year values for struct tm's specifications: * - tm_mon = "months from January" (0-11) * - tm_year = "years since 1900" */ @@ -94,12 +94,13 @@ SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, dateInfo.tm_year -= 1900; /* Construct ISO 8601 string using members of populated date struct. */ - lenFormatted = strftime( pDateISO8601, SIV4_ISO_STRING_LEN + 1, "%Y%m%dT%H%M%SZ", &dateInfo ); + lenFormatted = strftime( pDateISO8601, SIGV4_ISO_STRING_LEN + 1, "%Y%m%dT%H%M%SZ", &dateInfo ); - if( lenFormatted != SIV4_ISO_STRING_LEN ) + if( lenFormatted != SIGV4_ISO_STRING_LEN ) { - LogError( ( "Formatted string is not of expected length %u.", - SIV4_ISO_STRING_LEN ) ); + LogError( ( "Failed to generate ISO 8601 date: Call to strftime() for string formatting failed: " + "ExpectedReturnValue=%u, ActualReturnValue=%lu.", + SIGV4_ISO_STRING_LEN, lenFormatted ) ); returnStatus = SigV4ISOFormattingError; } else From c132711073e057082d8d978e53ac4cad1786231a Mon Sep 17 00:00:00 2001 From: sukhmanm Date: Fri, 26 Mar 2021 15:50:33 -0400 Subject: [PATCH 09/15] Doxygen updates --- source/include/sigv4.h | 30 +++++++++++++++++++++++------- source/sigv4.c | 4 ++-- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/source/include/sigv4.h b/source/include/sigv4.h index 0c8e484d..098d6b40 100644 --- a/source/include/sigv4.h +++ b/source/include/sigv4.h @@ -380,19 +380,35 @@ SigV4Status_t SigV4_GenerateHTTPAuthorization( const SigV4Parameters_t * pParams size_t * signatureLen ); /** - * @brief Parse the date header value from the AWS IoT response. + * @brief Parse the date header value from the AWS IoT response, and generate + * the formatted ISO 8601 date required for authentication. * - * The AWS IoT response date is of the form: 2018-01-18T09:18:06Z. - * The ISO8601-formatted date is: 20180118T091806Z. + * This is an optional utility function available to the application, to assist + * with formatting of the date header obtained from AWS IoT (when requesting a + * temporary token or sending a POST request). * - * @param[in] pDate The RFC3339-formatted date header value, found in the HTTP - * response returned by AWS IoT (ex. "2018-01-18T09:18:06Z"). - * @param[in] dateLen length of the pDate header value. + * AWS SigV4 authentication requires an ISO 8601 date to be present in the + * "x-amz-date" request header, as well as in the credential scope (must be + * identical). For additional information on date handling, please see + * https://docs.aws.amazon.com/general/latest/gr/sigv4-date-handling.html. + * + * Formatting Overview: + * - The AWS IoT response date is of the form "YYYY-MM-DD'T'hh:mm:ss'Z'" (ex. + * "2018-01-18T09:18:06Z"). + * - The ISO8601-formatted date is of the form "YYYYMMDD'T'HHMMSS'Z'" (ex. + * "20180118T091806Z"). + * + * @param[in] pDate The date header (in + * [RFC339](https://tools.ietf.org/html/rfc3339) format), found in the HTTP + * response returned by AWS IoT. This value is expected to be 20 characters in + * length, to comply with the RFC339 formatting standard used in the response. + * @param[in] dateLen The length of the pDate header value. Must be at least + * #SIGV4_EXPECTED_AWS_IOT_DATE_LEN, for valid input parameters. * @param[out] pDateISO8601 The ISO8601 format compliant date. This buffer must * be large enough to hold both the ISO8601-formatted date (16 characters) and * the terminating null character (17 in total). * @param[in] dateISO8601Len The length of buffer pDateISO8601. Must be at least - * 17 for valid input parameters. + * #SIGV4_ISO_STRING_LEN bytes, for valid input parameters. * * @return #SigV4Success code if successful, error code otherwise. */ diff --git a/source/sigv4.c b/source/sigv4.c index acb9f53f..ca74fb4a 100644 --- a/source/sigv4.c +++ b/source/sigv4.c @@ -80,7 +80,7 @@ SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, &dateInfo.tm_min, &dateInfo.tm_sec ) != 6 ) { - LogError( ( "sscanf() failed to parse the date string using the format expected." ) ); + LogError( ( "Failed to generate ISO 8601 date: call to sscanf() for input parsing failed." ) ); returnStatus = SigV4ISOFormattingError; } else @@ -98,7 +98,7 @@ SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, if( lenFormatted != SIGV4_ISO_STRING_LEN ) { - LogError( ( "Failed to generate ISO 8601 date: Call to strftime() for string formatting failed: " + LogError( ( "Failed to generate ISO 8601 date: call to strftime() for string formatting failed: " "ExpectedReturnValue=%u, ActualReturnValue=%lu.", SIGV4_ISO_STRING_LEN, lenFormatted ) ); returnStatus = SigV4ISOFormattingError; From 39b00e48f4539254af2a86b498153d1ca225fa74 Mon Sep 17 00:00:00 2001 From: sukhmanm Date: Tue, 30 Mar 2021 02:20:17 -0400 Subject: [PATCH 10/15] Add formatDate() and verify dateInfo --- source/include/sigv4.h | 11 +-- source/sigv4.c | 166 ++++++++++++++++++++++++++++++----------- 2 files changed, 129 insertions(+), 48 deletions(-) diff --git a/source/include/sigv4.h b/source/include/sigv4.h index 098d6b40..67b6981d 100644 --- a/source/include/sigv4.h +++ b/source/include/sigv4.h @@ -398,11 +398,12 @@ SigV4Status_t SigV4_GenerateHTTPAuthorization( const SigV4Parameters_t * pParams * - The ISO8601-formatted date is of the form "YYYYMMDD'T'HHMMSS'Z'" (ex. * "20180118T091806Z"). * - * @param[in] pDate The date header (in - * [RFC339](https://tools.ietf.org/html/rfc3339) format), found in the HTTP - * response returned by AWS IoT. This value is expected to be 20 characters in - * length, to comply with the RFC339 formatting standard used in the response. - * @param[in] dateLen The length of the pDate header value. Must be at least + * @param[in] pDate The date header (in [RFC3339 + * format](https://tools.ietf.org/html/rfc3339)), found in the HTTP response + * returned by AWS IoT. This value should use UTC (indicated by the "Z" + * character postfix, with no time-zone offset), and be 20 characters in length + * (excluding the null character). + * @param[in] dateLen The length of the pDate header value. Must be * #SIGV4_EXPECTED_AWS_IOT_DATE_LEN, for valid input parameters. * @param[out] pDateISO8601 The ISO8601 format compliant date. This buffer must * be large enough to hold both the ISO8601-formatted date (16 characters) and diff --git a/source/sigv4.c b/source/sigv4.c index ca74fb4a..d6a809ea 100644 --- a/source/sigv4.c +++ b/source/sigv4.c @@ -34,59 +34,97 @@ /*-----------------------------------------------------------*/ -SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, - size_t dateLen, - char * pDateISO8601, - size_t dateISO8601Len ) -{ - SigV4Status_t returnStatus = SigV4InvalidParameter; +/** + * @brief Format an ISO 8601 date, and fill the output buffer with the result. + * + * @param[in] pDate The date to be formatted, in RFC3339 format. + * @param[in] pDateISO8601 The buffer to hold the ISO 8601 encoded date. + * + * @return #SigV4Success if successful, and #SigV4ISOFormattingError if a + * parsing error occurred due to an incorrectly formatted date. If pDate is + * correctly formatted but contains an out-of-range date, #SigV4InvalidParameter + * is returned. + */ +static SigV4Status_t formatDate( const char * pDate, + char * pDateISO8601 ); - /* Check for NULL parameters. */ - if( pDate == NULL ) - { - LogError( ( "Parameter check failed: pDate is NULL." ) ); - } - else if( pDateISO8601 == NULL ) - { - LogError( ( "Parameter check failed: pDateISO8601 is NULL." ) ); - } +/*-----------------------------------------------------------*/ - /* Check that the date buffer provided is not shorter than the expected - * input format. */ - else if( dateLen < SIGV4_EXPECTED_AWS_IOT_DATE_LEN ) - { - LogError( ( "Parameter check failed: dateLen must be at least %u.", - SIGV4_EXPECTED_AWS_IOT_DATE_LEN ) ); - } +static SigV4Status_t formatDate( const char * pDate, + char * pDateISO8601 ) +{ + SigV4Status_t returnStatus = SigV4ISOFormattingError; + struct tm dateInfo = { 0 }; - /* Check that the output buffer provided is large enough for the formatted - * string. */ - else if( dateISO8601Len < SIGV4_ISO_STRING_LEN + 1 ) + /* Parse pDate according to the input's expected string format, and populate + * the date struct with its components. */ + if( sscanf( pDate, "%4d-%2d-%2dT%2d:%2d:%2dZ", + &dateInfo.tm_year, + &dateInfo.tm_mon, + &dateInfo.tm_mday, + &dateInfo.tm_hour, + &dateInfo.tm_min, + &dateInfo.tm_sec ) != 6 ) { - LogError( ( "Parameter check failed: dateISO8601Len must be at least %u.", - SIGV4_ISO_STRING_LEN + 1 ) ); + LogError( ( "Failed to generate ISO 8601 date: call to sscanf() for input parsing failed." ) ); + returnStatus = SigV4ISOFormattingError; } else { - struct tm dateInfo = { 0 }; - - /* Parse pDate according to the input's expected string format, and - * populate the date struct with its components. */ - if( sscanf( pDate, "%4d-%2d-%2dT%2d:%2d:%2dZ", - &dateInfo.tm_year, - &dateInfo.tm_mon, - &dateInfo.tm_mday, - &dateInfo.tm_hour, - &dateInfo.tm_min, - &dateInfo.tm_sec ) != 6 ) + size_t lenFormatted = 0U; + + if( ( dateInfo.tm_year < 1900 ) ) + { + LogError( ( "Invalid 'year' value parsed from date string. " + "Expected an integer larger than 1900, received: %ld", + ( long int ) dateInfo.tm_year ) ); + returnStatus = SigV4InvalidParameter; + } + + if( ( dateInfo.tm_mon < 1 ) || ( dateInfo.tm_mon > 12 ) ) + { + LogError( ( "Invalid 'month' value parsed from date string. " + "Expected an integer between 1 and 12, received: %ld", + ( long int ) dateInfo.tm_mon ) ); + returnStatus = SigV4InvalidParameter; + } + + if( ( dateInfo.tm_mday < 1 ) || ( dateInfo.tm_mday > 31 ) ) + { + LogError( ( "Invalid 'day' value parsed from date string. " + "Expected an integer between 1 and 31, received: %ld", + ( long int ) dateInfo.tm_mday ) ); + returnStatus = SigV4InvalidParameter; + } + + if( ( dateInfo.tm_hour < 0 ) || ( dateInfo.tm_hour > 23 ) ) + { + LogError( ( "Invalid 'hour' value parsed from date string. " + "Expected an integer between 0 and 23, received: %ld", + ( long int ) dateInfo.tm_hour ) ); + returnStatus = SigV4InvalidParameter; + } + + if( ( dateInfo.tm_min < 0 ) || ( dateInfo.tm_min > 59 ) ) { - LogError( ( "Failed to generate ISO 8601 date: call to sscanf() for input parsing failed." ) ); - returnStatus = SigV4ISOFormattingError; + LogError( ( "Invalid 'minute' value parsed from date string. " + "Expected an integer between 0 and 59, received: %ld", + ( long int ) dateInfo.tm_min ) ); + returnStatus = SigV4InvalidParameter; } - else + + /* C90 allows for an additional leap second corresponding to the (rare) + * UTC adjustment. */ + if( ( dateInfo.tm_sec < 0 ) || ( dateInfo.tm_sec > 60 ) ) { - size_t lenFormatted = 0U; + LogError( ( "Invalid 'second' value parsed from date string. " + "Expected an integer between 0 and 60, received: %ld", + ( long int ) dateInfo.tm_sec ) ); + returnStatus = SigV4InvalidParameter; + } + if( returnStatus != SigV4InvalidParameter ) + { /* Standardize month and year values for struct tm's specifications: * - tm_mon = "months from January" (0-11) * - tm_year = "years since 1900" */ @@ -100,7 +138,8 @@ SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, { LogError( ( "Failed to generate ISO 8601 date: call to strftime() for string formatting failed: " "ExpectedReturnValue=%u, ActualReturnValue=%lu.", - SIGV4_ISO_STRING_LEN, lenFormatted ) ); + SIGV4_ISO_STRING_LEN, + ( unsigned long ) lenFormatted ) ); returnStatus = SigV4ISOFormattingError; } else @@ -112,3 +151,44 @@ SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, return returnStatus; } + +/*-----------------------------------------------------------*/ + +SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, + size_t dateLen, + char * pDateISO8601, + size_t dateISO8601Len ) +{ + SigV4Status_t returnStatus = SigV4InvalidParameter; + + /* Check for NULL parameters. */ + if( pDate == NULL ) + { + LogError( ( "Parameter check failed: pDate is NULL." ) ); + } + else if( pDateISO8601 == NULL ) + { + LogError( ( "Parameter check failed: pDateISO8601 is NULL." ) ); + } + + /* Check that the date provided is of the expected length. */ + else if( dateLen != SIGV4_EXPECTED_AWS_IOT_DATE_LEN ) + { + LogError( ( "Parameter check failed: dateLen must be %u.", + SIGV4_EXPECTED_AWS_IOT_DATE_LEN ) ); + } + + /* Check that the output buffer provided is large enough for the formatted + * string. */ + else if( dateISO8601Len < SIGV4_ISO_STRING_LEN + 1 ) + { + LogError( ( "Parameter check failed: dateISO8601Len must be at least %u.", + SIGV4_ISO_STRING_LEN + 1 ) ); + } + else + { + returnStatus = formatDate( pDate, pDateISO8601 ); + } + + return returnStatus; +} From 3a7e5614dc0299d7da0b896b96d8348048235482 Mon Sep 17 00:00:00 2001 From: sukhmanm Date: Thu, 1 Apr 2021 12:17:43 -0400 Subject: [PATCH 11/15] Add parsing implementation --- source/include/sigv4.h | 6 +- source/include/sigv4_internal.h | 68 +++++++++ source/sigv4.c | 236 +++++++++++++++++++++++++++----- 3 files changed, 271 insertions(+), 39 deletions(-) create mode 100644 source/include/sigv4_internal.h diff --git a/source/include/sigv4.h b/source/include/sigv4.h index 67b6981d..13c89963 100644 --- a/source/include/sigv4.h +++ b/source/include/sigv4.h @@ -67,8 +67,10 @@ /**< Length of the date header in ISO 8601 format. */ #define SIGV4_ISO_STRING_LEN 16U -/**< Length of the date header found in AWS IoT's HTTP response. */ -#define SIGV4_EXPECTED_AWS_IOT_DATE_LEN 20U +/**< Expected length of an RFC 3339 date input. */ +#define SIGV4_EXPECTED_LEN_RFC_3339 20U +/**< Expected length of an RFC 5322 date input. */ +#define SIGV4_EXPECTED_LEN_RFC_5322 29U /** @}*/ /** diff --git a/source/include/sigv4_internal.h b/source/include/sigv4_internal.h new file mode 100644 index 00000000..4db9d06d --- /dev/null +++ b/source/include/sigv4_internal.h @@ -0,0 +1,68 @@ +/* + * SigV4 Utility Library v1.0.0 + * Copyright (C) 2020 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 sigv4_internal.h + * @brief Internal definitions for the SigV4 Client Utility Library. + */ + +#ifndef SIGV4_INTERNAL_H_ +#define SIGV4_INTERNAL_H_ + +/* Constants for date verification. */ +#define YEAR_MIN 1900L /**< Minimum year accepted. */ + +/** + * @brief Month name abbreviations for RFC 5322 date parsing. + */ +#define MONTH_NAMES { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" } + +/** + * @brief Number of days in each respective month. + */ +#define MONTH_DAYS { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } + +#define FORMAT_RFC_3339 "%4Y-%2M-%2DT%2h:%2m:%2sZ" /**< Format string to parse RFC 3339 date. */ +#define FORMAT_RFC_3339_LEN sizeof( FORMAT_RFC_3339 ) - 1U /**< Length of the RFC 3339 format string. */ + +#define FORMAT_RFC_5322 "%3*, %2D %3M %4Y %2h:%2m:%2s GMT" /**< Format string to parse RFC 5322 date. */ +#define FORMAT_RFC_5322_LEN sizeof( FORMAT_RFC_5322 ) - 1U /**< Length of the RFC 3339 format string. */ + +#define ISO_YEAR_LEN 4U /**< Length of year value in ISO 8601 date. */ +#define ISO_NON_YEAR_LEN 2U /**< Length of non-year values in ISO 8601 date. */ + +/** + * @brief An aggregator representing the individually parsed elements of the + * user-provided date parameter. This is used to verify the complete date + * representation, and construct the final ISO 8601 string. + */ +typedef struct SigV4DateTime +{ + int32_t tm_year; /**< Year (>= 1900) */ + int32_t tm_mon; /**< Month (1 to 12) */ + int32_t tm_mday; /**< Day of Month (1 to 28/29/20/31) */ + int32_t tm_hour; /**< Hour (0 to 23) */ + int32_t tm_min; /**< Minutes (0 to 59) */ + int32_t tm_sec; /**< Seconds (0 to 60) */ +} SigV4DateTime_t; + +#endif /* ifndef SIGV4_INTERNAL_H_ */ diff --git a/source/sigv4.c b/source/sigv4.c index d6a809ea..e2987477 100644 --- a/source/sigv4.c +++ b/source/sigv4.c @@ -25,15 +25,37 @@ * @brief Implements the user-facing functions in sigv4.h */ -#include #include #include -#include +#include #include "sigv4.h" +#include "sigv4_internal.h" /*-----------------------------------------------------------*/ +/** + * @brief Parses date according to format string parameter, and populates date + * representation struct SigV4DateTime_t with its elements. + * + * @param[in] pDate The date to be parsed. + * @param[in] dateLen Length of pDate, the date to be formatted. + * @param[in] pFormat The format string used to extract date pDateElements from pDate. + * This string, among other characters, may contain specifiers of the form + * "%LV", where L is the number of characters to be readLoc, and V is one of + * {Y, M, D, h, m, s, *}, representing a year, month, day, hour, minute, second, + * or skipped (un-parsed) value, respectively. + * @param[in] formatLen Length of the format string pFormat. + * @param[out] pDateElements The deconstructed date representation of pDate. + * + * @return The number of format specifiers found and filled in pDateElements. + */ +static size_t parseDate( const char * pDate, + size_t dateLen, + const char * pFormat, + size_t formatLen, + SigV4DateTime_t * pDateElements ); + /** * @brief Format an ISO 8601 date, and fill the output buffer with the result. * @@ -68,26 +90,164 @@ static SigV4Status_t formatDate( const char * pDate, { LogError( ( "Failed to generate ISO 8601 date: call to sscanf() for input parsing failed." ) ); returnStatus = SigV4ISOFormattingError; - } - else + +static size_t parseDate( const char * pDate, + size_t dateLen, + const char * pFormat, + size_t formatLen, + SigV4DateTime_t * pDateElements ) +{ + size_t readLoc = 0U, lenToRead = 0U, formatIndex = 0U, count = 0U; + int32_t result = 0; + char * pMonthNames[] = MONTH_NAMES; + + assert( pDate != NULL ); + assert( pFormat != NULL ); + assert( pDateElements != NULL ); + + /* Loop through format string. */ + while( formatIndex < formatLen ) { - size_t lenFormatted = 0U; + if( pFormat[ formatIndex ] == '%' ) + { + /* '%' must be followed by a length and type specification. */ + assert( formatIndex < formatLen - 2 ); - if( ( dateInfo.tm_year < 1900 ) ) + /* Numerical value of length specifier character. */ + lenToRead = pFormat[ ++formatIndex ] - '0'; + result = 0; + + /* Ensure read is within buffer bounds. */ + assert( readLoc + lenToRead - 1 < dateLen ); + + /* Read specified characters from pDate. */ + do + { + if( !isdigit( pDate[ readLoc ] ) ) + { + /* Month and skipped characters can be non-numeric, and are + * handled by the switch statement. */ + if( ( pFormat[ formatIndex + 1U ] != 'M' ) && + ( pFormat[ formatIndex + 1U ] != '*' ) ) + { + LogError( ( "Parsing Error: Expected numerical string of type '%%%d%c', " + "but received '%.*s'.", + ( int ) lenToRead, + pFormat[ formatIndex + 1U ], + ( int ) lenToRead, + &pDate[ readLoc ] ) ); + } + + /* Set invalid value to allow switch statement to handle + * case based on specifier. */ + result = -1; + break; + } + + result = result * 10 + ( pDate[ readLoc++ ] - '0' ); + } while( --lenToRead ); + + switch( pFormat[ ++formatIndex ] ) + { + case 'Y': + pDateElements->tm_year = result; + count += ( result != -1 ) ? 1 : 0; + break; + + case 'M': + + /* Numerical month representation (ex. RFC 3339). */ + if( result > 0 ) + { + pDateElements->tm_mon = result; + count++; + } + /* Non-numerical month representation (ex. RFC 5322). */ + else if( lenToRead == 3U ) + { + result = 0; + + while( result < 12 ) + { + /* If parsed value exists in pMonthNames, assign + * numerical representation. */ + if( strncmp( pMonthNames[ result++ ], &pDate[ readLoc ], lenToRead ) == 0 ) + { + pDateElements->tm_mon = result; + readLoc += lenToRead; + count++; + break; + } + } + } + + break; + + case 'D': + pDateElements->tm_mday = result; + count += ( result != -1 ) ? 1 : 0; + break; + + case 'h': + pDateElements->tm_hour = result; + count += ( result != -1 ) ? 1 : 0; + break; + + case 'm': + pDateElements->tm_min = result; + count += ( result != -1 ) ? 1 : 0; + break; + + case 's': + pDateElements->tm_sec = result; + count += ( result != -1 ) ? 1 : 0; + break; + + case '*': + readLoc += lenToRead; + break; + + default: + LogError( ( "Parsing error: Unexpected character '%c' " + "found in format string.", + ( char ) pFormat[ ++formatIndex ] ) ); + break; + } + } + else { - LogError( ( "Invalid 'year' value parsed from date string. " + if( pDate[ readLoc++ ] != pFormat[ formatIndex ] ) + { + LogError( ( "Parsing error: Expected character '%c', " + "but received '%c'.", + pFormat[ formatIndex ], pDate[ readLoc - 1 ] ) ); + break; + } + } + + formatIndex++; + } + + return count; +} + +/*-----------------------------------------------------------*/ + + if( ( dateInfo.tm_year < 1900 ) ) + { + LogError( ( "Invalid 'year' value parsed from date string. " "Expected an integer larger than 1900, received: %ld", ( long int ) dateInfo.tm_year ) ); returnStatus = SigV4InvalidParameter; - } + } if( ( dateInfo.tm_mon < 1 ) || ( dateInfo.tm_mon > 12 ) ) - { - LogError( ( "Invalid 'month' value parsed from date string. " - "Expected an integer between 1 and 12, received: %ld", + { + LogError( ( "Invalid 'month' value parsed from date string. " + "Expected an integer between 1 and 12, received: %ld", ( long int ) dateInfo.tm_mon ) ); returnStatus = SigV4InvalidParameter; - } + } if( ( dateInfo.tm_mday < 1 ) || ( dateInfo.tm_mday > 31 ) ) { @@ -95,36 +255,36 @@ static SigV4Status_t formatDate( const char * pDate, "Expected an integer between 1 and 31, received: %ld", ( long int ) dateInfo.tm_mday ) ); returnStatus = SigV4InvalidParameter; - } + } if( ( dateInfo.tm_hour < 0 ) || ( dateInfo.tm_hour > 23 ) ) - { - LogError( ( "Invalid 'hour' value parsed from date string. " - "Expected an integer between 0 and 23, received: %ld", + { + LogError( ( "Invalid 'hour' value parsed from date string. " + "Expected an integer between 0 and 23, received: %ld", ( long int ) dateInfo.tm_hour ) ); returnStatus = SigV4InvalidParameter; - } + } if( ( dateInfo.tm_min < 0 ) || ( dateInfo.tm_min > 59 ) ) - { - LogError( ( "Invalid 'minute' value parsed from date string. " - "Expected an integer between 0 and 59, received: %ld", + { + LogError( ( "Invalid 'minute' value parsed from date string. " + "Expected an integer between 0 and 59, received: %ld", ( long int ) dateInfo.tm_min ) ); returnStatus = SigV4InvalidParameter; - } + } /* C90 allows for an additional leap second corresponding to the (rare) * UTC adjustment. */ if( ( dateInfo.tm_sec < 0 ) || ( dateInfo.tm_sec > 60 ) ) - { - LogError( ( "Invalid 'second' value parsed from date string. " - "Expected an integer between 0 and 60, received: %ld", + { + LogError( ( "Invalid 'second' value parsed from date string. " + "Expected an integer between 0 and 60, received: %ld", ( long int ) dateInfo.tm_sec ) ); returnStatus = SigV4InvalidParameter; - } + } if( returnStatus != SigV4InvalidParameter ) - { +{ /* Standardize month and year values for struct tm's specifications: * - tm_mon = "months from January" (0-11) * - tm_year = "years since 1900" */ @@ -135,18 +295,18 @@ static SigV4Status_t formatDate( const char * pDate, lenFormatted = strftime( pDateISO8601, SIGV4_ISO_STRING_LEN + 1, "%Y%m%dT%H%M%SZ", &dateInfo ); if( lenFormatted != SIGV4_ISO_STRING_LEN ) - { + { LogError( ( "Failed to generate ISO 8601 date: call to strftime() for string formatting failed: " "ExpectedReturnValue=%u, ActualReturnValue=%lu.", SIGV4_ISO_STRING_LEN, ( unsigned long ) lenFormatted ) ); returnStatus = SigV4ISOFormattingError; - } - else - { + } + else + { returnStatus = SigV4Success; } - } + } } return returnStatus; @@ -170,20 +330,22 @@ SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, { LogError( ( "Parameter check failed: pDateISO8601 is NULL." ) ); } - /* Check that the date provided is of the expected length. */ - else if( dateLen != SIGV4_EXPECTED_AWS_IOT_DATE_LEN ) + else if( ( dateLen != SIGV4_EXPECTED_LEN_RFC_3339 ) && + ( dateLen != SIGV4_EXPECTED_LEN_RFC_5322 ) ) { - LogError( ( "Parameter check failed: dateLen must be %u.", - SIGV4_EXPECTED_AWS_IOT_DATE_LEN ) ); + LogError( ( "Parameter check failed: dateLen must be either %u or %u, " + "for RFC 3339 and RFC 5322 formats, respectively.", + SIGV4_EXPECTED_LEN_RFC_3339, + SIGV4_EXPECTED_LEN_RFC_5322 ) ); } /* Check that the output buffer provided is large enough for the formatted * string. */ - else if( dateISO8601Len < SIGV4_ISO_STRING_LEN + 1 ) + else if( dateISO8601Len < SIGV4_ISO_STRING_LEN ) { LogError( ( "Parameter check failed: dateISO8601Len must be at least %u.", - SIGV4_ISO_STRING_LEN + 1 ) ); + SIGV4_ISO_STRING_LEN ) ); } else { From 0de9696eceff29ff04b0e9c3a00d175b105a88b4 Mon Sep 17 00:00:00 2001 From: sukhmanm Date: Sat, 3 Apr 2021 12:19:18 -0400 Subject: [PATCH 12/15] Validate + buffer write --- source/sigv4.c | 209 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 150 insertions(+), 59 deletions(-) diff --git a/source/sigv4.c b/source/sigv4.c index e2987477..a70f76e8 100644 --- a/source/sigv4.c +++ b/source/sigv4.c @@ -34,6 +34,20 @@ /*-----------------------------------------------------------*/ +/** + * @brief Converts an integer value to its ASCII respresentation, and stores the + * result in the provided buffer. + * + * @param[in] value The value to convert to ASCII. + * @param[in, out] pBuffer The starting location of the buffer on input, and the + * ending location on output. + * @param[in] lenBuf Width of value to write (padded with leading 0s if + * necessary). + */ +static void intToAscii( int32_t value, + char ** pBuffer, + size_t lenBuf ); + /** * @brief Parses date according to format string parameter, and populates date * representation struct SigV4DateTime_t with its elements. @@ -56,40 +70,58 @@ static size_t parseDate( const char * pDate, size_t formatLen, SigV4DateTime_t * pDateElements ); +/** + * @brief Verify date stored in a SigV4DateTime_t date representation. + * + * @param[in] pDateElements The date representation to be verified. + * + * @return #SigV4Success if successful, and #SigV4ISOFormattingError if any + * member of SigV4DateTime_t is invalid or represents an out-of-range date. + */ +static SigV4Status_t validateDateTime( SigV4DateTime_t * pDateElements ); + /** * @brief Format an ISO 8601 date, and fill the output buffer with the result. * * @param[in] pDate The date to be formatted, in RFC3339 format. + * @param[in] dateLen Length of pDate, the date to be formatted. * @param[in] pDateISO8601 The buffer to hold the ISO 8601 encoded date. + * @param[in] dateISO8601Len Length of pDateISO8601, the formatted date buffer. * * @return #SigV4Success if successful, and #SigV4ISOFormattingError if a - * parsing error occurred due to an incorrectly formatted date. If pDate is - * correctly formatted but contains an out-of-range date, #SigV4InvalidParameter - * is returned. + * parsing error occurred due to an incorrectly formatted date, or if pDate + * contains an out-of-range date. */ -static SigV4Status_t formatDate( const char * pDate, - char * pDateISO8601 ); +static SigV4Status_t dateToIso8601( const char * pDate, + size_t dateLen, + char * pDateISO8601, + size_t dateISO8601Len ); /*-----------------------------------------------------------*/ -static SigV4Status_t formatDate( const char * pDate, - char * pDateISO8601 ) +static void intToAscii( int32_t value, + char ** pBuffer, + size_t bufferLen ) { - SigV4Status_t returnStatus = SigV4ISOFormattingError; - struct tm dateInfo = { 0 }; - - /* Parse pDate according to the input's expected string format, and populate - * the date struct with its components. */ - if( sscanf( pDate, "%4d-%2d-%2dT%2d:%2d:%2dZ", - &dateInfo.tm_year, - &dateInfo.tm_mon, - &dateInfo.tm_mday, - &dateInfo.tm_hour, - &dateInfo.tm_min, - &dateInfo.tm_sec ) != 6 ) + int32_t currentVal = value; + size_t lenRemaining = bufferLen; + + assert( pBuffer != NULL ); + assert( bufferLen > 0U ); + + /* Write base-10 remainder in its ASCII representation, and fill any + * remaining width with '0' characters. */ + while( lenRemaining-- ) { - LogError( ( "Failed to generate ISO 8601 date: call to sscanf() for input parsing failed." ) ); - returnStatus = SigV4ISOFormattingError; + ( *pBuffer )[ lenRemaining ] = ( currentVal % 10 ) + '0'; + currentVal /= 10; + } + + /* Move pointer to follow last written character. */ + *pBuffer += bufferLen; +} + +/*-----------------------------------------------------------*/ static size_t parseDate( const char * pDate, size_t dateLen, @@ -233,80 +265,139 @@ static size_t parseDate( const char * pDate, /*-----------------------------------------------------------*/ - if( ( dateInfo.tm_year < 1900 ) ) +static SigV4Status_t validateDateTime( SigV4DateTime_t * pDateElements ) +{ + SigV4Status_t returnStatus = SigV4InvalidParameter; + int32_t daysPerMonth[] = MONTH_DAYS; + + assert( pDateElements != NULL ); + + if( pDateElements->tm_year < YEAR_MIN ) { LogError( ( "Invalid 'year' value parsed from date string. " - "Expected an integer larger than 1900, received: %ld", - ( long int ) dateInfo.tm_year ) ); - returnStatus = SigV4InvalidParameter; + "Expected an integer %ld or greater, received: %ld", + ( long int ) YEAR_MIN, + ( long int ) pDateElements->tm_year ) ); + returnStatus = SigV4ISOFormattingError; } - if( ( dateInfo.tm_mon < 1 ) || ( dateInfo.tm_mon > 12 ) ) + if( ( pDateElements->tm_mon < 1 ) || ( pDateElements->tm_mon > 12 ) ) { LogError( ( "Invalid 'month' value parsed from date string. " "Expected an integer between 1 and 12, received: %ld", - ( long int ) dateInfo.tm_mon ) ); - returnStatus = SigV4InvalidParameter; + ( long int ) pDateElements->tm_mon ) ); + returnStatus = SigV4ISOFormattingError; } - if( ( dateInfo.tm_mday < 1 ) || ( dateInfo.tm_mday > 31 ) ) + /* Ensure that day value is within the valid range of its relevant month. */ + else if( ( pDateElements->tm_mday < 1 ) || + ( pDateElements->tm_mday > daysPerMonth[ pDateElements->tm_mon - 1 ] ) ) + { + /* Check validity of a leap year date. */ + if( ( pDateElements->tm_mon == 2 ) && ( pDateElements->tm_mday == 29 ) ) + { + if( ( ( pDateElements->tm_year % 400 ) != 0 ) && + ( ( ( pDateElements->tm_year % 4 ) != 0 ) || + ( ( pDateElements->tm_year % 100 ) == 0 ) ) ) + { + LogError( ( "%ld is not a valid leap year.", + ( long int ) pDateElements->tm_year ) ); + returnStatus = SigV4ISOFormattingError; + } + } + else { LogError( ( "Invalid 'day' value parsed from date string. " "Expected an integer between 1 and 31, received: %ld", - ( long int ) dateInfo.tm_mday ) ); - returnStatus = SigV4InvalidParameter; + ( long int ) pDateElements->tm_mday ) ); + + returnStatus = SigV4ISOFormattingError; + } } - if( ( dateInfo.tm_hour < 0 ) || ( dateInfo.tm_hour > 23 ) ) + if( ( pDateElements->tm_hour < 0 ) || ( pDateElements->tm_hour > 23 ) ) { LogError( ( "Invalid 'hour' value parsed from date string. " "Expected an integer between 0 and 23, received: %ld", - ( long int ) dateInfo.tm_hour ) ); - returnStatus = SigV4InvalidParameter; + ( long int ) pDateElements->tm_hour ) ); + returnStatus = SigV4ISOFormattingError; } - if( ( dateInfo.tm_min < 0 ) || ( dateInfo.tm_min > 59 ) ) + if( ( pDateElements->tm_min < 0 ) || ( pDateElements->tm_min > 59 ) ) { LogError( ( "Invalid 'minute' value parsed from date string. " "Expected an integer between 0 and 59, received: %ld", - ( long int ) dateInfo.tm_min ) ); - returnStatus = SigV4InvalidParameter; + ( long int ) pDateElements->tm_min ) ); + returnStatus = SigV4ISOFormattingError; } - /* C90 allows for an additional leap second corresponding to the (rare) - * UTC adjustment. */ - if( ( dateInfo.tm_sec < 0 ) || ( dateInfo.tm_sec > 60 ) ) + /* An upper limit of 60 accounts for the occasional leap second UTC + * adjustment. */ + if( ( pDateElements->tm_sec < 0 ) || ( pDateElements->tm_sec > 60 ) ) { LogError( ( "Invalid 'second' value parsed from date string. " "Expected an integer between 0 and 60, received: %ld", - ( long int ) dateInfo.tm_sec ) ); - returnStatus = SigV4InvalidParameter; + ( long int ) pDateElements->tm_sec ) ); + returnStatus = SigV4ISOFormattingError; } - if( returnStatus != SigV4InvalidParameter ) + return ( returnStatus != SigV4ISOFormattingError ) ? SigV4Success : returnStatus; +} + +/*-----------------------------------------------------------*/ + +static SigV4Status_t dateToIso8601( const char * pDate, + size_t dateLen, + char * pDateISO8601, + size_t dateISO8601Len ) { - /* Standardize month and year values for struct tm's specifications: - * - tm_mon = "months from January" (0-11) - * - tm_year = "years since 1900" */ - dateInfo.tm_mon--; - dateInfo.tm_year -= 1900; + SigV4Status_t returnStatus = SigV4InvalidParameter; + SigV4DateTime_t date = { 0 }; + char * pWriteLoc = pDateISO8601; + const char * pFormatStr = NULL; + size_t formatLen = 0U; + + assert( pDate != NULL ); + assert( pDateISO8601 != NULL ); + assert( dateLen == SIGV4_EXPECTED_LEN_RFC_3339 || + dateLen == SIGV4_EXPECTED_LEN_RFC_5322 ); + assert( dateISO8601Len >= SIGV4_ISO_STRING_LEN ); - /* Construct ISO 8601 string using members of populated date struct. */ - lenFormatted = strftime( pDateISO8601, SIGV4_ISO_STRING_LEN + 1, "%Y%m%dT%H%M%SZ", &dateInfo ); + /* Assign format string according to input type received. */ + pFormatStr = ( dateLen == SIGV4_EXPECTED_LEN_RFC_3339 ) ? + FORMAT_RFC_3339 : FORMAT_RFC_5322; - if( lenFormatted != SIGV4_ISO_STRING_LEN ) + formatLen = ( dateLen == SIGV4_EXPECTED_LEN_RFC_3339 ) ? + FORMAT_RFC_3339_LEN : FORMAT_RFC_5322_LEN; + + /* ISO 8601 contains 6 numerical date components requiring parsing. */ + if( parseDate( pDate, dateLen, pFormatStr, formatLen, &date ) == 6U ) { - LogError( ( "Failed to generate ISO 8601 date: call to strftime() for string formatting failed: " - "ExpectedReturnValue=%u, ActualReturnValue=%lu.", - SIGV4_ISO_STRING_LEN, - ( unsigned long ) lenFormatted ) ); - returnStatus = SigV4ISOFormattingError; + returnStatus = validateDateTime( &date ); } else { - returnStatus = SigV4Success; - } + LogError( ( "Parsing Error: Date did not match expected string format." ) ); + returnStatus = SigV4ISOFormattingError; } + + if( returnStatus == SigV4Success ) + { + /* Combine date elements into complete ASCII representation, and fill + * buffer with result. */ + intToAscii( date.tm_year, &pWriteLoc, ISO_YEAR_LEN ); + intToAscii( date.tm_mon, &pWriteLoc, ISO_NON_YEAR_LEN ); + intToAscii( date.tm_mday, &pWriteLoc, ISO_NON_YEAR_LEN ); + *pWriteLoc++ = 'T'; + + intToAscii( date.tm_hour, &pWriteLoc, ISO_NON_YEAR_LEN ); + intToAscii( date.tm_min, &pWriteLoc, ISO_NON_YEAR_LEN ); + intToAscii( date.tm_sec, &pWriteLoc, ISO_NON_YEAR_LEN ); + *pWriteLoc++ = 'Z'; + + LogDebug( ( "Successfully formatted ISO 8601 date: \"%.*s\"", + ( int ) dateISO8601Len, + pDateISO8601 ) ); } return returnStatus; @@ -349,7 +440,7 @@ SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, } else { - returnStatus = formatDate( pDate, pDateISO8601 ); + returnStatus = dateToIso8601( pDate, dateLen, pDateISO8601, dateISO8601Len ); } return returnStatus; From bd5756b5101f9d7fcadb9cd997cf52c56023b4c5 Mon Sep 17 00:00:00 2001 From: sukhmanm Date: Tue, 6 Apr 2021 06:08:21 -0400 Subject: [PATCH 13/15] MISRA/complexity updates --- source/include/sigv4_internal.h | 3 +- source/sigv4.c | 515 ++++++++++++++++++-------------- test/unit-test/sigv4_utest.c | 143 +++++++++ 3 files changed, 439 insertions(+), 222 deletions(-) create mode 100644 test/unit-test/sigv4_utest.c diff --git a/source/include/sigv4_internal.h b/source/include/sigv4_internal.h index 4db9d06d..02cf8bcc 100644 --- a/source/include/sigv4_internal.h +++ b/source/include/sigv4_internal.h @@ -35,6 +35,7 @@ * @brief Month name abbreviations for RFC 5322 date parsing. */ #define MONTH_NAMES { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" } +#define MONTH_ASCII_LEN 3U /** * @brief Number of days in each respective month. @@ -57,7 +58,7 @@ */ typedef struct SigV4DateTime { - int32_t tm_year; /**< Year (>= 1900) */ + int32_t tm_year; /**< Year (1900 or later) */ int32_t tm_mon; /**< Month (1 to 12) */ int32_t tm_mday; /**< Day of Month (1 to 28/29/20/31) */ int32_t tm_hour; /**< Hour (0 to 23) */ diff --git a/source/sigv4.c b/source/sigv4.c index a70f76e8..5f06eae5 100644 --- a/source/sigv4.c +++ b/source/sigv4.c @@ -27,7 +27,6 @@ #include #include -#include #include "sigv4.h" #include "sigv4_internal.h" @@ -41,61 +40,89 @@ * @param[in] value The value to convert to ASCII. * @param[in, out] pBuffer The starting location of the buffer on input, and the * ending location on output. - * @param[in] lenBuf Width of value to write (padded with leading 0s if + * @param[in] bufferLen Width of value to write (padded with leading 0s if * necessary). */ static void intToAscii( int32_t value, char ** pBuffer, - size_t lenBuf ); + size_t bufferLen ); /** - * @brief Parses date according to format string parameter, and populates date - * representation struct SigV4DateTime_t with its elements. + * @brief Check if the date represents a valid leap year day. * - * @param[in] pDate The date to be parsed. - * @param[in] dateLen Length of pDate, the date to be formatted. - * @param[in] pFormat The format string used to extract date pDateElements from pDate. - * This string, among other characters, may contain specifiers of the form - * "%LV", where L is the number of characters to be readLoc, and V is one of - * {Y, M, D, h, m, s, *}, representing a year, month, day, hour, minute, second, - * or skipped (un-parsed) value, respectively. - * @param[in] formatLen Length of the format string pFormat. - * @param[out] pDateElements The deconstructed date representation of pDate. + * @param[in] pDateElements The date representation to be verified. * - * @return The number of format specifiers found and filled in pDateElements. + * @return #SigV4Success if the date corresponds to a valid leap year, + * #SigV4ISOFormattingError otherwise. */ -static size_t parseDate( const char * pDate, - size_t dateLen, - const char * pFormat, - size_t formatLen, - SigV4DateTime_t * pDateElements ); +static SigV4Status_t checkLeap( const SigV4DateTime_t * pDateElements ); /** - * @brief Verify date stored in a SigV4DateTime_t date representation. + * @brief Verify the date stored in a SigV4DateTime_t date representation. * * @param[in] pDateElements The date representation to be verified. * - * @return #SigV4Success if successful, and #SigV4ISOFormattingError if any - * member of SigV4DateTime_t is invalid or represents an out-of-range date. + * @return #SigV4Success if the date is valid, and #SigV4ISOFormattingError if + * any member of SigV4DateTime_t is invalid or represents an out-of-range date. */ -static SigV4Status_t validateDateTime( SigV4DateTime_t * pDateElements ); +static SigV4Status_t validateDateTime( const SigV4DateTime_t * pDateElements ); + +/** + * @brief Append calculated integer value to the internal date representation. + * + * @param[in] formatChar The specifier identifying the struct member to fill. + * @param[in] result The value to assign to the specified struct member. + * @param[in, out] pDateElements The date representation structure to modify. + * + * @return #SigV4Success if the formatChar specifier corresponds to a + * SigV4DateTime_t member, #SigV4ISOFormattingError otherwise. + */ +static SigV4Status_t addToDate( const char formatChar, + int32_t result, + SigV4DateTime_t * pDateElements ); + +/** + * @brief Interpret the value of the specified characters in date, based on the + * format specifier, and append to the internal date representation. + * + * @param[in] pDate The date to be parsed. + * @param[in] formatChar The format specifier used to interpret characters. + * @param[in] readLoc The index of pDate to read from. + * @param[in] lenToRead The number of characters to read. + * @param[in, out] pDateElements The date representation to modify. + * + * @return #SigV4Success if parsing succeeded, #SigV4ISOFormattingError if the + * characters read did not match the format specifier. + */ +static SigV4Status_t scanValue( const char * pDate, + const char formatChar, + size_t readLoc, + size_t lenToRead, + SigV4DateTime_t * pDateElements ); + /** - * @brief Format an ISO 8601 date, and fill the output buffer with the result. + * @brief Parses date according to format string parameter, and populates date + * representation struct SigV4DateTime_t with its elements. * - * @param[in] pDate The date to be formatted, in RFC3339 format. + * @param[in] pDate The date to be parsed. * @param[in] dateLen Length of pDate, the date to be formatted. - * @param[in] pDateISO8601 The buffer to hold the ISO 8601 encoded date. - * @param[in] dateISO8601Len Length of pDateISO8601, the formatted date buffer. + * @param[in] pFormat The format string used to extract date pDateElements from pDate. + * This string, among other characters, may contain specifiers of the form + * "%LV", where L is the number of characters to be readLoc, and V is one of + * {Y, M, D, h, m, s, *}, representing a year, month, day, hour, minute, second, + * or skipped (un-parsed) value, respectively. + * @param[in] formatLen Length of the format string pFormat. + * @param[out] pDateElements The deconstructed date representation of pDate. * - * @return #SigV4Success if successful, and #SigV4ISOFormattingError if a - * parsing error occurred due to an incorrectly formatted date, or if pDate - * contains an out-of-range date. + * @return #SigV4Success if all format specifiers were matched successfully, + * #SigV4ISOFormattingError otherwise. */ -static SigV4Status_t dateToIso8601( const char * pDate, - size_t dateLen, - char * pDateISO8601, - size_t dateISO8601Len ); +static SigV4Status_t parseDate( const char * pDate, + size_t dateLen, + const char * pFormat, + size_t formatLen, + SigV4DateTime_t * pDateElements ); /*-----------------------------------------------------------*/ @@ -111,9 +138,9 @@ static void intToAscii( int32_t value, /* Write base-10 remainder in its ASCII representation, and fill any * remaining width with '0' characters. */ - while( lenRemaining-- ) + while( lenRemaining-- > 0U ) { - ( *pBuffer )[ lenRemaining ] = ( currentVal % 10 ) + '0'; + ( *pBuffer )[ lenRemaining ] = ( char ) ( ( currentVal % 10 ) + '0' ); currentVal /= 10; } @@ -122,153 +149,37 @@ static void intToAscii( int32_t value, } /*-----------------------------------------------------------*/ - -static size_t parseDate( const char * pDate, - size_t dateLen, - const char * pFormat, - size_t formatLen, - SigV4DateTime_t * pDateElements ) +static SigV4Status_t checkLeap( const SigV4DateTime_t * pDateElements ) { - size_t readLoc = 0U, lenToRead = 0U, formatIndex = 0U, count = 0U; - int32_t result = 0; - char * pMonthNames[] = MONTH_NAMES; + SigV4Status_t returnStatus = SigV4ISOFormattingError; - assert( pDate != NULL ); - assert( pFormat != NULL ); assert( pDateElements != NULL ); - /* Loop through format string. */ - while( formatIndex < formatLen ) + /* If the date represents a leap day, verify that the leap year is valid. */ + if( ( pDateElements->tm_mon == 2 ) && ( pDateElements->tm_mday == 29 ) ) { - if( pFormat[ formatIndex ] == '%' ) + if( ( ( pDateElements->tm_year % 400 ) != 0 ) && + ( ( ( pDateElements->tm_year % 4 ) != 0 ) || + ( ( pDateElements->tm_year % 100 ) == 0 ) ) ) { - /* '%' must be followed by a length and type specification. */ - assert( formatIndex < formatLen - 2 ); - - /* Numerical value of length specifier character. */ - lenToRead = pFormat[ ++formatIndex ] - '0'; - result = 0; - - /* Ensure read is within buffer bounds. */ - assert( readLoc + lenToRead - 1 < dateLen ); - - /* Read specified characters from pDate. */ - do - { - if( !isdigit( pDate[ readLoc ] ) ) - { - /* Month and skipped characters can be non-numeric, and are - * handled by the switch statement. */ - if( ( pFormat[ formatIndex + 1U ] != 'M' ) && - ( pFormat[ formatIndex + 1U ] != '*' ) ) - { - LogError( ( "Parsing Error: Expected numerical string of type '%%%d%c', " - "but received '%.*s'.", - ( int ) lenToRead, - pFormat[ formatIndex + 1U ], - ( int ) lenToRead, - &pDate[ readLoc ] ) ); - } - - /* Set invalid value to allow switch statement to handle - * case based on specifier. */ - result = -1; - break; - } - - result = result * 10 + ( pDate[ readLoc++ ] - '0' ); - } while( --lenToRead ); - - switch( pFormat[ ++formatIndex ] ) - { - case 'Y': - pDateElements->tm_year = result; - count += ( result != -1 ) ? 1 : 0; - break; - - case 'M': - - /* Numerical month representation (ex. RFC 3339). */ - if( result > 0 ) - { - pDateElements->tm_mon = result; - count++; - } - /* Non-numerical month representation (ex. RFC 5322). */ - else if( lenToRead == 3U ) - { - result = 0; - - while( result < 12 ) - { - /* If parsed value exists in pMonthNames, assign - * numerical representation. */ - if( strncmp( pMonthNames[ result++ ], &pDate[ readLoc ], lenToRead ) == 0 ) - { - pDateElements->tm_mon = result; - readLoc += lenToRead; - count++; - break; - } - } - } - - break; - - case 'D': - pDateElements->tm_mday = result; - count += ( result != -1 ) ? 1 : 0; - break; - - case 'h': - pDateElements->tm_hour = result; - count += ( result != -1 ) ? 1 : 0; - break; - - case 'm': - pDateElements->tm_min = result; - count += ( result != -1 ) ? 1 : 0; - break; - - case 's': - pDateElements->tm_sec = result; - count += ( result != -1 ) ? 1 : 0; - break; - - case '*': - readLoc += lenToRead; - break; - - default: - LogError( ( "Parsing error: Unexpected character '%c' " - "found in format string.", - ( char ) pFormat[ ++formatIndex ] ) ); - break; - } + LogError( ( "%ld is not a valid leap year.", + ( long int ) pDateElements->tm_year ) ); } else { - if( pDate[ readLoc++ ] != pFormat[ formatIndex ] ) - { - LogError( ( "Parsing error: Expected character '%c', " - "but received '%c'.", - pFormat[ formatIndex ], pDate[ readLoc - 1 ] ) ); - break; - } + returnStatus = SigV4Success; } - - formatIndex++; } - return count; + return returnStatus; } /*-----------------------------------------------------------*/ -static SigV4Status_t validateDateTime( SigV4DateTime_t * pDateElements ) +static SigV4Status_t validateDateTime( const SigV4DateTime_t * pDateElements ) { SigV4Status_t returnStatus = SigV4InvalidParameter; - int32_t daysPerMonth[] = MONTH_DAYS; + const int32_t daysPerMonth[] = MONTH_DAYS; assert( pDateElements != NULL ); @@ -289,29 +200,19 @@ static SigV4Status_t validateDateTime( SigV4DateTime_t * pDateElements ) returnStatus = SigV4ISOFormattingError; } - /* Ensure that day value is within the valid range of its relevant month. */ - else if( ( pDateElements->tm_mday < 1 ) || - ( pDateElements->tm_mday > daysPerMonth[ pDateElements->tm_mon - 1 ] ) ) + /* Ensure that the day of the month is valid for the relevant month. */ + if( ( returnStatus != SigV4ISOFormattingError ) && + ( ( pDateElements->tm_mday < 1 ) || + ( pDateElements->tm_mday > daysPerMonth[ pDateElements->tm_mon - 1 ] ) ) ) { - /* Check validity of a leap year date. */ - if( ( pDateElements->tm_mon == 2 ) && ( pDateElements->tm_mday == 29 ) ) - { - if( ( ( pDateElements->tm_year % 400 ) != 0 ) && - ( ( ( pDateElements->tm_year % 4 ) != 0 ) || - ( ( pDateElements->tm_year % 100 ) == 0 ) ) ) - { - LogError( ( "%ld is not a valid leap year.", - ( long int ) pDateElements->tm_year ) ); - returnStatus = SigV4ISOFormattingError; - } - } - else + /* Check if the date is a valid leap year day. */ + returnStatus = checkLeap( pDateElements ); + + if( returnStatus == SigV4ISOFormattingError ) { LogError( ( "Invalid 'day' value parsed from date string. " "Expected an integer between 1 and 31, received: %ld", ( long int ) pDateElements->tm_mday ) ); - - returnStatus = SigV4ISOFormattingError; } } @@ -346,58 +247,194 @@ static SigV4Status_t validateDateTime( SigV4DateTime_t * pDateElements ) /*-----------------------------------------------------------*/ -static SigV4Status_t dateToIso8601( const char * pDate, - size_t dateLen, - char * pDateISO8601, - size_t dateISO8601Len ) +static SigV4Status_t addToDate( const char formatChar, + int32_t result, + SigV4DateTime_t * pDateElements ) +{ + SigV4Status_t returnStatus = SigV4Success; + + assert( pDateElements != NULL ); + + switch( formatChar ) + { + case 'Y': + pDateElements->tm_year = result; + break; + + case 'M': + pDateElements->tm_mon = result; + break; + + case 'D': + pDateElements->tm_mday = result; + break; + + case 'h': + pDateElements->tm_hour = result; + break; + + case 'm': + pDateElements->tm_min = result; + break; + + case 's': + pDateElements->tm_sec = result; + break; + + case '*': + break; + + default: + LogError( ( "Parsing error: Unexpected character '%c' " + "found in format string.", + ( char ) formatChar ) ); + returnStatus = SigV4ISOFormattingError; + break; + } + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +static SigV4Status_t scanValue( const char * pDate, + const char formatChar, + size_t readLoc, + size_t lenToRead, + SigV4DateTime_t * pDateElements ) { SigV4Status_t returnStatus = SigV4InvalidParameter; - SigV4DateTime_t date = { 0 }; - char * pWriteLoc = pDateISO8601; - const char * pFormatStr = NULL; - size_t formatLen = 0U; + const char * pMonthNames[] = MONTH_NAMES; + const char * pLoc = pDate + readLoc; + size_t readLen = lenToRead; + int32_t result = 0; assert( pDate != NULL ); - assert( pDateISO8601 != NULL ); - assert( dateLen == SIGV4_EXPECTED_LEN_RFC_3339 || - dateLen == SIGV4_EXPECTED_LEN_RFC_5322 ); - assert( dateISO8601Len >= SIGV4_ISO_STRING_LEN ); + assert( pDateElements != NULL ); - /* Assign format string according to input type received. */ - pFormatStr = ( dateLen == SIGV4_EXPECTED_LEN_RFC_3339 ) ? - FORMAT_RFC_3339 : FORMAT_RFC_5322; + if( formatChar == '*' ) + { + readLen = 0U; + } - formatLen = ( dateLen == SIGV4_EXPECTED_LEN_RFC_3339 ) ? - FORMAT_RFC_3339_LEN : FORMAT_RFC_5322_LEN; + /* Determine if month value is non-numeric. */ + if( ( formatChar == 'M' ) && ( *pLoc >= 'A' ) && ( *pLoc <= 'Z' ) ) + { + assert( readLen == MONTH_ASCII_LEN ); - /* ISO 8601 contains 6 numerical date components requiring parsing. */ - if( parseDate( pDate, dateLen, pFormatStr, formatLen, &date ) == 6U ) + while( result++ < 12 ) + { + /* Search month array for parsed string. */ + if( strncmp( pMonthNames[ result - 1 ], pLoc, MONTH_ASCII_LEN ) == 0 ) + { + returnStatus = SigV4Success; + break; + } + } + + if( returnStatus != SigV4Success ) + { + LogError( ( "Unable to match string '%.3s' to a month value.", + pLoc ) ); + returnStatus = SigV4ISOFormattingError; + } + + readLen = 0U; + } + + /* Interpret integer value of numeric representation. */ + while( ( readLen > 0U ) && ( *pLoc >= '0' ) && ( *pLoc <= '9' ) ) { - returnStatus = validateDateTime( &date ); + result = ( result * 10 ) + ( int32_t ) ( *pLoc - '0' ); + readLen--; + pLoc += 1; } - else + + if( readLen != 0U ) { - LogError( ( "Parsing Error: Date did not match expected string format." ) ); + LogError( ( "Parsing Error: Expected numerical string of type '%%%d%c', " + "but received '%.*s'.", + ( int ) lenToRead, + formatChar, + ( int ) lenToRead, + pLoc ) ); returnStatus = SigV4ISOFormattingError; } - if( returnStatus == SigV4Success ) + if( returnStatus != SigV4ISOFormattingError ) { - /* Combine date elements into complete ASCII representation, and fill - * buffer with result. */ - intToAscii( date.tm_year, &pWriteLoc, ISO_YEAR_LEN ); - intToAscii( date.tm_mon, &pWriteLoc, ISO_NON_YEAR_LEN ); - intToAscii( date.tm_mday, &pWriteLoc, ISO_NON_YEAR_LEN ); - *pWriteLoc++ = 'T'; + returnStatus = addToDate( formatChar, + result, + pDateElements ); + } - intToAscii( date.tm_hour, &pWriteLoc, ISO_NON_YEAR_LEN ); - intToAscii( date.tm_min, &pWriteLoc, ISO_NON_YEAR_LEN ); - intToAscii( date.tm_sec, &pWriteLoc, ISO_NON_YEAR_LEN ); - *pWriteLoc++ = 'Z'; + return returnStatus; +} - LogDebug( ( "Successfully formatted ISO 8601 date: \"%.*s\"", - ( int ) dateISO8601Len, - pDateISO8601 ) ); +/*-----------------------------------------------------------*/ + +static SigV4Status_t parseDate( const char * pDate, + size_t dateLen, + const char * pFormat, + size_t formatLen, + SigV4DateTime_t * pDateElements ) +{ + SigV4Status_t returnStatus = SigV4InvalidParameter; + size_t readLoc = 0U, lenToRead = 0U, formatIndex = 0U; + + assert( pDate != NULL ); + assert( pFormat != NULL ); + assert( pDateElements != NULL ); + ( void ) dateLen; + + /* Loop through the format string. */ + while( ( formatIndex < formatLen ) && ( returnStatus != SigV4ISOFormattingError ) ) + { + if( pFormat[ formatIndex ] == '%' ) + { + /* '%' must be followed by a length and type specification. */ + assert( formatIndex < formatLen - 2 ); + formatIndex++; + + /* Numerical value of length specifier character. */ + lenToRead = ( ( uint64_t ) pFormat[ formatIndex ] - ( uint64_t ) '0' ); + formatIndex++; + + /* Ensure read is within buffer bounds. */ + assert( readLoc + lenToRead - 1 < dateLen ); + returnStatus = scanValue( pDate, + pFormat[ formatIndex ], + readLoc, + lenToRead, + pDateElements ); + + readLoc += lenToRead; + } + else if( pDate[ readLoc ] != pFormat[ formatIndex ] ) + { + LogError( ( "Parsing error: Expected character '%c', " + "but received '%c'.", + pFormat[ formatIndex ], pDate[ readLoc ] ) ); + returnStatus = SigV4ISOFormattingError; + } + else + { + readLoc++; + LogDebug( ( "Successfully matched character '%c' found in format string.", + pDate[ readLoc - 1 ] ) ); + } + + formatIndex++; + } + + if( ( returnStatus != SigV4ISOFormattingError ) ) + { + returnStatus = SigV4Success; + } + else + { + LogError( ( "Parsing Error: Date did not match expected string format." ) ); + returnStatus = SigV4ISOFormattingError; } return returnStatus; @@ -405,12 +442,17 @@ static SigV4Status_t dateToIso8601( const char * pDate, /*-----------------------------------------------------------*/ + SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, size_t dateLen, char * pDateISO8601, size_t dateISO8601Len ) { SigV4Status_t returnStatus = SigV4InvalidParameter; + SigV4DateTime_t date = { 0 }; + char * pWriteLoc = pDateISO8601; + const char * pFormatStr = NULL; + size_t formatLen = 0U; /* Check for NULL parameters. */ if( pDate == NULL ) @@ -440,7 +482,38 @@ SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, } else { - returnStatus = dateToIso8601( pDate, dateLen, pDateISO8601, dateISO8601Len ); + /* Assign format string according to input type received. */ + pFormatStr = ( dateLen == SIGV4_EXPECTED_LEN_RFC_3339 ) ? + ( FORMAT_RFC_3339 ) : ( FORMAT_RFC_5322 ); + + formatLen = ( dateLen == SIGV4_EXPECTED_LEN_RFC_3339 ) ? + ( FORMAT_RFC_3339_LEN ) : ( FORMAT_RFC_5322_LEN ); + + returnStatus = parseDate( pDate, dateLen, pFormatStr, formatLen, &date ); + } + + if( returnStatus == SigV4Success ) + { + returnStatus = validateDateTime( &date ); + } + + if( returnStatus == SigV4Success ) + { + /* Combine date elements into complete ASCII representation, and fill + * buffer with result. */ + intToAscii( date.tm_year, &pWriteLoc, ISO_YEAR_LEN ); + intToAscii( date.tm_mon, &pWriteLoc, ISO_NON_YEAR_LEN ); + intToAscii( date.tm_mday, &pWriteLoc, ISO_NON_YEAR_LEN ); + *pWriteLoc = 'T'; + pWriteLoc++; + intToAscii( date.tm_hour, &pWriteLoc, ISO_NON_YEAR_LEN ); + intToAscii( date.tm_min, &pWriteLoc, ISO_NON_YEAR_LEN ); + intToAscii( date.tm_sec, &pWriteLoc, ISO_NON_YEAR_LEN ); + *pWriteLoc = 'Z'; + + LogDebug( ( "Successfully formatted ISO 8601 date: \"%.*s\"", + ( int ) dateISO8601Len, + pDateISO8601 ) ); } return returnStatus; diff --git a/test/unit-test/sigv4_utest.c b/test/unit-test/sigv4_utest.c new file mode 100644 index 00000000..3ea0430f --- /dev/null +++ b/test/unit-test/sigv4_utest.c @@ -0,0 +1,143 @@ +/* + * SigV4 Utility Library v1.0.0 + * Copyright (C) 2020 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. + */ + +#include + +#include "unity.h" + +/* Include paths for public enums, structures, and macros. */ +#include "sigv4.h" +#include "sigv4_internal.h" + +/* Size of valid output date buffer. */ +#define SIGV4_TEST_BUFFER_SIZE ( 17 ) + +/* Size of larger-than-required output date buffer. */ +#define SIGV4_TEST_BUFFER_SIZE_LONG ( 30 ) + +/* Size of an output date buffer of insufficient length. */ +#define SIGV4_TEST_BUFFER_SIZE_SHORT ( 16 ) + +/* Test valid date inputs. */ +static const char pTestDateExpected[] = "Wed, 18 Jan 2018 09:18:06 GMT"; /*"2018-01-18T09:18:06Z"; */ +static const char pTestDateLong[] = "2018-01-18T09:18:06Z00:00"; +static const char pOutputExpected[] = "20180118T091806Z"; + +/* Test invalid date inputs. */ +static const char pTestDateShort[] = "2018-01T09:18Z"; +static const char pTestParsingFailure[] = "2018-01-18X09:18:06Z"; +static const char pTestFormatFailure[] = " 018-01-18T09:18:06Z"; + +/* File-scoped global variables */ +static SigV4Status_t retCode = SigV4Success; +static char testBufferValid[ SIGV4_TEST_BUFFER_SIZE ] = { 0 }; +static char testBufferLong[ SIGV4_TEST_BUFFER_SIZE_LONG ] = { 0 }; +static char testBufferShort[ SIGV4_TEST_BUFFER_SIZE_SHORT ] = { 0 }; + + +/* ============================ UNITY FIXTURES ============================== */ +/* Called before each test method. */ +void setUp() +{ +} + +/* Called after each test method. */ +void tearDown() +{ + retCode = SigV4Success; + memset( &testBufferValid, 0, sizeof( testBufferValid ) ); + memset( &testBufferLong, 0, sizeof( testBufferLong ) ); + memset( &testBufferShort, 0, sizeof( testBufferShort ) ); +} + +/* Called at the beginning of the whole suite. */ +void suiteSetUp() +{ +} + +/* Called at the end of the whole suite. */ +int suiteTearDown( int numFailures ) +{ + return numFailures; +} +/* ==================== Testing SigV4_AwsIotDateToIso8601 =================== */ + +/** + * @brief Test the happy path with zero-initialized and adequately sized output + * buffers. + */ +void test_SigV4_AwsIotDateToIso8601_Happy_Path() +{ + SigV4Status_t returnVal = SigV4Success; + + returnVal = SigV4_AwsIotDateToIso8601( pTestDateExpected, SIGV4_EXPECTED_LEN_RFC_5322, testBufferValid, SIGV4_TEST_BUFFER_SIZE ); + + TEST_ASSERT_EQUAL( SigV4Success, returnVal ); + TEST_ASSERT_EQUAL_STRING( pOutputExpected, testBufferValid ); + + /* returnVal = SigV4_AwsIotDateToIso8601( pTestDateLong, sizeof( pTestDateLong ), testBufferValid, SIGV4_TEST_BUFFER_SIZE ); */ + + /* TEST_ASSERT_EQUAL( SigV4Success, returnVal ); */ + /* TEST_ASSERT_EQUAL_STRING( pOutputExpected, testBufferValid ); */ +} + +/** + * @brief Test NULL and invalid parameters, following order of else-if blocks in + * SigV4_AwsIotDateToIso8601(). + */ +void test_SigV4_AwsIotDateToIso8601_Invalid_Params() +{ + SigV4Status_t returnVal = SigV4Success; + + /* Test pDate == NULL */ + returnVal = SigV4_AwsIotDateToIso8601( NULL, SIGV4_EXPECTED_LEN_RFC_3339, testBufferValid, SIGV4_TEST_BUFFER_SIZE ); + TEST_ASSERT_EQUAL( SigV4InvalidParameter, returnVal ); + + /* Test pDateISO8601 == NULL */ + returnVal = SigV4_AwsIotDateToIso8601( pTestDateExpected, SIGV4_EXPECTED_LEN_RFC_5322, NULL, SIGV4_TEST_BUFFER_SIZE ); + TEST_ASSERT_EQUAL( SigV4InvalidParameter, returnVal ); + + /* Test dateLen < SIGV4_EXPECTED_LEN_RFC_3339 */ + returnVal = SigV4_AwsIotDateToIso8601( pTestDateShort, sizeof( pTestDateShort ), testBufferValid, SIGV4_TEST_BUFFER_SIZE ); + TEST_ASSERT_EQUAL( SigV4InvalidParameter, returnVal ); + + /* Test dateISO8601Len < SIGV4_ISO_STRING_LEN + 1 */ + returnVal = SigV4_AwsIotDateToIso8601( pTestDateExpected, sizeof( pTestDateExpected ), testBufferShort, SIGV4_TEST_BUFFER_SIZE_SHORT ); + TEST_ASSERT_EQUAL( SigV4InvalidParameter, returnVal ); +} + +/** + * @brief Test invalid input date formats. + */ +void test_SigV4_AwsIotDateToIso8601_Formatting_Error() +{ + SigV4Status_t returnVal = SigV4Success; + + /* sscanf() failed to match input date to expected format string. */ + returnVal = SigV4_AwsIotDateToIso8601( pTestParsingFailure, sizeof( pTestParsingFailure ) - 1U, testBufferValid, SIGV4_TEST_BUFFER_SIZE ); + TEST_ASSERT_EQUAL( SigV4ISOFormattingError, returnVal ); + + /* sscanf() parsed the input successfully (by trimming extraneous + * spaces/zeros), feeding an invalid input to strftime(). */ + returnVal = SigV4_AwsIotDateToIso8601( pTestFormatFailure, sizeof( pTestFormatFailure ) - 1U, testBufferValid, SIGV4_TEST_BUFFER_SIZE ); + TEST_ASSERT_EQUAL( SigV4ISOFormattingError, returnVal ); +} From 60b48787cee2409bea6be887171fb8a2246c1825 Mon Sep 17 00:00:00 2001 From: sukhmanm Date: Tue, 6 Apr 2021 07:01:15 -0400 Subject: [PATCH 14/15] Requirement updates --- source/include/sigv4.h | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/source/include/sigv4.h b/source/include/sigv4.h index 13c89963..d015ff08 100644 --- a/source/include/sigv4.h +++ b/source/include/sigv4.h @@ -394,22 +394,31 @@ SigV4Status_t SigV4_GenerateHTTPAuthorization( const SigV4Parameters_t * pParams * identical). For additional information on date handling, please see * https://docs.aws.amazon.com/general/latest/gr/sigv4-date-handling.html. * - * Formatting Overview: - * - The AWS IoT response date is of the form "YYYY-MM-DD'T'hh:mm:ss'Z'" (ex. - * "2018-01-18T09:18:06Z"). - * - The ISO8601-formatted date is of the form "YYYYMMDD'T'HHMMSS'Z'" (ex. - * "20180118T091806Z"). + * Acceptable Input Formats: + * - RFC 5322 (ex. "Thu, 18 Jan 2018 09:18:06 GMT"), the preferred format in + * HTTP 'Date' response headers. If using this format, the date parameter + * should match "***, DD 'MMM' YYYY hh:mm:ss GMT" exactly. + * - RFC 3339 (ex. "2018-01-18T09:18:06Z"), found occasionally in 'Date' and + * expiration headers. If using this format, the date parameter should match + * "YYYY-MM-DD'T'hh:mm:ss'Z'" exactly. * - * @param[in] pDate The date header (in [RFC3339 - * format](https://tools.ietf.org/html/rfc3339)), found in the HTTP response - * returned by AWS IoT. This value should use UTC (indicated by the "Z" - * character postfix, with no time-zone offset), and be 20 characters in length - * (excluding the null character). - * @param[in] dateLen The length of the pDate header value. Must be - * #SIGV4_EXPECTED_AWS_IOT_DATE_LEN, for valid input parameters. - * @param[out] pDateISO8601 The ISO8601 format compliant date. This buffer must - * be large enough to hold both the ISO8601-formatted date (16 characters) and - * the terminating null character (17 in total). + * Formatted Output: + * - The ISO8601-formatted date will be returned in the form + * "YYYYMMDD'T'HHMMSS'Z'" (ex. "20180118T091806Z"). + * + * @param[in] pDate The date header (in + * [RFC 3339](https://tools.ietf.org/html/rfc3339) or + * [RFC 5322](https://tools.ietf.org/html/rfc5322) formats). An acceptable date + * header can be found in the HTTP response returned by AWS IoT. This value + * should use UTC (with no time-zone offset), and be exactly 20 or 29 characters + * in length (excluding the null character), to comply with RFC 3339 and RFC + * 5322 formats, respectively. + * @param[in] dateLen The length of the pDate header value. Must be either + * #SIGV4_EXPECTED_LEN_RFC_3339 or #SIGV4_EXPECTED_LEN_RFC_5322, for valid input + * parameters. + * @param[out] pDateISO8601 The formatted ISO8601-compliant date. The date value + * written to this buffer will be exactly 16 characters in length, to comply + * with the ISO8601 standard required for SigV4 authentication. * @param[in] dateISO8601Len The length of buffer pDateISO8601. Must be at least * #SIGV4_ISO_STRING_LEN bytes, for valid input parameters. * From 7fba5327f3b4a878959a5ff7452d87cadcd3223b Mon Sep 17 00:00:00 2001 From: sukhmanm Date: Tue, 20 Apr 2021 14:10:04 -0400 Subject: [PATCH 15/15] Remove unit test file --- test/unit-test/sigv4_utest.c | 143 ----------------------------------- 1 file changed, 143 deletions(-) delete mode 100644 test/unit-test/sigv4_utest.c diff --git a/test/unit-test/sigv4_utest.c b/test/unit-test/sigv4_utest.c deleted file mode 100644 index 3ea0430f..00000000 --- a/test/unit-test/sigv4_utest.c +++ /dev/null @@ -1,143 +0,0 @@ -/* - * SigV4 Utility Library v1.0.0 - * Copyright (C) 2020 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. - */ - -#include - -#include "unity.h" - -/* Include paths for public enums, structures, and macros. */ -#include "sigv4.h" -#include "sigv4_internal.h" - -/* Size of valid output date buffer. */ -#define SIGV4_TEST_BUFFER_SIZE ( 17 ) - -/* Size of larger-than-required output date buffer. */ -#define SIGV4_TEST_BUFFER_SIZE_LONG ( 30 ) - -/* Size of an output date buffer of insufficient length. */ -#define SIGV4_TEST_BUFFER_SIZE_SHORT ( 16 ) - -/* Test valid date inputs. */ -static const char pTestDateExpected[] = "Wed, 18 Jan 2018 09:18:06 GMT"; /*"2018-01-18T09:18:06Z"; */ -static const char pTestDateLong[] = "2018-01-18T09:18:06Z00:00"; -static const char pOutputExpected[] = "20180118T091806Z"; - -/* Test invalid date inputs. */ -static const char pTestDateShort[] = "2018-01T09:18Z"; -static const char pTestParsingFailure[] = "2018-01-18X09:18:06Z"; -static const char pTestFormatFailure[] = " 018-01-18T09:18:06Z"; - -/* File-scoped global variables */ -static SigV4Status_t retCode = SigV4Success; -static char testBufferValid[ SIGV4_TEST_BUFFER_SIZE ] = { 0 }; -static char testBufferLong[ SIGV4_TEST_BUFFER_SIZE_LONG ] = { 0 }; -static char testBufferShort[ SIGV4_TEST_BUFFER_SIZE_SHORT ] = { 0 }; - - -/* ============================ UNITY FIXTURES ============================== */ -/* Called before each test method. */ -void setUp() -{ -} - -/* Called after each test method. */ -void tearDown() -{ - retCode = SigV4Success; - memset( &testBufferValid, 0, sizeof( testBufferValid ) ); - memset( &testBufferLong, 0, sizeof( testBufferLong ) ); - memset( &testBufferShort, 0, sizeof( testBufferShort ) ); -} - -/* Called at the beginning of the whole suite. */ -void suiteSetUp() -{ -} - -/* Called at the end of the whole suite. */ -int suiteTearDown( int numFailures ) -{ - return numFailures; -} -/* ==================== Testing SigV4_AwsIotDateToIso8601 =================== */ - -/** - * @brief Test the happy path with zero-initialized and adequately sized output - * buffers. - */ -void test_SigV4_AwsIotDateToIso8601_Happy_Path() -{ - SigV4Status_t returnVal = SigV4Success; - - returnVal = SigV4_AwsIotDateToIso8601( pTestDateExpected, SIGV4_EXPECTED_LEN_RFC_5322, testBufferValid, SIGV4_TEST_BUFFER_SIZE ); - - TEST_ASSERT_EQUAL( SigV4Success, returnVal ); - TEST_ASSERT_EQUAL_STRING( pOutputExpected, testBufferValid ); - - /* returnVal = SigV4_AwsIotDateToIso8601( pTestDateLong, sizeof( pTestDateLong ), testBufferValid, SIGV4_TEST_BUFFER_SIZE ); */ - - /* TEST_ASSERT_EQUAL( SigV4Success, returnVal ); */ - /* TEST_ASSERT_EQUAL_STRING( pOutputExpected, testBufferValid ); */ -} - -/** - * @brief Test NULL and invalid parameters, following order of else-if blocks in - * SigV4_AwsIotDateToIso8601(). - */ -void test_SigV4_AwsIotDateToIso8601_Invalid_Params() -{ - SigV4Status_t returnVal = SigV4Success; - - /* Test pDate == NULL */ - returnVal = SigV4_AwsIotDateToIso8601( NULL, SIGV4_EXPECTED_LEN_RFC_3339, testBufferValid, SIGV4_TEST_BUFFER_SIZE ); - TEST_ASSERT_EQUAL( SigV4InvalidParameter, returnVal ); - - /* Test pDateISO8601 == NULL */ - returnVal = SigV4_AwsIotDateToIso8601( pTestDateExpected, SIGV4_EXPECTED_LEN_RFC_5322, NULL, SIGV4_TEST_BUFFER_SIZE ); - TEST_ASSERT_EQUAL( SigV4InvalidParameter, returnVal ); - - /* Test dateLen < SIGV4_EXPECTED_LEN_RFC_3339 */ - returnVal = SigV4_AwsIotDateToIso8601( pTestDateShort, sizeof( pTestDateShort ), testBufferValid, SIGV4_TEST_BUFFER_SIZE ); - TEST_ASSERT_EQUAL( SigV4InvalidParameter, returnVal ); - - /* Test dateISO8601Len < SIGV4_ISO_STRING_LEN + 1 */ - returnVal = SigV4_AwsIotDateToIso8601( pTestDateExpected, sizeof( pTestDateExpected ), testBufferShort, SIGV4_TEST_BUFFER_SIZE_SHORT ); - TEST_ASSERT_EQUAL( SigV4InvalidParameter, returnVal ); -} - -/** - * @brief Test invalid input date formats. - */ -void test_SigV4_AwsIotDateToIso8601_Formatting_Error() -{ - SigV4Status_t returnVal = SigV4Success; - - /* sscanf() failed to match input date to expected format string. */ - returnVal = SigV4_AwsIotDateToIso8601( pTestParsingFailure, sizeof( pTestParsingFailure ) - 1U, testBufferValid, SIGV4_TEST_BUFFER_SIZE ); - TEST_ASSERT_EQUAL( SigV4ISOFormattingError, returnVal ); - - /* sscanf() parsed the input successfully (by trimming extraneous - * spaces/zeros), feeding an invalid input to strftime(). */ - returnVal = SigV4_AwsIotDateToIso8601( pTestFormatFailure, sizeof( pTestFormatFailure ) - 1U, testBufferValid, SIGV4_TEST_BUFFER_SIZE ); - TEST_ASSERT_EQUAL( SigV4ISOFormattingError, returnVal ); -}