/*
 * SigV4 Library v1.2.0
 * Copyright (C) 2021 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * SPDX-License-Identifier: MIT
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

/**
 * @file sigv4.c
 * @brief Implements the user-facing functions in sigv4.h
 */

#include <assert.h>
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>

#include "sigv4.h"
#include "sigv4_internal.h"
#include "sigv4_quicksort.h"

/*-----------------------------------------------------------*/

#if ( SIGV4_USE_CANONICAL_SUPPORT == 1 )

/**
 * @brief Normalize a URI string according to RFC 3986 and fill destination
 * buffer with the formatted string.
 *
 * @param[in] pUri The URI string to encode.
 * @param[in] uriLen Length of pUri.
 * @param[out] pCanonicalURI The resulting canonicalized URI.
 * @param[in, out] canonicalURILen input: the length of pCanonicalURI,
 * output: the length of the generated canonical URI.
 * @param[in] encodeSlash Option to indicate if slashes should be encoded.
 * @param[in] doubleEncodeEquals Option to indicate if equals should be double-encoded.
 */
    static SigV4Status_t encodeURI( const char * pUri,
                                    size_t uriLen,
                                    char * pCanonicalURI,
                                    size_t * canonicalURILen,
                                    bool encodeSlash,
                                    bool doubleEncodeEquals );

/**
 * @brief Canonicalize the full URI path. The input URI starts after the
 * HTTP host and ends at the question mark character ("?") that begins the
 * query string parameters (if any). Example: folder/subfolder/item.txt"
 *
 * @param[in] pUri HTTP request URI, also known that the request absolute
 * path.
 * @param[in] uriLen Length of pUri.
 * @param[in] encodeTwice Service-dependent option to indicate whether
 * encoding should be done twice. For example, S3 requires that the
 * URI is encoded only once, while other services encode twice.
 * @param[in, out] pCanonicalRequest Struct to maintain intermediary buffer
 * and state of canonicalization.
 */
    static SigV4Status_t generateCanonicalURI( const char * pUri,
                                               size_t uriLen,
                                               bool encodeTwice,
                                               CanonicalContext_t * pCanonicalRequest );

/**
 * @brief Canonicalize the query string HTTP URL, beginning (but not
 * including) at the "?" character. Does not include "/".
 *
 * @param[in] pQuery HTTP request query.
 * @param[in] queryLen Length of pQuery.
 * @param[in, out] pCanonicalContext Struct to maintain intermediary buffer
 * and state of canonicalization.
 */
    static SigV4Status_t generateCanonicalQuery( const char * pQuery,
                                                 size_t queryLen,
                                                 CanonicalContext_t * pCanonicalContext );

/**
 * @brief Determine if a character can be written without needing URI encoding when generating Canonical Request.
 *
 * @param[in] c The character to evaluate.
 * @param[in] encodeSlash Whether slashes may be encoded.
 *
 * @return `true` if the character does not need encoding, `false` if it does.
 */
    static bool isAllowedChar( char c,
                               bool encodeSlash );

/**
 * @brief Compare two SigV4 data structures lexicographically, without case-sensitivity.
 *
 * @param[in] pFirstVal SigV4 key value data structure to sort.
 * @param[in] pSecondVal SigV4 key value data structure to sort.
 *
 * @return Returns a value less than 0 if @pFirstVal < @pSecondVal, or
 * a value greater than 0 if @pSecondVal < @pFirstVal. 0 is never returned in
 * order to provide stability to quickSort() calls.
 */
    static int32_t cmpHeaderField( const void * pFirstVal,
                                   const void * pSecondVal );

#endif /* #if (SIGV4_USE_CANONICAL_SUPPORT == 1) */

/**
 * @brief Converts an integer value to its ASCII representation, 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] bufferLen Width of value to write (padded with leading 0s if
 * necessary).
 */
static void intToAscii( int32_t value,
                        char ** pBuffer,
                        size_t bufferLen );

/**
 * @brief Extract all header key-value pairs from the passed headers data and add them
 * to the canonical request.
 *
 * @param[in] pHeaders HTTP headers to canonicalize.
 * @param[in] headersLen Length of HTTP headers to canonicalize.
 * @param[in] flags Flag to indicate if headers are already
 * in the canonical form.
 * @param[out] canonicalRequest Struct to maintain intermediary buffer
 * and state of canonicalization.
 * @param[out] pSignedHeaders The starting location of the signed headers.
 * @param[out] pSignedHeadersLen The length of the signed headers.
 *
 * @return Following statuses will be returned by the function:
 * #SigV4Success if headers are successfully added to the canonical request.
 * #SigV4InsufficientMemory if canonical request buffer cannot accommodate the header.
 * #SigV4InvalidParameter if HTTP headers are invalid.
 * #SigV4MaxHeaderPairCountExceeded if number of headers that needs to be canonicalized
 * exceed the SIGV4_MAX_HTTP_HEADER_COUNT macro defined in the config file.
 */
static SigV4Status_t generateCanonicalAndSignedHeaders( const char * pHeaders,
                                                        size_t headersLen,
                                                        uint32_t flags,
                                                        CanonicalContext_t * canonicalRequest,
                                                        char ** pSignedHeaders,
                                                        size_t * pSignedHeadersLen );

/**
 * @brief Append Signed Headers to the Canonical Request buffer.
 *
 * @param[in] headerCount Number of headers which needs to be appended.
 * @param[in] flags Flag to indicate if headers are already
 * in the canonical form.
 * @param[in,out] canonicalRequest Struct to maintain intermediary buffer
 * and state of canonicalization.
 * @param[out] pSignedHeaders The starting location of the signed headers.
 * @param[out] pSignedHeadersLen The length of the signed headers.
 */
static SigV4Status_t appendSignedHeaders( size_t headerCount,
                                          uint32_t flags,
                                          CanonicalContext_t * canonicalRequest,
                                          char ** pSignedHeaders,
                                          size_t * pSignedHeadersLen );

/**
 * @brief Canonicalize headers and append it to the Canonical Request buffer.
 *
 * @param[in] headerCount Number of headers which needs to be appended.
 * @param[in] flags Flag to indicate if headers are already
 * in the canonical form.
 * @param[in,out] canonicalRequest Struct to maintain intermediary buffer
 * and state of canonicalization.
 *
 * @return Following statuses will be returned by the function:
 * #SigV4Success if headers are successfully added to the canonical request.
 * #SigV4InsufficientMemory if canonical request buffer cannot accommodate the header.
 */
static SigV4Status_t appendCanonicalizedHeaders( size_t headerCount,
                                                 uint32_t flags,
                                                 CanonicalContext_t * canonicalRequest );

/**
 * @brief Store the location of HTTP request hashed payload in the HTTP request.
 *
 * @param[in] headerIndex Index of request Header in the list of parsed headers.
 * @param[in] pAmzSHA256Header Literal for x-amz-content-sha256 header in HTTP request.
 * @param[in] amzSHA256HeaderLen Length of @p pAmzSHA256Header.
 * @param[in,out] pCanonicalRequest Struct to maintain intermediary buffer
 * and state of canonicalization.
 */
static void storeHashedPayloadLocation( size_t headerIndex,
                                        const char * pAmzSHA256Header,
                                        size_t amzSHA256HeaderLen,
                                        CanonicalContext_t * pCanonicalRequest );

/**
 * @brief Parse each header key and value pair from HTTP headers.
 *
 * @param[in] pHeaders HTTP headers to parse.
 * @param[in] headersDataLen Length of HTTP headers to parse.
 * @param[in] flags Flag to indicate if headers are already
 * in the canonical form.
 * @param[out] headerCount Count of key-value pairs parsed from pData.
 * @param[out] canonicalRequest Struct to maintain intermediary buffer
 * and state of canonicalization.
 *
 * @return Following statuses will be returned by the function:
 * #SigV4Success if header key or value is successfully added to the canonical request.
 * #SigV4InsufficientMemory if canonical request buffer cannot accommodate the header.
 * #SigV4MaxHeaderPairCountExceeded if number of key-value entries in the headers data
 * exceeds the SIGV4_MAX_HTTP_HEADER_COUNT macro defined in the config file.
 */
static SigV4Status_t parseHeaderKeyValueEntries( const char * pHeaders,
                                                 size_t headersDataLen,
                                                 uint32_t flags,
                                                 size_t * headerCount,
                                                 CanonicalContext_t * canonicalRequest );

/**
 * @brief Copy header key or header value to the Canonical Request buffer.
 *
 * @param[in] pData Header Key or value to be copied to the canonical request.
 * @param[in] dataLen Length of Header Key or value.
 * @param[in] flags Flag to indicate if headers are already
 * in the canonical form.
 * @param[in] separator Character separating the multiple key-value pairs or key and values.
 * @param[in,out] canonicalRequest Struct to maintain intermediary buffer
 * and state of canonicalization.
 *
 * @return Following statuses will be returned by the function:
 * #SigV4Success if the headers are successfully added to the canonical request.
 * #SigV4InsufficientMemory if canonical request buffer cannot accommodate the header.
 */
static SigV4Status_t copyHeaderStringToCanonicalBuffer( const char * pData,
                                                        size_t dataLen,
                                                        uint32_t flags,
                                                        char separator,
                                                        CanonicalContext_t * canonicalRequest );

/**
 * @brief Helper function to determine whether a header string character represents a space
 * that can be trimmed when creating "Canonical Headers".
 * All leading and trailing spaces in the header strings need to be trimmed. Also, sequential spaces
 * in the header value need to be trimmed to a single space.
 *
 * Example of modifying header field for Canonical Headers:
 * Actual header pair:                 |      Modifier header pair
 * My-Header2:    "a   b   c"  \n      |      my-header2:"a b c"\n
 *
 * @param[in] value Header value or key string to be trimmed.
 * @param[in] index Index of current character.
 * @param[in] valLen Length of the string.
 * @param[in] trimmedLength Current length of trimmed string.
 *
 * @return `true` if the character needs to be trimmed, else `false`.
 */
static bool isTrimmableSpace( const char * value,
                              size_t index,
                              size_t valLen,
                              size_t trimmedLength );

/**
 * @brief Generate the canonical request but excluding the canonical headers
 * and anything that goes after it. Write it onto @p pSignedHeaders and update
 * it to point to the next location to write the rest of the canonical request.
 *
 * @param[in] pParams The application-defined parameters used to
 * generate the canonical request.
 * @param[in] pCanonicalContext The context of the canonical request.
 * @param[in,out] pSignedHeaders The location to start writing the canonical request and
 * becomes the location to write the rest of it when this function returns.
 * @param[in,out] pSignedHeadersLen The amount of buffer available and becomes the number
 * of bytes actually written when this function returns.
 * @return SigV4InsufficientMemory if the length of the canonical request output
 * buffer cannot fit the actual request before the headers, #SigV4Success otherwise.
 */
static SigV4Status_t generateCanonicalRequestUntilHeaders( const SigV4Parameters_t * pParams,
                                                           CanonicalContext_t * pCanonicalContext,
                                                           char ** pSignedHeaders,
                                                           size_t * pSignedHeadersLen );

/**
 * @brief Generates the prefix of the Authorization header of the format:
 * "<algorithm> Credential=<access key ID>/<credential scope>, SignedHeaders=<SignedHeaders>, Signature="
 *
 * @param[in] pParams The application-defined parameters used to
 * generate the canonical request.
 * @param[in] pAlgorithm The signing algorithm used for SigV4 authentication.
 * @param[in] algorithmLen The length of @p pAlgorithm.
 * @param[in] pSignedHeaders The signed headers of the SigV4 request.
 * @param[in] signedHeadersLen The length of @p pSignedHeaders.
 * @param[in,out] pAuthBuf The authorization buffer where to write the prefix.
 * Pointer is updated with the next location to write the value of the signature.
 * @param[in, out] pAuthPrefixLen On input, it should contain the total length of @p pAuthBuf.
 * On output, this will be filled with the length of the Authorization header, if
 * operation is successful.
 *
 * @return #SigV4InsufficientMemory if the length of the authorization buffer, @p pAuthBuf
 * is insufficient to store the entire authorization header value (i.e. Prefix + HexEncoded Signature);
 * otherwise #SigV4Success.
 */
static SigV4Status_t generateAuthorizationValuePrefix( const SigV4Parameters_t * pParams,
                                                       const char * pAlgorithm,
                                                       size_t algorithmLen,
                                                       const char * pSignedHeaders,
                                                       size_t signedHeadersLen,
                                                       char * pAuthBuf,
                                                       size_t * pAuthPrefixLen );

/**
 * @brief Write a line in the canonical request.
 * @note Used whenever there are components of the request that
 * are already canonicalized.
 *
 * @param[in] pLine The line to write to the canonical request.
 * @param[in] lineLen The length of @p pLine
 * @param[in,out] pCanonicalContext The canonical context where
 * the line should be written.
 * @return SigV4InsufficientMemory if the length of the canonical request
 * buffer cannot write the desired line, #SigV4Success otherwise.
 */
static SigV4Status_t writeLineToCanonicalRequest( const char * pLine,
                                                  size_t lineLen,
                                                  CanonicalContext_t * pCanonicalContext );

/**
 * @brief Set a query parameter key in the canonical request.
 *
 * @param[in] currentParameter The index of the query key to set
 * @param[in] pKey The pointer to the query key
 * @param[in] keyLen The length of @p pKey
 * @param[in,out] pCanonicalRequest The canonical request containing the
 * query parameter array of keys and values
 */
static void setQueryParameterKey( size_t currentParameter,
                                  const char * pKey,
                                  size_t keyLen,
                                  CanonicalContext_t * pCanonicalRequest );

/**
 * @brief Set a query parameter value in the canonical request.
 *
 * @param[in] currentParameter The index of the query value to set
 * @param[in] pValue The pointer to the query value
 * @param[in] valueLen The length of @p pValue
 * @param[in,out] pCanonicalRequest The canonical request containing the
 * query parameter array of keys and values
 */
static void setQueryParameterValue( size_t currentParameter,
                                    const char * pValue,
                                    size_t valueLen,
                                    CanonicalContext_t * pCanonicalRequest );

/**
 * @brief Convert the character to lowercase.
 *
 * @param[in] inputChar character to be lowercased.
 *
 * @return Input character converted to lowercase.
 */
static char lowercaseCharacter( char inputChar );

/**
 * @brief Write the HTTP request payload hash to the canonical request.
 *
 * @param[in] pParams The application-defined parameters used for writing
 * the payload hash.
 * @param[out] pCanonicalContext The canonical context where the payload
 * hash should be written.
 *
 * @return SigV4InsufficientMemory if the length of the canonical request
 * buffer cannot write the desired line, #SigV4Success otherwise.
 */
static SigV4Status_t writePayloadHashToCanonicalRequest( const SigV4Parameters_t * pParams,
                                                         CanonicalContext_t * pCanonicalContext );

/**
 * @brief Generates the key for the HMAC operation.
 *
 * @note This function can be called multiple times before calling
 * #hmacIntermediate. Appending multiple substrings, then calling #hmacAddKey
 * on the appended string is also equivalent to calling #hmacAddKey on
 * each individual substring.
 * @note This function accepts a const char * so that string literals
 * can be passed in.
 *
 * @param[in] pHmacContext The context used for HMAC calculation.
 * @param[in] pKey The key used as input for HMAC calculation.
 * @param[in] keyLen The length of @p pKey.
 * @param[in] isKeyPrefix Flag to indicate whether the passed key is
 * prefix of a complete key for an HMAC operation. If this is a prefix,
 * then it will be stored in cache for use with remaining part of the
 * key that will be provided in a subsequent call to @ref hmacAddKey.
 *
 * @return Zero on success, all other return values are failures.
 */
static int32_t hmacAddKey( HmacContext_t * pHmacContext,
                           const char * pKey,
                           size_t keyLen,
                           bool isKeyPrefix );

/**
 * @brief Generates the intermediate hash output in the HMAC signing process.
 * This represents the H( K' ^ i_pad || message ) part of the HMAC algorithm.
 *
 * @note This MUST be ONLY called after #hmacAddKey; otherwise results in
 * undefined behavior. Likewise, one SHOULD NOT call #hmacAddKey after
 * calling #hmacIntermediate. One must call #hmacFinal first before calling
 * #hmacAddKey again.
 *
 * @param[in] pHmacContext The context used for HMAC calculation.
 * @param[in] pData The data used as input for HMAC calculation.
 * @param[in] dataLen The length of @p pData.
 *
 * @return Zero on success, all other return values are failures.
 */
static int32_t hmacIntermediate( HmacContext_t * pHmacContext,
                                 const char * pData,
                                 size_t dataLen );

/**
 * @brief Generates the end output of the HMAC algorithm.
 *
 * This represents the second hash operation in the HMAC algorithm:
 *   H( K' ^ o_pad || Intermediate Hash Output )
 * where the Intermediate Hash Output is generated from the call
 * to @ref hmacIntermediate.
 *
 * @param[in] pHmacContext The context used for HMAC calculation.
 * @param[out] pMac The buffer onto which to write the HMAC digest.
 * @param[in] macLen The length of @p pMac.
 *
 * @return Zero on success, all other return values are failures.
 */
static int32_t hmacFinal( HmacContext_t * pHmacContext,
                          char * pMac,
                          size_t macLen );

/**
 * @brief Generates the complete HMAC digest given a key and value, then write
 * the digest in the provided output buffer.
 *
 * @param[in] pHmacContext The context used for the current HMAC calculation.
 * @param[in] pKey The key passed as input to the HMAC function.
 * @param[in] keyLen The length of @p pKey.
 * @param[in] pData The data passed as input to the HMAC function.
 * @param[in] dataLen The length of @p pData.
 * @param[out] pOutput The buffer onto which to write the HMAC digest.
 * @param[out] outputLen The length of @p pOutput and must be greater
 * than pCryptoInterface->hashDigestLen for this function to succeed.
 * @return Zero on success, all other return values are failures.
 */
static int32_t completeHmac( HmacContext_t * pHmacContext,
                             const char * pKey,
                             size_t keyLen,
                             const char * pData,
                             size_t dataLen,
                             char * pOutput,
                             size_t outputLen );

/**
 * @brief Generates the complete hash of an input string, then write
 * the digest in the provided output buffer.
 * @note Unlike #completeHashAndHexEncode, this function will not
 * encode the hash and will simply output the bytes written by the
 * hash function.
 *
 * @param[in] pInput The data passed as input to the hash function.
 * @param[in] inputLen The length of @p pInput.
 * @param[out] pOutput The buffer onto which to write the hash.
 * @param[out] outputLen The length of @p pOutput and must be greater
 * than pCryptoInterface->hashDigestLen for this function to succeed.
 * @param[in] pCryptoInterface The interface used to call hash functions.
 * @return Zero on success, all other return values are failures.
 */
static int32_t completeHash( const uint8_t * pInput,
                             size_t inputLen,
                             uint8_t * pOutput,
                             size_t outputLen,
                             const SigV4CryptoInterface_t * pCryptoInterface );

/**
 * @brief Generate the complete hash of an input string, then write
 * the digest in an intermediary buffer before hex encoding and
 * writing it onto @p pOutput.
 *
 * @param[in] pInput The data passed as input to the hash function.
 * @param[in] inputLen The length of @p pInput.
 * @param[out] pOutput The buffer onto which to write the hex-encoded hash.
 * @param[out] pOutputLen The length of @p pOutput and must be greater
 * than pCryptoInterface->hashDigestLen * 2 for this function to succeed.
 * @param[in] pCryptoInterface The interface used to call hash functions.
 * @return Zero on success, all other return values are failures.
 */
static SigV4Status_t completeHashAndHexEncode( const char * pInput,
                                               size_t inputLen,
                                               char * pOutput,
                                               size_t * pOutputLen,
                                               const SigV4CryptoInterface_t * pCryptoInterface );

/**
 * @brief Generate the prefix of the string to sign containing the
 * algorithm and date then write it onto @p pBufStart.
 * @note This function assumes that enough bytes remain in @p pBufStart in
 * order to write the algorithm and date.
 *
 * @param[in] pBufStart The starting location of the buffer to write the string
 * to sign.
 * @param[in] pAlgorithm The algorithm used for generating the SigV4 signature.
 * @param[in] algorithmLen The length of @p pAlgorithm.
 * @param[in] pDateIso8601 The date used as part of the string to sign.
 * @return The number of bytes written to @p pBufStart.
 */
static size_t writeStringToSignPrefix( char * pBufStart,
                                       const char * pAlgorithm,
                                       size_t algorithmLen,
                                       const char * pDateIso8601 );

/**
 * @brief Generate the string to sign and write it onto a #SigV4String_t.
 *
 * @param[in] pParams The application-defined parameters used to
 * generate the string to sign.
 * @param[in] pAlgorithm The algorithm used for generating the SigV4 signature.
 * @param[in] algorithmLen The length of @p pAlgorithm.
 * @param[in,out] pCanonicalContext The context of the canonical request.
 * @return SigV4InsufficientMemory if the length of the canonical request output
 * buffer cannot fit the string to sign, #SigV4Success otherwise.
 */
static SigV4Status_t writeStringToSign( const SigV4Parameters_t * pParams,
                                        const char * pAlgorithm,
                                        size_t algorithmLen,
                                        CanonicalContext_t * pCanonicalContext );

/**
 * @brief Generate the signing key and write it onto a #SigV4String_t.
 *
 * @param[in] pSigV4Params The application-defined parameters used to
 * generate the signing key.
 * @param[in] pHmacContext The context used for the current HMAC calculation.
 * @param[out] pSigningKey The #SigV4String_t onto which the signing key will be written.
 * @param[in,out] pBytesRemaining The number of bytes remaining in the canonical buffer.
 * @return SigV4InsufficientMemory if the length of @p pSigningKey was insufficient to
 * fit the actual signing key, #SigV4Success otherwise.
 */
static SigV4Status_t generateSigningKey( const SigV4Parameters_t * pSigV4Params,
                                         HmacContext_t * pHmacContext,
                                         SigV4String_t * pSigningKey,
                                         size_t * pBytesRemaining );

/**
 * @brief Format the credential scope for the authorization header.
 * Credential scope includes the access key ID, date, region, and service parameters, and
 * ends with "aws4_request" terminator.
 *
 * @param[in] pSigV4Params The application parameters defining the credential's scope.
 * @param[in, out] pCredScope The credential scope in the SigV4 format.
 */
static void generateCredentialScope( const SigV4Parameters_t * pSigV4Params,
                                     SigV4String_t * pCredScope );

/**
 * @brief Check if the date represents a valid leap year day.
 *
 * @param[in] pDateElements The date representation to be verified.
 *
 * @return #SigV4Success if the date corresponds to a valid leap year,
 * #SigV4ISOFormattingError otherwise.
 */
static SigV4Status_t checkLeap( const SigV4DateTime_t * pDateElements );

/**
 * @brief Verify the date stored in a SigV4DateTime_t date representation.
 *
 * @param[in] pDateElements The date representation to be verified.
 *
 * @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( const SigV4DateTime_t * pDateElements );

/**
 * @brief Append the value of a date element to the internal date representation
 * structure.
 *
 * @param[in] formatChar The specifier identifying the struct member to fill.
 * @param[in] result The value to assign to the specified struct member.
 * @param[out] pDateElements The date representation structure to modify.
 */
static void 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[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 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 read, 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 all format specifiers were matched successfully,
 * #SigV4ISOFormattingError otherwise.
 */
static SigV4Status_t parseDate( const char * pDate,
                                size_t dateLen,
                                const char * pFormat,
                                size_t formatLen,
                                SigV4DateTime_t * pDateElements );

/**
 * @brief Verify input parameters to the SigV4_GenerateHTTPAuthorization API.
 *
 * @param[in] pParams Complete SigV4 configurations passed by application.
 * @param[in] pAuthBuf The user-supplied buffer for filling Authorization Header.
 * @param[in] authBufLen The user-supplied size value of @p pAuthBuf buffer.
 * @param[in] pSignature The user-supplied pointer memory to store starting location of
 * Signature in Authorization Buffer.
 * @param[in] signatureLen The user-supplied pointer to store length of Signature.
 *
 * @return #SigV4Success if successful, #SigV4InvalidParameter otherwise.
 */
static SigV4Status_t verifyParamsToGenerateAuthHeaderApi( const SigV4Parameters_t * pParams,
                                                          const char * pAuthBuf,
                                                          const size_t * authBufLen,
                                                          char * const * pSignature,
                                                          const size_t * signatureLen );

/**
 * @brief Assign default arguments based on parameters set in @p pParams.
 *
 * @param[in] pParams Complete SigV4 configurations passed by application.
 * @param[out] pAlgorithm The algorithm used for SigV4 authentication.
 * @param[out] algorithmLen The length of @p pAlgorithm.
 */
static void assignDefaultArguments( const SigV4Parameters_t * pParams,
                                    const char ** pAlgorithm,
                                    size_t * algorithmLen );

/**
 * @brief Hex digest of provided string parameter.
 *
 * @param[in] pInputStr String to encode.
 * @param[out] pHexOutput Hex representation of @p pInputStr.
 *
 * @return #SigV4Success if successful, #SigV4InsufficientMemory otherwise.
 */
static SigV4Status_t lowercaseHexEncode( const SigV4String_t * pInputStr,
                                         SigV4String_t * pHexOutput );

/**
 * @brief Calculate number of bytes needed for the credential scope.
 * @note This does not include the linefeed character.
 *
 * @param[in] pSigV4Params SigV4 configurations passed by application.
 *
 * @return Number of bytes needed for credential scope.
 */
static size_t sizeNeededForCredentialScope( const SigV4Parameters_t * pSigV4Params );

/**
 * @brief Copy a string into a char * buffer.
 * @note This function can be used to copy a string literal without
 * MISRA warnings.
 *
 * @note This function assumes the destination buffer is large enough to hold
 * the string to copy, so will always write @p length bytes.
 *
 * @param[in] destination The buffer to write.
 * @param[in] source String to copy.
 * @param[in] length Number of characters to copy.
 *
 * @return @p length The number of characters written from @p source into
 * @p destination.
 */
static size_t copyString( char * destination,
                          const char * source,
                          size_t length );

/*-----------------------------------------------------------*/

static void intToAscii( int32_t value,
                        char ** pBuffer,
                        size_t bufferLen )
{
    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 > 0U )
    {
        lenRemaining--;
        ( *pBuffer )[ lenRemaining ] = ( char ) ( ( currentVal % 10 ) + '0' );
        currentVal /= 10;
    }

    /* Move pointer to follow last written character. */
    *pBuffer += bufferLen;
}

/*-----------------------------------------------------------*/

static SigV4Status_t checkLeap( const SigV4DateTime_t * pDateElements )
{
    SigV4Status_t returnStatus = SigV4ISOFormattingError;

    assert( pDateElements != NULL );

    /* If the date represents a leap day, verify that the leap year is valid. */
    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 ) );
        }
        else
        {
            returnStatus = SigV4Success;
        }
    }

    return returnStatus;
}

/*-----------------------------------------------------------*/

static SigV4Status_t validateDateTime( const SigV4DateTime_t * pDateElements )
{
    SigV4Status_t returnStatus = SigV4Success;
    const 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 %ld or greater, received: %ld",
                    ( long int ) YEAR_MIN,
                    ( long int ) pDateElements->tm_year ) );
        returnStatus = SigV4ISOFormattingError;
    }

    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 ) pDateElements->tm_mon ) );
        returnStatus = SigV4ISOFormattingError;
    }

    /* 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 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 %ld, received: %ld",
                        ( long int ) daysPerMonth[ pDateElements->tm_mon - 1 ],
                        ( long int ) pDateElements->tm_mday ) );
        }
    }

    /* SigV4DateTime_t values are asserted to be non-negative before they are
     * assigned in function addToDate(). Therefore, we only verify logical upper
     * bounds for the following values. */
    if( pDateElements->tm_hour > 23 )
    {
        LogError( ( "Invalid 'hour' value parsed from date string. "
                    "Expected an integer between 0 and 23, received: %ld",
                    ( long int ) pDateElements->tm_hour ) );
        returnStatus = SigV4ISOFormattingError;
    }

    if( pDateElements->tm_min > 59 )
    {
        LogError( ( "Invalid 'minute' value parsed from date string. "
                    "Expected an integer between 0 and 59, received: %ld",
                    ( long int ) pDateElements->tm_min ) );
        returnStatus = SigV4ISOFormattingError;
    }

    /* An upper limit of 60 accounts for the occasional leap second UTC
     * adjustment. */
    if( pDateElements->tm_sec > 60 )
    {
        LogError( ( "Invalid 'second' value parsed from date string. "
                    "Expected an integer between 0 and 60, received: %ld",
                    ( long int ) pDateElements->tm_sec ) );
        returnStatus = SigV4ISOFormattingError;
    }

    return returnStatus;
}

/*-----------------------------------------------------------*/

static void addToDate( const char formatChar,
                       int32_t result,
                       SigV4DateTime_t * pDateElements )
{
    assert( pDateElements != NULL );
    assert( result >= 0 );

    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;

        default:

            /* Do not assign values for skipped characters ('*'), or
             * unrecognized format specifiers. */
            break;
    }
}

/*-----------------------------------------------------------*/

static SigV4Status_t scanValue( const char * pDate,
                                const char formatChar,
                                size_t readLoc,
                                size_t lenToRead,
                                SigV4DateTime_t * pDateElements )
{
    SigV4Status_t returnStatus = SigV4InvalidParameter;
    const char * const pMonthNames[] = MONTH_NAMES;
    const char * pLoc = pDate + readLoc;
    size_t remainingLenToRead = lenToRead;
    int32_t result = 0;

    assert( pDate != NULL );
    assert( pDateElements != NULL );

    if( formatChar == '*' )
    {
        remainingLenToRead = 0U;
    }

    /* Determine if month value is non-numeric. */
    if( ( formatChar == 'M' ) && ( remainingLenToRead == MONTH_ASCII_LEN ) )
    {
        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;
        }

        remainingLenToRead = 0U;
    }

    /* Interpret integer value of numeric representation. */
    while( ( remainingLenToRead > 0U ) && ( *pLoc >= '0' ) && ( *pLoc <= '9' ) )
    {
        result = ( result * 10 ) + ( int32_t ) ( *pLoc - '0' );
        remainingLenToRead--;
        pLoc += 1;
    }

    if( remainingLenToRead != 0U )
    {
        LogError( ( "Parsing Error: Expected numerical string of type '%%%d%c', "
                    "but received '%.*s'.",
                    ( int ) lenToRead,
                    formatChar,
                    ( int ) lenToRead,
                    pLoc ) );
        returnStatus = SigV4ISOFormattingError;
    }

    if( returnStatus != SigV4ISOFormattingError )
    {
        addToDate( formatChar,
                   result,
                   pDateElements );
    }

    return returnStatus;
}

/*-----------------------------------------------------------*/

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, formatIndex = 0U;
    uint8_t lenToRead = 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 = ( ( uint8_t ) pFormat[ formatIndex ] - ( uint8_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;
}

/*-----------------------------------------------------------*/

static SigV4Status_t lowercaseHexEncode( const SigV4String_t * pInputStr,
                                         SigV4String_t * pHexOutput )
{
    SigV4Status_t returnStatus = SigV4Success;
    static const char digitArr[] = "0123456789abcdef";
    char * hex = NULL;
    size_t i = 0U;
    const uint8_t * bytes;

    assert( pInputStr != NULL );
    assert( pHexOutput != NULL );
    assert( pInputStr->pData != NULL );
    assert( pHexOutput->pData != NULL );

    hex = pHexOutput->pData;
    bytes = ( const uint8_t * ) pInputStr->pData;

    /* Hex string notification of binary data takes twice the size. */
    if( pHexOutput->dataLen < ( pInputStr->dataLen * 2U ) )
    {
        returnStatus = SigV4InsufficientMemory;
        LOG_INSUFFICIENT_MEMORY_ERROR( "hex encode",
                                       ( pInputStr->dataLen * 2U ) - pHexOutput->dataLen );
    }
    else
    {
        for( i = 0; i < pInputStr->dataLen; i++ )
        {
            *hex = digitArr[ ( bytes[ i ] & 0xF0U ) >> 4 ];
            hex++;
            *hex = digitArr[ ( bytes[ i ] & 0x0FU ) ];
            hex++;
        }

        pHexOutput->dataLen = pInputStr->dataLen * 2U;
    }

    return returnStatus;
}

/*-----------------------------------------------------------*/

static size_t sizeNeededForCredentialScope( const SigV4Parameters_t * pSigV4Params )
{
    assert( pSigV4Params != NULL );
    return ISO_DATE_SCOPE_LEN +                                        \
           CREDENTIAL_SCOPE_SEPARATOR_LEN + pSigV4Params->regionLen +  \
           CREDENTIAL_SCOPE_SEPARATOR_LEN + pSigV4Params->serviceLen + \
           CREDENTIAL_SCOPE_SEPARATOR_LEN + CREDENTIAL_SCOPE_TERMINATOR_LEN;
}

/*-----------------------------------------------------------*/

static size_t copyString( char * destination,
                          const char * source,
                          size_t length )
{
    ( void ) memcpy( destination, source, length );
    return length;
}

/*-----------------------------------------------------------*/

static void generateCredentialScope( const SigV4Parameters_t * pSigV4Params,
                                     SigV4String_t * pCredScope )
{
    char * pBufWrite = NULL;
    size_t credScopeLen = sizeNeededForCredentialScope( pSigV4Params );

    assert( pSigV4Params != NULL );
    assert( pSigV4Params->pCredentials != NULL );
    assert( pSigV4Params->pRegion != NULL );
    assert( pSigV4Params->pService != NULL );
    assert( pCredScope != NULL );
    assert( pCredScope->pData != NULL );
    assert( pCredScope->dataLen >= credScopeLen );

    pBufWrite = pCredScope->pData;

    /* Each concatenated component is separated by a '/' character. */
    /* Concatenate first 8 characters from the provided ISO 8601 string (YYYYMMDD). */
    ( void ) memcpy( pBufWrite, pSigV4Params->pDateIso8601, ISO_DATE_SCOPE_LEN );
    pBufWrite += ISO_DATE_SCOPE_LEN;

    *pBufWrite = CREDENTIAL_SCOPE_SEPARATOR;
    pBufWrite += CREDENTIAL_SCOPE_SEPARATOR_LEN;

    /* Concatenate AWS region. */
    ( void ) memcpy( pBufWrite, pSigV4Params->pRegion, pSigV4Params->regionLen );
    pBufWrite += pSigV4Params->regionLen;

    *pBufWrite = CREDENTIAL_SCOPE_SEPARATOR;
    pBufWrite += CREDENTIAL_SCOPE_SEPARATOR_LEN;

    /* Concatenate AWS service. */
    ( void ) memcpy( pBufWrite, pSigV4Params->pService, pSigV4Params->serviceLen );
    pBufWrite += pSigV4Params->serviceLen;

    *pBufWrite = CREDENTIAL_SCOPE_SEPARATOR;
    pBufWrite += CREDENTIAL_SCOPE_SEPARATOR_LEN;

    /* Concatenate terminator. */
    pBufWrite += copyString( pBufWrite, CREDENTIAL_SCOPE_TERMINATOR, CREDENTIAL_SCOPE_TERMINATOR_LEN );

    /* Verify that the number of bytes written match the sizeNeededForCredentialScope()
     * utility function for calculating size of credential scope. */
    assert( ( size_t ) ( pBufWrite - pCredScope->pData ) == credScopeLen );

    pCredScope->dataLen = credScopeLen;
}

/*-----------------------------------------------------------*/

#if ( SIGV4_USE_CANONICAL_SUPPORT == 1 )

    static int32_t cmpHeaderField( const void * pFirstVal,
                                   const void * pSecondVal )
    {
        const SigV4KeyValuePair_t * pFirst, * pSecond = NULL;
        size_t lenSmall = 0U;

        assert( pFirstVal != NULL );
        assert( pSecondVal != NULL );

        pFirst = ( const SigV4KeyValuePair_t * ) pFirstVal;
        pSecond = ( const SigV4KeyValuePair_t * ) pSecondVal;

        assert( ( pFirst->key.pData != NULL ) && ( pFirst->key.dataLen != 0U ) );
        assert( ( pSecond->key.pData != NULL ) && ( pSecond->key.dataLen != 0U ) );

        if( pFirst->key.dataLen <= pSecond->key.dataLen )
        {
            lenSmall = pFirst->key.dataLen;
        }
        else
        {
            lenSmall = pSecond->key.dataLen;
        }

        return strncmp( pFirst->key.pData,
                        pSecond->key.pData,
                        lenSmall );
    }

/*-----------------------------------------------------------*/

    static int32_t cmpQueryFieldValue( const void * pFirstVal,
                                       const void * pSecondVal )
    {
        const SigV4KeyValuePair_t * pFirst, * pSecond = NULL;
        size_t lenSmall = 0U;
        int32_t compResult = -1;

        assert( pFirstVal != NULL );
        assert( pSecondVal != NULL );

        pFirst = ( const SigV4KeyValuePair_t * ) pFirstVal;
        pSecond = ( const SigV4KeyValuePair_t * ) pSecondVal;

        assert( ( pFirst->key.pData != NULL ) && ( pFirst->key.dataLen != 0U ) );
        assert( ( pSecond->key.pData != NULL ) && ( pSecond->key.dataLen != 0U ) );

        lenSmall = ( pFirst->key.dataLen < pSecond->key.dataLen ) ? pFirst->key.dataLen : pSecond->key.dataLen;
        compResult = ( int32_t ) strncmp( pFirst->key.pData,
                                          pSecond->key.pData,
                                          lenSmall );

        if( compResult == 0 )
        {
            if( pFirst->key.dataLen == pSecond->key.dataLen )
            {
                /* The fields are equal, so sorting must be done by value. */
                lenSmall = ( pFirst->value.dataLen < pSecond->value.dataLen ) ? pFirst->value.dataLen : pSecond->value.dataLen;
                compResult = ( int32_t ) strncmp( pFirst->value.pData,
                                                  pSecond->value.pData,
                                                  lenSmall );
            }
            else
            {
                /* Fields share a common prefix, so the shorter one should come first. */
                compResult = ( pFirst->key.dataLen < pSecond->key.dataLen ) ? -1 : 1;
            }
        }

        if( ( compResult == 0 ) && ( pFirst->value.dataLen != pSecond->value.dataLen ) )
        {
            /* Values share a common prefix, so the shorter one should come first. */
            compResult = ( pFirst->value.dataLen < pSecond->value.dataLen ) ? -1 : 1;
        }

        return compResult;
    }

/*-----------------------------------------------------------*/

    static char toUpperHexChar( uint8_t value )
    {
        static const char upperHexArr[] = "0123456789ABCDEF";

        assert( value < 16U );

        return upperHexArr[ value ];
    }

/*-----------------------------------------------------------*/

    static size_t writeHexCodeOfChar( char * pBuffer,
                                      size_t bufferLen,
                                      char code )
    {
        assert( pBuffer != NULL );
        assert( bufferLen >= URI_ENCODED_SPECIAL_CHAR_SIZE );

        /* Suppress unused warning in when asserts are disabled. */
        ( void ) bufferLen;

        *pBuffer = '%';
        *( pBuffer + 1U ) = toUpperHexChar( ( ( uint8_t ) code ) >> 4U );
        *( pBuffer + 2U ) = toUpperHexChar( ( ( uint8_t ) code ) & 0x0FU );

        return URI_ENCODED_SPECIAL_CHAR_SIZE;
    }

/*-----------------------------------------------------------*/

    static size_t writeDoubleEncodedEquals( char * pBuffer,
                                            size_t bufferLen )
    {
        assert( pBuffer != NULL );
        assert( bufferLen > URI_DOUBLE_ENCODED_EQUALS_CHAR_SIZE );

        /* Suppress unused warning in when asserts are disabled. */
        ( void ) bufferLen;

        *pBuffer = '%';
        *( pBuffer + 1U ) = '2';
        *( pBuffer + 2U ) = '5';
        *( pBuffer + 3U ) = '3';
        *( pBuffer + 4U ) = 'D';

        return URI_DOUBLE_ENCODED_EQUALS_CHAR_SIZE;
    }

/*-----------------------------------------------------------*/

    static bool isAllowedChar( char c,
                               bool encodeSlash )
    {
        bool ret = false;

        /* Lowercase. */
        if( ( c >= 'a' ) && ( c <= 'z' ) )
        {
            ret = true;
        }
        /* Uppercase. */
        else if( ( c >= 'A' ) && ( c <= 'Z' ) )
        {
            ret = true;
        }
        /* Numeric. */
        else if( ( c >= '0' ) && ( c <= '9' ) )
        {
            ret = true;
        }
        /* Other characters. */
        else if( ( c == '-' ) || ( c == '_' ) || ( c == '.' ) || ( c == '~' ) )
        {
            ret = true;
        }
        else if( c == '/' )
        {
            ret = !encodeSlash;
        }
        else
        {
            ret = false;
        }

        return ret;
    }

/*-----------------------------------------------------------*/

    static SigV4Status_t encodeURI( const char * pUri,
                                    size_t uriLen,
                                    char * pCanonicalURI,
                                    size_t * canonicalURILen,
                                    bool encodeSlash,
                                    bool doubleEncodeEquals )
    {
        size_t uriIndex = 0U, bytesConsumed = 0U;
        size_t bufferLen = 0U;
        SigV4Status_t returnStatus = SigV4Success;

        assert( pUri != NULL );
        assert( pCanonicalURI != NULL );
        assert( canonicalURILen != NULL );

        bufferLen = *canonicalURILen;

        while( ( uriIndex < uriLen ) && ( returnStatus == SigV4Success ) )
        {
            if( doubleEncodeEquals && ( pUri[ uriIndex ] == '=' ) )
            {
                if( ( bufferLen - bytesConsumed ) < URI_DOUBLE_ENCODED_EQUALS_CHAR_SIZE )
                {
                    returnStatus = SigV4InsufficientMemory;
                    LOG_INSUFFICIENT_MEMORY_ERROR( "double encode '=' character in canonical query",
                                                   ( bytesConsumed + URI_DOUBLE_ENCODED_EQUALS_CHAR_SIZE - bufferLen ) );
                }
                else
                {
                    bytesConsumed += writeDoubleEncodedEquals( pCanonicalURI + bytesConsumed, bufferLen - bytesConsumed );
                }
            }
            else if( isAllowedChar( pUri[ uriIndex ], encodeSlash ) )
            {
                /* If the output buffer has space, add the character as-is in URI encoding as it
                 * is neither a special character nor an '=' character requiring double encoding. */
                if( bytesConsumed < bufferLen )
                {
                    pCanonicalURI[ bytesConsumed ] = pUri[ uriIndex ];
                    ++bytesConsumed;
                }
                else
                {
                    returnStatus = SigV4InsufficientMemory;
                    LogError( ( "Failed to encode URI in buffer due to insufficient memory" ) );
                }
            }
            else if( pUri[ uriIndex ] == '\0' )
            {
                /* The URI path beyond the NULL terminator is not encoded. */
                uriIndex = uriLen;
            }
            else
            {
                if( ( bufferLen - bytesConsumed ) < URI_ENCODED_SPECIAL_CHAR_SIZE )
                {
                    returnStatus = SigV4InsufficientMemory;
                    LOG_INSUFFICIENT_MEMORY_ERROR( "encode special character in canonical URI",
                                                   ( bytesConsumed + URI_ENCODED_SPECIAL_CHAR_SIZE - bufferLen ) );
                }
                else
                {
                    bytesConsumed += writeHexCodeOfChar( pCanonicalURI + bytesConsumed, bufferLen - bytesConsumed, pUri[ uriIndex - 1U ] );
                }
            }

            uriIndex++;
        }

        if( returnStatus == SigV4Success )
        {
            /* Set the output parameter of the number of URI encoded bytes written
             * to the buffer. */
            *canonicalURILen = bytesConsumed;
        }

        return returnStatus;
    }

/*-----------------------------------------------------------*/

    static SigV4Status_t generateCanonicalURI( const char * pUri,
                                               size_t uriLen,
                                               bool encodeTwice,
                                               CanonicalContext_t * pCanonicalRequest )
    {
        SigV4Status_t returnStatus = SigV4Success;
        char * pBufLoc = NULL;
        size_t encodedLen = 0U;

        assert( pUri != NULL );
        assert( pCanonicalRequest != NULL );
        assert( pCanonicalRequest->pBufCur != NULL );

        pBufLoc = pCanonicalRequest->pBufCur;
        encodedLen = pCanonicalRequest->bufRemaining;

        /* If the canonical URI needs to be encoded twice, then we encode once here,
         * and again at the end of the buffer. Afterwards, the second encode is copied
         * to overwrite the first one. */
        returnStatus = encodeURI( pUri, uriLen, pBufLoc, &encodedLen, false, false );

        if( returnStatus == SigV4Success )
        {
            if( encodeTwice )
            {
                size_t doubleEncodedLen = pCanonicalRequest->bufRemaining - encodedLen;

                /* Note that the result of encoding the URI a second time must be
                 * written to a different position in the buffer. It should not be done
                 * at an overlapping position of the single-encoded URI. Once written,
                 * the double-encoded URI is moved to the starting location of the single-encoded URI. */
                returnStatus = encodeURI( pBufLoc,
                                          encodedLen,
                                          pBufLoc + encodedLen,
                                          &doubleEncodedLen,
                                          false,
                                          false );

                if( returnStatus == SigV4Success )
                {
                    ( void ) memmove( pBufLoc, pBufLoc + encodedLen, doubleEncodedLen );
                    pBufLoc += doubleEncodedLen;
                    pCanonicalRequest->bufRemaining -= doubleEncodedLen;
                }
            }
            else
            {
                pBufLoc += encodedLen;
                pCanonicalRequest->bufRemaining -= encodedLen;
            }
        }

        if( returnStatus == SigV4Success )
        {
            if( pCanonicalRequest->bufRemaining < 1U )
            {
                returnStatus = SigV4InsufficientMemory;
                LOG_INSUFFICIENT_MEMORY_ERROR( "write newline character after canonical URI", 1U );
            }
            else
            {
                *pBufLoc = LINEFEED_CHAR;
                pCanonicalRequest->pBufCur = pBufLoc + 1U;
                pCanonicalRequest->bufRemaining -= 1U;
            }
        }

        return returnStatus;
    }

/*-----------------------------------------------------------*/

    static bool isTrimmableSpace( const char * value,
                                  size_t index,
                                  size_t valLen,
                                  size_t trimmedLength )
    {
        bool ret = false;

        assert( ( value != NULL ) && ( index < valLen ) );

        /* Only trim spaces. */
        if( isWhitespace( value[ index ] ) )
        {
            /* The last character is a trailing space. */
            if( ( index + 1U ) == valLen )
            {
                ret = true;
            }
            /* Trim if the next character is also a space. */
            else if( isWhitespace( value[ index + 1U ] ) )
            {
                ret = true;
            }
            /* It is a leading space if no characters have been written yet. */
            else if( trimmedLength == 0U )
            {
                ret = true;
            }
            else
            {
                /* Empty else. */
            }
        }

        return ret;
    }

/*-----------------------------------------------------------*/
    static char lowercaseCharacter( char inputChar )
    {
        char outputChar;

        /* Get the offset from a capital to lowercase character */
        int8_t offset = 'a' - 'A';

        if( ( inputChar >= 'A' ) && ( inputChar <= 'Z' ) )
        {
            outputChar = inputChar + offset;
        }
        else
        {
            outputChar = inputChar;
        }

        return outputChar;
    }

    static SigV4Status_t copyHeaderStringToCanonicalBuffer( const char * pData,
                                                            size_t dataLen,
                                                            uint32_t flags,
                                                            char separator,
                                                            CanonicalContext_t * canonicalRequest )
    {
        SigV4Status_t status = SigV4Success;
        size_t index = 0;
        size_t numOfBytesCopied = 0;
        size_t buffRemaining;
        char * pCurrBufLoc;

        assert( ( pData != NULL ) && ( dataLen > 0 ) );
        assert( canonicalRequest != NULL );
        assert( canonicalRequest->pBufCur != NULL );

        buffRemaining = canonicalRequest->bufRemaining;
        pCurrBufLoc = canonicalRequest->pBufCur;

        for( index = 0; index < dataLen; index++ )
        {
            /* If the header field is not in canonical form already, we need to check
             * whether this character represents a trimmable space. */
            if( !FLAG_IS_SET( flags, SIGV4_HTTP_HEADERS_ARE_CANONICAL_FLAG ) &&
                isTrimmableSpace( pData, index, dataLen, numOfBytesCopied ) )
            {
                /* Cannot copy trimmable space into canonical request buffer. */
            }
            /* Remaining buffer space should at least accommodate the character to copy and the trailing separator character. */
            else if( buffRemaining <= 1U )
            {
                status = SigV4InsufficientMemory;
                break;
            }
            else
            {
                /* Lowercase header key only. '\n' character marks the end of the value and header value
                 * does not need to be lowercased. */
                if( separator == '\n' )
                {
                    *pCurrBufLoc = ( pData[ index ] );
                }
                else
                {
                    *pCurrBufLoc = lowercaseCharacter( pData[ index ] );
                }

                pCurrBufLoc++;
                numOfBytesCopied++;
                buffRemaining--;
            }
        }

        /* Check that data to be copied does not contain all spaces only. */
        if( ( status == SigV4Success ) && ( numOfBytesCopied == 0U ) )
        {
            status = SigV4InvalidParameter;
        }

        /* Add the ending separating character passed to the function.
         * Note: Space for the separator character is accounted for while copying
         * header field data to canonical request buffer. */
        if( status == SigV4Success )
        {
            assert( buffRemaining >= 1 );
            *pCurrBufLoc = separator;
            pCurrBufLoc++;
            canonicalRequest->pBufCur = pCurrBufLoc;
            canonicalRequest->bufRemaining = ( buffRemaining - 1U );
        }

        return status;
    }

/*-----------------------------------------------------------*/

    static SigV4Status_t appendSignedHeaders( size_t headerCount,
                                              uint32_t flags,
                                              CanonicalContext_t * canonicalRequest,
                                              char ** pSignedHeaders,
                                              size_t * pSignedHeadersLen )
    {
        size_t headerIndex = 0, keyLen = 0;
        SigV4Status_t sigV4Status = SigV4Success;
        const char * headerKey;
        ptrdiff_t signedHeadersLen = 0;

        assert( canonicalRequest != NULL );
        assert( canonicalRequest->pBufCur != NULL );
        assert( headerCount > 0 );

        /* Store the starting location of the Signed Headers in the Canonical Request buffer. */
        *pSignedHeaders = canonicalRequest->pBufCur;

        for( headerIndex = 0; headerIndex < headerCount; headerIndex++ )
        {
            assert( ( canonicalRequest->pHeadersLoc[ headerIndex ].key.pData ) != NULL );
            keyLen = canonicalRequest->pHeadersLoc[ headerIndex ].key.dataLen;

            headerKey = canonicalRequest->pHeadersLoc[ headerIndex ].key.pData;

            /* ';' is used to separate signed multiple headers in the canonical request. */
            sigV4Status = copyHeaderStringToCanonicalBuffer( headerKey, keyLen, flags, ';', canonicalRequest );

            if( sigV4Status != SigV4Success )
            {
                LogError( ( "Unable to write Signed Headers for Canonical Request: Insufficient memory configured in \"SIGV4_PROCESSING_BUFFER_LENGTH\"" ) );
                break;
            }
        }

        /* Store the length of the "Signed Headers" data appended to the Canonical Request. */
        signedHeadersLen = canonicalRequest->pBufCur - *pSignedHeaders - 1;
        *pSignedHeadersLen = ( size_t ) signedHeadersLen;

        if( sigV4Status == SigV4Success )
        {
            /* Replacing the last ';' with '\n' as last header should not have ';'. */
            *( canonicalRequest->pBufCur - 1 ) = '\n';
        }

        return sigV4Status;
    }

/*-----------------------------------------------------------*/
    static void storeHashedPayloadLocation( size_t headerIndex,
                                            const char * pAmzSHA256Header,
                                            size_t amzSHA256HeaderLen,
                                            CanonicalContext_t * pCanonicalRequest )
    {
        assert( pCanonicalRequest != NULL );

        const uint8_t * pHeaderData = ( const uint8_t * ) pCanonicalRequest->pHeadersLoc[ headerIndex ].key.pData;
        size_t headerLen = pCanonicalRequest->pHeadersLoc[ headerIndex ].key.dataLen;
        const uint8_t * pHeaderLiteral = ( const uint8_t * ) pAmzSHA256Header;

        if( ( headerLen == amzSHA256HeaderLen ) && ( memcmp( pHeaderData, pHeaderLiteral, headerLen ) == 0 ) )
        {
            pCanonicalRequest->pHashPayloadLoc = pCanonicalRequest->pHeadersLoc[ headerIndex ].value.pData;
            pCanonicalRequest->hashPayloadLen = pCanonicalRequest->pHeadersLoc[ headerIndex ].value.dataLen;
        }
    }

    static SigV4Status_t appendCanonicalizedHeaders( size_t headerCount,
                                                     uint32_t flags,
                                                     CanonicalContext_t * canonicalRequest )
    {
        size_t headerIndex = 0, keyLen = 0, valLen = 0;
        const char * value;
        const char * headerKey;
        SigV4Status_t sigV4Status = SigV4Success;

        assert( canonicalRequest != NULL );
        assert( canonicalRequest->pBufCur != NULL );
        assert( headerCount > 0 );

        for( headerIndex = 0; headerIndex < headerCount; headerIndex++ )
        {
            assert( canonicalRequest->pHeadersLoc[ headerIndex ].key.pData != NULL );
            keyLen = canonicalRequest->pHeadersLoc[ headerIndex ].key.dataLen;
            valLen = canonicalRequest->pHeadersLoc[ headerIndex ].value.dataLen;
            headerKey = canonicalRequest->pHeadersLoc[ headerIndex ].key.pData;
            /* ':' is used to separate header key and header value in the canonical request. */
            sigV4Status = copyHeaderStringToCanonicalBuffer( headerKey, keyLen, flags, ':', canonicalRequest );

            if( sigV4Status == SigV4Success )
            {
                value = canonicalRequest->pHeadersLoc[ headerIndex ].value.pData;
                /* '\n' is used to separate each key-value pair in the canonical request. */
                sigV4Status = copyHeaderStringToCanonicalBuffer( value, valLen, flags, '\n', canonicalRequest );
            }

            if( sigV4Status != SigV4Success )
            {
                break;
            }
        }

        return sigV4Status;
    }

/*-----------------------------------------------------------*/

    static SigV4Status_t parseHeaderKeyValueEntries( const char * pHeaders,
                                                     size_t headersDataLen,
                                                     uint32_t flags,
                                                     size_t * headerCount,
                                                     CanonicalContext_t * canonicalRequest )
    {
        size_t index = 0, noOfHeaders;
        const char * pKeyOrValStartLoc;
        const char * pCurrLoc;
        bool keyFlag = true;
        SigV4Status_t sigV4Status = SigV4Success;
        ptrdiff_t dataLen = 0;

        assert( pHeaders != NULL );
        assert( headersDataLen > 0 );
        assert( canonicalRequest != NULL );
        assert( headerCount != NULL );

        noOfHeaders = *headerCount;
        pKeyOrValStartLoc = pHeaders;
        pCurrLoc = pHeaders;

        for( index = 0; index < headersDataLen; index++ )
        {
            if( noOfHeaders == SIGV4_MAX_HTTP_HEADER_COUNT )
            {
                sigV4Status = SigV4MaxHeaderPairCountExceeded;
                break;
            }
            /* Look for key part of an header field entry. */
            else if( ( keyFlag ) && ( pHeaders[ index ] == ':' ) )
            {
                dataLen = pCurrLoc - pKeyOrValStartLoc;
                canonicalRequest->pHeadersLoc[ noOfHeaders ].key.pData = pKeyOrValStartLoc;
                canonicalRequest->pHeadersLoc[ noOfHeaders ].key.dataLen = ( size_t ) dataLen;
                pKeyOrValStartLoc = pCurrLoc + 1U;
                keyFlag = false;
            }
            /* Look for header value part of a header field entry for both canonicalized and non-canonicalized forms. */
            /* Non-canonicalized headers will have header values ending with "\r\n". */
            else if( ( !keyFlag ) && !FLAG_IS_SET( flags, SIGV4_HTTP_HEADERS_ARE_CANONICAL_FLAG ) && ( ( index + 1U ) < headersDataLen ) &&
                     ( 0 == strncmp( pCurrLoc, HTTP_REQUEST_LINE_ENDING, HTTP_REQUEST_LINE_ENDING_LEN ) ) )
            {
                dataLen = pCurrLoc - pKeyOrValStartLoc;
                canonicalRequest->pHeadersLoc[ noOfHeaders ].value.pData = pKeyOrValStartLoc;
                canonicalRequest->pHeadersLoc[ noOfHeaders ].value.dataLen = ( size_t ) dataLen;

                /* Storing location of hashed request payload */
                storeHashedPayloadLocation( noOfHeaders, SIGV4_HTTP_X_AMZ_CONTENT_SHA256_HEADER, SIGV4_HTTP_X_AMZ_CONTENT_SHA256_HEADER_LENGTH, canonicalRequest );

                /* Set starting location of the next header key string after the "\r\n". */
                pKeyOrValStartLoc = pCurrLoc + 2U;
                keyFlag = true;
                noOfHeaders++;
            }
            /* Canonicalized headers will have header values ending just with "\n". */
            else if( ( !keyFlag ) && FLAG_IS_SET( flags, SIGV4_HTTP_HEADERS_ARE_CANONICAL_FLAG ) && ( pHeaders[ index ] == '\n' ) )
            {
                dataLen = pCurrLoc - pKeyOrValStartLoc;
                canonicalRequest->pHeadersLoc[ noOfHeaders ].value.pData = pKeyOrValStartLoc;
                canonicalRequest->pHeadersLoc[ noOfHeaders ].value.dataLen = ( size_t ) dataLen;

                /* Storing location of hashed request payload */
                storeHashedPayloadLocation( noOfHeaders, SIGV4_HTTP_X_AMZ_CONTENT_SHA256_HEADER, SIGV4_HTTP_X_AMZ_CONTENT_SHA256_HEADER_LENGTH, canonicalRequest );

                /* Set starting location of the next header key string after the "\n". */
                pKeyOrValStartLoc = pCurrLoc + 1U;
                keyFlag = true;
                noOfHeaders++;
            }
            else
            {
                /* Empty else. */
            }

            pCurrLoc++;
        }

        /* Ensure each key has its corresponding value. */
        assert( keyFlag == true );

        /* If no header was found OR header value was not found for a header key,
         *  that represents incorrect HTTP headers data passed by the application. */
        if( ( noOfHeaders == 0U ) || ( keyFlag == false ) )
        {
            sigV4Status = SigV4InvalidHttpHeaders;
        }
        else
        {
            *headerCount = noOfHeaders;
        }

        return sigV4Status;
    }

/*-----------------------------------------------------------*/

    static SigV4Status_t generateCanonicalAndSignedHeaders( const char * pHeaders,
                                                            size_t headersLen,
                                                            uint32_t flags,
                                                            CanonicalContext_t * canonicalRequest,
                                                            char ** pSignedHeaders,
                                                            size_t * pSignedHeadersLen )
    {
        size_t noOfHeaders = 0;
        SigV4Status_t sigV4Status = SigV4Success;

        assert( pHeaders != NULL );
        assert( canonicalRequest != NULL );
        assert( canonicalRequest->pBufCur != NULL );
        assert( pSignedHeaders != NULL );
        assert( pSignedHeadersLen != NULL );

        /* Parsing header string to extract key and value. */
        sigV4Status = parseHeaderKeyValueEntries( pHeaders,
                                                  headersLen,
                                                  flags,
                                                  &noOfHeaders,
                                                  canonicalRequest );

        if( sigV4Status == SigV4Success )
        {
            if( FLAG_IS_SET( flags, SIGV4_HTTP_HEADERS_ARE_CANONICAL_FLAG ) )
            {
                /* Headers are already canonicalized, so just write it to the buffer as is. */
                sigV4Status = writeLineToCanonicalRequest( pHeaders,
                                                           headersLen,
                                                           canonicalRequest );
            }
            else
            {
                /* Sorting headers based on keys. */
                quickSort( canonicalRequest->pHeadersLoc, noOfHeaders, sizeof( SigV4KeyValuePair_t ), cmpHeaderField );

                /* If the headers are canonicalized, we will copy them directly into the buffer as they do not
                 * need processing, else we need to call the following function. */
                sigV4Status = appendCanonicalizedHeaders( noOfHeaders, flags, canonicalRequest );
            }
        }

        /* The \n character must be written if provided headers are not already canonicalized. */
        if( ( sigV4Status == SigV4Success ) && !FLAG_IS_SET( flags, SIGV4_HTTP_HEADERS_ARE_CANONICAL_FLAG ) )
        {
            if( canonicalRequest->bufRemaining < 1U )
            {
                sigV4Status = SigV4InsufficientMemory;
                LOG_INSUFFICIENT_MEMORY_ERROR( "write the newline character after canonical headers", 1U );
            }
            else
            {
                *canonicalRequest->pBufCur = LINEFEED_CHAR;
                canonicalRequest->pBufCur++;
                canonicalRequest->bufRemaining--;
            }
        }

        if( sigV4Status == SigV4Success )
        {
            sigV4Status = appendSignedHeaders( noOfHeaders,
                                               flags,
                                               canonicalRequest,
                                               pSignedHeaders,
                                               pSignedHeadersLen );
        }

        return sigV4Status;
    }

/*-----------------------------------------------------------*/

    static void setQueryParameterKey( size_t currentParameter,
                                      const char * pKey,
                                      size_t keyLen,
                                      CanonicalContext_t * pCanonicalRequest )
    {
        assert( pKey != NULL );
        assert( keyLen > 0U );
        assert( ( pCanonicalRequest != NULL ) && ( pCanonicalRequest->pQueryLoc != NULL ) );

        pCanonicalRequest->pQueryLoc[ currentParameter ].key.pData = pKey;
        pCanonicalRequest->pQueryLoc[ currentParameter ].key.dataLen = keyLen;
    }

/*-----------------------------------------------------------*/

    static void setQueryParameterValue( size_t currentParameter,
                                        const char * pValue,
                                        size_t valueLen,
                                        CanonicalContext_t * pCanonicalRequest )
    {
        assert( ( pCanonicalRequest != NULL ) && ( pCanonicalRequest->pQueryLoc != NULL ) );

        pCanonicalRequest->pQueryLoc[ currentParameter ].value.pData = pValue;
        pCanonicalRequest->pQueryLoc[ currentParameter ].value.dataLen = valueLen;
    }

/*-----------------------------------------------------------*/

    static void processAmpersandInQueryString( bool fieldHasValue,
                                               size_t currQueryIndex,
                                               size_t * pStartOfFieldOrValue,
                                               size_t * pCurrParamCount,
                                               const char * pQuery,
                                               CanonicalContext_t * pCanonicalRequest )
    {
        bool previousParamIsValid = true;

        assert( pCurrParamCount != NULL );
        assert( pStartOfFieldOrValue != NULL );
        assert( pQuery != NULL );
        assert( pCanonicalRequest != NULL );

        /* Process scenarios when a value for the previous parameter entry has been detected. */
        if( fieldHasValue )
        {
            /* Case when parameter has empty value even though the query parameter entry has the form
             * "<param>=". */
            if( ( currQueryIndex - *pStartOfFieldOrValue ) == 0U )
            {
                /* Store the previous parameter's empty value information. Use NULL to represent empty value. */
                setQueryParameterValue( *pCurrParamCount, NULL, 0U, pCanonicalRequest );
            }

            /* This represents the case when the previous parameter has a value associated with it. */
            else
            {
                /* End of value reached, so store a pointer to the previously set value. */
                setQueryParameterValue( *pCurrParamCount, &pQuery[ *pStartOfFieldOrValue ], currQueryIndex - *pStartOfFieldOrValue, pCanonicalRequest );
            }
        }
        /* A parameter value has not been found for the previous parameter. */
        else
        {
            /* Ignore unnecessary '&' that represent empty parameter names.
             * Trimmable '&'s can be leading, trailing or repeated as separators between parameter
             * pairs.
             * For example, this function will prune "&p1=v1&&p2=v2&" to "p1=v1&p2=v2". */
            if( ( currQueryIndex - *pStartOfFieldOrValue ) == 0U )
            {
                /* Do nothing as previous parameter name is empty. */
                previousParamIsValid = false;
            }

            /* Case when a new query parameter pair has begun without the previous parameter having an associated value,
             * i.e. of the format "<param1>&<param2>=<value>" where "<param1>".
             * In thus case, as the previous parameter does not have a value, we will store an empty value for it. */
            else
            {
                /* Store information about previous query parameter name. The query parameter has no associated value. */
                setQueryParameterKey( *pCurrParamCount, &pQuery[ *pStartOfFieldOrValue ], currQueryIndex - *pStartOfFieldOrValue, pCanonicalRequest );

                /* Store the previous parameter's empty value information. Use NULL to represent empty value. */
                setQueryParameterValue( *pCurrParamCount, NULL, 0U, pCanonicalRequest );
            }
        }

        /* Increment the parameter count if the previous parameter is not empty
         * An empty previous parameter is caused in query strings where the '&' are unnecessary. */
        if( previousParamIsValid == true )
        {
            /* As the previous parameter is valid, increment the total parameters
             * parsed so far from the query string. */
            ( *pCurrParamCount )++;
        }

        /* As '&' is always treated as a separator, update the starting index to represent the next
         * query parameter. */
        *pStartOfFieldOrValue = currQueryIndex + 1U;
    }

/*-----------------------------------------------------------*/

    static SigV4Status_t parseQueryString( const char * pQuery,
                                           size_t queryLen,
                                           size_t * pNumberOfParameters,
                                           CanonicalContext_t * pCanonicalRequest )
    {
        size_t currentParameter = 0U, i = 0U, startOfFieldOrValue = 0U;
        bool fieldHasValue = false;
        SigV4Status_t returnStatus = SigV4Success;

        assert( pNumberOfParameters != NULL );
        assert( pCanonicalRequest != NULL );
        assert( pCanonicalRequest->pQueryLoc != NULL );

        /* Note: Constness of the query string is casted out here, taking care not to modify
         * its contents in any way. */

        /* Set cursors to each field and value in the query string.
         * Note: We iterate the index to queryLen (instead of queryLen - 1) for the
         * special case of handling the last index as a terminating value of a query
         * parameter. */
        for( i = 0U; i <= queryLen; i++ )
        {
            /* This test is at the beginning of the loop to ensure that
             * `pCanonicalRequest->pQueryLoc`is only accessed with a valid index.
             * The final iteration may result in `currentParameter` holding
             * SIGV4_MAX_QUERY_PAIR_COUNT, in order to set the number of parameters. */
            if( currentParameter >= SIGV4_MAX_QUERY_PAIR_COUNT )
            {
                returnStatus = SigV4MaxQueryPairCountExceeded;
                LogError( ( "Failed to parse query string: Number of query parameters exceeds max threshold defined in config. "
                            "SIGV4_MAX_QUERY_PAIR_COUNT=%lu", ( unsigned long ) SIGV4_MAX_QUERY_PAIR_COUNT ) );
                break;
            }

            /* Encountering an '&' indicates the start of a new key, and the end of the
             * old value (or key if the field has no value). The last value will not be
             * followed by anything, so we iterate to one beyond the end of the string.
             * Short circuit evaluation ensures the string is not dereferenced for that case. */
            if( ( i == queryLen ) || ( pQuery[ i ] == '&' ) )
            {
                processAmpersandInQueryString( fieldHasValue, i, &startOfFieldOrValue,
                                               &currentParameter, pQuery, pCanonicalRequest );

                /* As '&' represents a new parameter, reset the flag about parameter value state.*/
                fieldHasValue = false;
            }
            else if( ( pQuery[ i ] == '=' ) && !fieldHasValue )
            {
                /* Store information about Query Parameter Key in the canonical context. This query parameter has an associated value. */
                setQueryParameterKey( currentParameter, &pQuery[ startOfFieldOrValue ], i - startOfFieldOrValue, pCanonicalRequest );

                /* Set the starting index for the query parameter's value. */
                startOfFieldOrValue = i + 1U;

                /* Set the flag to indicate that the current parameter's value has been found. */
                fieldHasValue = true;
            }
            else
            {
                /* Empty else. */
            }
        }

        *pNumberOfParameters = currentParameter;

        return returnStatus;
    }

/*-----------------------------------------------------------*/

    static SigV4Status_t writeValueInCanonicalizedQueryString( char * pBufCur,
                                                               size_t bufferLen,
                                                               const char * pValue,
                                                               size_t valueLen,
                                                               size_t * pEncodedLen )
    {
        SigV4Status_t returnStatus = SigV4Success;
        size_t valueBytesWritten = 0U;

        assert( pBufCur != NULL );
        assert( pEncodedLen != NULL );
        assert( ( pValue == NULL ) || ( valueLen != 0U ) );

        /* Check that there is space at least for the equals to character. */
        if( bufferLen < 1U )
        {
            LOG_INSUFFICIENT_MEMORY_ERROR( "write '=' query parameter separator", 1U );
            returnStatus = SigV4InsufficientMemory;
        }
        else
        {
            *pBufCur = '=';
            *pEncodedLen = 1U;
            valueBytesWritten = bufferLen - 1U;

            /* Encode parameter value if non-empty. Query parameters can have empty values. */
            if( valueLen > 0U )
            {
                returnStatus = encodeURI( pValue,
                                          valueLen,
                                          pBufCur + 1U,
                                          &valueBytesWritten,
                                          true,
                                          true );

                if( returnStatus == SigV4Success )
                {
                    *pEncodedLen += valueBytesWritten;
                }
            }
        }

        return returnStatus;
    }

/*-----------------------------------------------------------*/

    static SigV4Status_t writeCanonicalQueryParameters( CanonicalContext_t * pCanonicalRequest,
                                                        size_t numberOfParameters )
    {
        SigV4Status_t returnStatus = SigV4Success;
        char * pBufLoc = NULL;
        size_t encodedLen = 0U, remainingLen = 0U, paramsIndex = 0U;

        assert( pCanonicalRequest != NULL );
        assert( pCanonicalRequest->pBufCur != NULL );
        assert( pCanonicalRequest->pQueryLoc != NULL );

        pBufLoc = pCanonicalRequest->pBufCur;
        remainingLen = pCanonicalRequest->bufRemaining;

        for( paramsIndex = 0U; paramsIndex < numberOfParameters; paramsIndex++ )
        {
            assert( pCanonicalRequest->pQueryLoc[ paramsIndex ].key.pData != NULL );
            assert( pCanonicalRequest->pQueryLoc[ paramsIndex ].key.dataLen > 0U );

            encodedLen = remainingLen;
            returnStatus = encodeURI( pCanonicalRequest->pQueryLoc[ paramsIndex ].key.pData,
                                      pCanonicalRequest->pQueryLoc[ paramsIndex ].key.dataLen,
                                      pBufLoc,
                                      &encodedLen,
                                      true /* Encode slash (/) */,
                                      false /* Do not encode '='. */ );

            if( returnStatus == SigV4Success )
            {
                pBufLoc += encodedLen;
                remainingLen -= encodedLen;

                assert( pCanonicalRequest->pQueryLoc[ paramsIndex ].value.pData != NULL );
                returnStatus = writeValueInCanonicalizedQueryString( pBufLoc,
                                                                     remainingLen,
                                                                     pCanonicalRequest->pQueryLoc[ paramsIndex ].value.pData,
                                                                     pCanonicalRequest->pQueryLoc[ paramsIndex ].value.dataLen,
                                                                     &encodedLen );
                pBufLoc += encodedLen;
                remainingLen -= encodedLen;
            }

            /* If there is a succeeding query parameter, add the '&' separator in the canonical query. */
            if( ( returnStatus == SigV4Success ) && ( ( paramsIndex + 1U ) != numberOfParameters ) )
            {
                /* Before adding the '&' for the next query parameter, check that there is
                 * space in the buffer. */
                if( remainingLen > 0U )
                {
                    *pBufLoc = '&';
                    ++pBufLoc;
                    remainingLen--;
                }

                /* There is at least another query parameter entry to encode
                 * but no space in the buffer. */
                else
                {
                    returnStatus = SigV4InsufficientMemory;
                    LOG_INSUFFICIENT_MEMORY_ERROR( "write query parameter separator, '&'", 1U );
                    break;
                }
            }
        }

        /* Update the context state if canonical query generation was successful. */
        if( returnStatus == SigV4Success )
        {
            pCanonicalRequest->pBufCur = pBufLoc;
            pCanonicalRequest->bufRemaining = remainingLen;
        }

        return returnStatus;
    }

/*-----------------------------------------------------------*/

    static SigV4Status_t generateCanonicalQuery( const char * pQuery,
                                                 size_t queryLen,
                                                 CanonicalContext_t * pCanonicalContext )
    {
        SigV4Status_t returnStatus = SigV4Success;
        size_t numberOfParameters = 0U;

        assert( pCanonicalContext != NULL );
        assert( pCanonicalContext->pBufCur != NULL );

        if( pQuery != NULL )
        {
            returnStatus = parseQueryString( pQuery, queryLen, &numberOfParameters, pCanonicalContext );
        }

        if( ( returnStatus == SigV4Success ) && ( numberOfParameters > 0U ) )
        {
            /* Sort the parameter names by character code point in ascending order.
             * Parameters with duplicate names should be sorted by value. */
            quickSort( pCanonicalContext->pQueryLoc, numberOfParameters, sizeof( SigV4KeyValuePair_t ), cmpQueryFieldValue );

            /* URI-encode each parameter name and value according to the following rules specified for SigV4:
             *  - Do not URI-encode any of the unreserved characters that RFC 3986 defines:
             *      A-Z, a-z, 0-9, hyphen ( - ), underscore ( _ ), period ( . ), and tilde ( ~ ).
             *  - Percent-encode all other characters with %XY, where X and Y are hexadecimal characters (0-9 and uppercase A-F).
             *  - Double-encode any equals ( = ) characters in parameter values.
             */
            returnStatus = writeCanonicalQueryParameters( pCanonicalContext, numberOfParameters );
        }

        if( returnStatus == SigV4Success )
        {
            if( pCanonicalContext->bufRemaining > 0U )
            {
                /* Append a linefeed at the end. */
                *pCanonicalContext->pBufCur = LINEFEED_CHAR;
                pCanonicalContext->pBufCur += 1U;
                pCanonicalContext->bufRemaining -= 1U;
            }
            else
            {
                returnStatus = SigV4InsufficientMemory;
                LOG_INSUFFICIENT_MEMORY_ERROR( "write newline character after canonical query", 1U );
            }
        }

        return returnStatus;
    }

#endif /* #if ( SIGV4_USE_CANONICAL_SUPPORT == 1 ) */

/*-----------------------------------------------------------*/

static SigV4Status_t verifyParamsToGenerateAuthHeaderApi( const SigV4Parameters_t * pParams,
                                                          const char * pAuthBuf,
                                                          const size_t * authBufLen,
                                                          char * const * pSignature,
                                                          const size_t * signatureLen )
{
    SigV4Status_t returnStatus = SigV4Success;

    /* Check for NULL members of struct pParams */
    if( ( pParams == NULL ) || ( pAuthBuf == NULL ) || ( authBufLen == NULL ) ||
        ( pSignature == NULL ) || ( signatureLen == NULL ) )
    {
        LogError( ( "Parameter check failed: At least one of the input parameters is NULL. "
                    "Input parameters cannot be NULL" ) );
        returnStatus = SigV4InvalidParameter;
    }
    else if( pParams->pCredentials == NULL )
    {
        LogError( ( "Parameter check failed: pParams->pCredentials is NULL." ) );
        returnStatus = SigV4InvalidParameter;
    }
    else if( ( pParams->pCredentials->pAccessKeyId == NULL ) || ( pParams->pCredentials->accessKeyIdLen == 0U ) )
    {
        LogError( ( "Parameter check failed: Access Key ID data is empty." ) );
        returnStatus = SigV4InvalidParameter;
    }
    else if( ( pParams->pCredentials->pSecretAccessKey == NULL ) || ( pParams->pCredentials->secretAccessKeyLen == 0U ) )
    {
        LogError( ( "Parameter check failed: Secret Access Key data is empty." ) );
        returnStatus = SigV4InvalidParameter;
    }
    else if( pParams->pDateIso8601 == NULL )
    {
        LogError( ( "Parameter check failed: pParams->DateIso8601 data is NULL." ) );
        returnStatus = SigV4InvalidParameter;
    }
    else if( ( pParams->pRegion == NULL ) || ( pParams->regionLen == 0U ) )
    {
        LogError( ( "Parameter check failed: Region data is empty." ) );
        returnStatus = SigV4InvalidParameter;
    }
    else if( ( pParams->pService == NULL ) || ( pParams->serviceLen == 0U ) )
    {
        LogError( ( "Parameter check failed: Service data is empty." ) );
        returnStatus = SigV4InvalidParameter;
    }
    else if( ( pParams->pAlgorithm != NULL ) && ( pParams->algorithmLen == 0U ) )
    {
        LogError( ( "Parameter check failed: Algorithm is specified but length (pParams->algorithmLen) passed is 0U." ) );
        returnStatus = SigV4InvalidParameter;
    }
    else if( pParams->pCryptoInterface == NULL )
    {
        LogError( ( "Parameter check failed: pParams->pCryptoInterface is NULL." ) );
        returnStatus = SigV4InvalidParameter;
    }
    else if( ( pParams->pCryptoInterface->hashInit == NULL ) || ( pParams->pCryptoInterface->hashUpdate == NULL ) ||
             ( pParams->pCryptoInterface->hashFinal == NULL ) )
    {
        LogError( ( "Parameter check failed: At least one of hashInit, hashUpdate, hashFinal function pointer members is NULL." ) );
        returnStatus = SigV4InvalidParameter;
    }
    else if( pParams->pCryptoInterface->hashBlockLen > SIGV4_HASH_MAX_BLOCK_LENGTH )
    {
        LogError( ( "Parameter check failed: pParams->pCryptoInterface->hashBlockLen is greater than `SIGV4_HASH_MAX_BLOCK_LENGTH`, "
                    "which can be configured in sigv4_config.h." ) );
        returnStatus = SigV4InvalidParameter;
    }
    else if( pParams->pCryptoInterface->hashDigestLen > SIGV4_HASH_MAX_DIGEST_LENGTH )
    {
        LogError( ( "Parameter check failed: pParams->pCryptoInterface->hashDigestLen is greater than `SIGV4_HASH_MAX_DIGEST_LENGTH`, "
                    "which can be configured in sigv4_config.h." ) );
        returnStatus = SigV4InvalidParameter;
    }
    else if( pParams->pHttpParameters == NULL )
    {
        LogError( ( "Parameter check failed: pParams->pHttpParameters is NULL." ) );
        returnStatus = SigV4InvalidParameter;
    }
    else if( ( pParams->pHttpParameters->pHttpMethod == NULL ) || ( pParams->pHttpParameters->httpMethodLen == 0U ) )
    {
        LogError( ( "Parameter check failed: HTTP Method data is either NULL or zero bytes in length." ) );
        returnStatus = SigV4InvalidParameter;
    }
    else if( ( pParams->pHttpParameters->pHeaders == NULL ) || ( pParams->pHttpParameters->headersLen == 0U ) )
    {
        LogError( ( "Parameter check failed: HTTP URI path information is either NULL or zero bytes in length." ) );
        returnStatus = SigV4InvalidParameter;
    }
    else
    {
        /* Empty else block for MISRA C:2012 compliance. */
    }

    return returnStatus;
}

/*-----------------------------------------------------------*/

static void assignDefaultArguments( const SigV4Parameters_t * pParams,
                                    const char ** pAlgorithm,
                                    size_t * algorithmLen )
{
    if( pParams->pAlgorithm == NULL )
    {
        /* The default algorithm is AWS4-HMAC-SHA256. */
        *pAlgorithm = SIGV4_AWS4_HMAC_SHA256;
        *algorithmLen = SIGV4_AWS4_HMAC_SHA256_LENGTH;
    }
    else
    {
        *pAlgorithm = pParams->pAlgorithm;
        *algorithmLen = pParams->algorithmLen;
    }
}

/*-----------------------------------------------------------*/

static int32_t completeHash( const uint8_t * pInput,
                             size_t inputLen,
                             uint8_t * pOutput,
                             size_t outputLen,
                             const SigV4CryptoInterface_t * pCryptoInterface )
{
    int32_t hashStatus = -1;

    assert( pOutput != NULL );
    assert( outputLen > 0 );
    assert( pCryptoInterface != NULL );
    assert( pCryptoInterface->hashInit != NULL );
    assert( pCryptoInterface->hashUpdate != NULL );
    assert( pCryptoInterface->hashFinal != NULL );

    hashStatus = pCryptoInterface->hashInit( pCryptoInterface->pHashContext );

    if( hashStatus == 0 )
    {
        hashStatus = pCryptoInterface->hashUpdate( pCryptoInterface->pHashContext,
                                                   pInput, inputLen );
    }

    if( hashStatus == 0 )
    {
        hashStatus = pCryptoInterface->hashFinal( pCryptoInterface->pHashContext,
                                                  pOutput, outputLen );
    }

    return hashStatus;
}

/*-----------------------------------------------------------*/

static SigV4Status_t completeHashAndHexEncode( const char * pInput,
                                               size_t inputLen,
                                               char * pOutput,
                                               size_t * pOutputLen,
                                               const SigV4CryptoInterface_t * pCryptoInterface )
{
    SigV4Status_t returnStatus = SigV4Success;
    /* Used to store the hash of the request payload. */
    uint8_t hashBuffer[ SIGV4_HASH_MAX_DIGEST_LENGTH ];
    SigV4String_t originalHash;
    SigV4String_t hexEncodedHash;

    assert( pOutput != NULL );
    assert( pOutputLen != NULL );
    assert( pCryptoInterface != NULL );
    assert( pCryptoInterface->hashInit != NULL );
    assert( pCryptoInterface->hashUpdate != NULL );
    assert( pCryptoInterface->hashFinal != NULL );

    originalHash.pData = ( char * ) hashBuffer;
    originalHash.dataLen = pCryptoInterface->hashDigestLen;
    hexEncodedHash.pData = pOutput;
    hexEncodedHash.dataLen = *pOutputLen;

    if( completeHash( ( const uint8_t * ) pInput,
                      inputLen,
                      hashBuffer,
                      pCryptoInterface->hashDigestLen,
                      pCryptoInterface ) != 0 )
    {
        returnStatus = SigV4HashError;
    }

    if( returnStatus == SigV4Success )
    {
        /* Hex-encode the request payload. */
        returnStatus = lowercaseHexEncode( &originalHash,
                                           &hexEncodedHash );
    }

    if( returnStatus == SigV4Success )
    {
        assert( hexEncodedHash.dataLen == pCryptoInterface->hashDigestLen * 2U );
        *pOutputLen = hexEncodedHash.dataLen;
    }

    return returnStatus;
}

static int32_t hmacAddKey( HmacContext_t * pHmacContext,
                           const char * pKey,
                           size_t keyLen,
                           bool isKeyPrefix )
{
    int32_t returnStatus = 0;
    const SigV4CryptoInterface_t * pCryptoInterface = NULL;
    const uint8_t * pUnsignedKey = ( const uint8_t * ) pKey;

    assert( pHmacContext != NULL );
    assert( pHmacContext->key != NULL );
    assert( pHmacContext->pCryptoInterface != NULL );
    assert( pHmacContext->pCryptoInterface->hashInit != NULL );
    assert( pHmacContext->pCryptoInterface->hashUpdate != NULL );
    assert( pHmacContext->pCryptoInterface->hashFinal != NULL );

    pCryptoInterface = pHmacContext->pCryptoInterface;

    /* At the first time this function is called, it is important that pHmacContext->keyLen
     * is set to 0U so that the key can be copied to the start of the buffer. */
    if( ( pHmacContext->keyLen + keyLen ) <= pCryptoInterface->hashBlockLen )
    {
        /* The key fits into the block so just append it. */
        ( void ) memcpy( pHmacContext->key + pHmacContext->keyLen, pUnsignedKey, keyLen );
        pHmacContext->keyLen += keyLen;
    }
    else
    {
        returnStatus = pCryptoInterface->hashInit( pCryptoInterface->pHashContext );

        /* Hash part of the key that is cached in the HMAC context. */
        if( returnStatus == 0 )
        {
            returnStatus = pCryptoInterface->hashUpdate( pCryptoInterface->pHashContext,
                                                         pHmacContext->key,
                                                         pHmacContext->keyLen );
        }

        /* Hash down the remaining part of the key in order to create a block-sized derived key. */
        if( returnStatus == 0 )
        {
            returnStatus = pCryptoInterface->hashUpdate( pCryptoInterface->pHashContext,
                                                         pUnsignedKey,
                                                         keyLen );
        }

        if( returnStatus == 0 )
        {
            /* Store the final block-sized derived key. */
            returnStatus = pCryptoInterface->hashFinal( pCryptoInterface->pHashContext,
                                                        pHmacContext->key,
                                                        pCryptoInterface->hashBlockLen );
            pHmacContext->keyLen = pCryptoInterface->hashDigestLen;
        }
    }

    /* If the complete key has been obtained and it is less than the hash block size, then append
     * padding with zero valued bytes to make it block-sized. */
    if( !isKeyPrefix && ( returnStatus == 0 ) && ( pHmacContext->keyLen < pCryptoInterface->hashBlockLen ) )
    {
        /* Zero pad to the right so that the key has the same size as the block size. */
        ( void ) memset( ( void * ) ( pHmacContext->key + pHmacContext->keyLen ),
                         0,
                         pCryptoInterface->hashBlockLen - pHmacContext->keyLen );

        pHmacContext->keyLen = pCryptoInterface->hashBlockLen;
    }

    return returnStatus;
}

/*-----------------------------------------------------------*/

static int32_t hmacIntermediate( HmacContext_t * pHmacContext,
                                 const char * pData,
                                 size_t dataLen )
{
    int32_t returnStatus = 0;
    size_t i = 0U;
    const SigV4CryptoInterface_t * pCryptoInterface = pHmacContext->pCryptoInterface;

    assert( pHmacContext != NULL );
    assert( dataLen > 0U );
    assert( pHmacContext->key != NULL );
    assert( pHmacContext->pCryptoInterface != NULL );
    assert( pHmacContext->pCryptoInterface->hashInit != NULL );
    assert( pHmacContext->pCryptoInterface->hashUpdate != NULL );
    assert( pHmacContext->pCryptoInterface->hashFinal != NULL );
    assert( pHmacContext->keyLen == pCryptoInterface->hashBlockLen );

    /* Derive the inner HMAC key by XORing the key with inner pad byte. */
    for( i = 0U; i < pCryptoInterface->hashBlockLen; i++ )
    {
        /* XOR the key with the ipad. */
        pHmacContext->key[ i ] ^= HMAC_INNER_PAD_BYTE;
    }

    /* Initialize the Hash Crypto interface for performing new hash operation
     * of H(Inner Key || Data) in the HMAC algorithm. */
    returnStatus = pCryptoInterface->hashInit( pCryptoInterface->pHashContext );

    if( returnStatus == 0 )
    {
        /* Hash the inner-padded block-sized key. */
        returnStatus = pCryptoInterface->hashUpdate( pCryptoInterface->pHashContext,
                                                     pHmacContext->key,
                                                     pCryptoInterface->hashBlockLen );
    }

    if( returnStatus == 0 )
    {
        /* Hash the data. */
        returnStatus = pCryptoInterface->hashUpdate( pCryptoInterface->pHashContext,
                                                     ( const uint8_t * ) pData,
                                                     dataLen );
    }

    return returnStatus;
}

/*-----------------------------------------------------------*/

static int32_t hmacFinal( HmacContext_t * pHmacContext,
                          char * pMac,
                          size_t macLen )
{
    int32_t returnStatus = -1;
    uint8_t innerHashDigest[ SIGV4_HASH_MAX_DIGEST_LENGTH ];
    size_t i = 0U;
    const SigV4CryptoInterface_t * pCryptoInterface = NULL;

    assert( pHmacContext != NULL );
    assert( pHmacContext->key != NULL );
    assert( pHmacContext->pCryptoInterface != NULL );
    /* Note that we must have a block-sized derived key before calling this function. */
    assert( pHmacContext->pCryptoInterface->hashInit != NULL );
    assert( pHmacContext->pCryptoInterface->hashUpdate != NULL );
    assert( pHmacContext->pCryptoInterface->hashFinal != NULL );

    pCryptoInterface = pHmacContext->pCryptoInterface;

    /* Write the inner hash. */
    returnStatus = pCryptoInterface->hashFinal( pCryptoInterface->pHashContext,
                                                innerHashDigest,
                                                pCryptoInterface->hashDigestLen );

    if( returnStatus == 0 )
    {
        /* Create the outer-padded key by retrieving the original key from
         * the inner-padded key (by XORing with ipad byte) and then XOR with opad
         * to generate the outer-padded key. As XOR is associative, one way to do
         * this is by performing XOR on each byte of the inner-padded key (ipad ^ opad).  */
        for( i = 0U; i < pCryptoInterface->hashBlockLen; i++ )
        {
            pHmacContext->key[ i ] ^= HMAX_IPAD_XOR_OPAD_BYTE;
        }

        returnStatus = pCryptoInterface->hashInit( pCryptoInterface->pHashContext );
    }

    if( returnStatus == 0 )
    {
        /* Update hash using the outer-padded key. */
        returnStatus = pCryptoInterface->hashUpdate( pCryptoInterface->pHashContext,
                                                     pHmacContext->key,
                                                     pCryptoInterface->hashBlockLen );
    }

    if( returnStatus == 0 )
    {
        /* Update hash using the inner digest. */
        returnStatus = pCryptoInterface->hashUpdate( pCryptoInterface->pHashContext,
                                                     innerHashDigest,
                                                     pCryptoInterface->hashDigestLen );
    }

    if( returnStatus == 0 )
    {
        /* Write the final HMAC value. */
        returnStatus = pCryptoInterface->hashFinal( pCryptoInterface->pHashContext,
                                                    ( uint8_t * ) pMac,
                                                    macLen );
    }

    /* Reset the HMAC context. */
    pHmacContext->keyLen = 0U;

    return returnStatus;
}

static SigV4Status_t writeLineToCanonicalRequest( const char * pLine,
                                                  size_t lineLen,
                                                  CanonicalContext_t * pCanonicalContext )
{
    SigV4Status_t returnStatus = SigV4Success;

    assert( pLine != NULL );
    assert( ( pCanonicalContext != NULL ) && ( pCanonicalContext->pBufCur != NULL ) );

    /* Make sure that there is space for the Method and the newline character.*/
    if( pCanonicalContext->bufRemaining < ( lineLen + 1U ) )
    {
        returnStatus = SigV4InsufficientMemory;
    }
    else
    {
        ( void ) memcpy( pCanonicalContext->pBufCur,
                         pLine,
                         lineLen );
        pCanonicalContext->pBufCur += lineLen;

        *( pCanonicalContext->pBufCur ) = LINEFEED_CHAR;
        pCanonicalContext->pBufCur += 1U;

        pCanonicalContext->bufRemaining -= ( lineLen + 1U );
    }

    return returnStatus;
}

static int32_t completeHmac( HmacContext_t * pHmacContext,
                             const char * pKey,
                             size_t keyLen,
                             const char * pData,
                             size_t dataLen,
                             char * pOutput,
                             size_t outputLen )
{
    int32_t returnStatus = 0;

    assert( pHmacContext != NULL );
    assert( pKey != NULL );
    assert( keyLen > 0U );
    assert( pData != NULL );
    assert( dataLen > 0U );
    assert( pOutput != NULL );
    assert( outputLen >= pHmacContext->pCryptoInterface->hashDigestLen );

    returnStatus = hmacAddKey( pHmacContext,
                               pKey,
                               keyLen,
                               false /* Not a key prefix. */ );

    if( returnStatus == 0 )
    {
        returnStatus = hmacIntermediate( pHmacContext, pData, dataLen );
    }

    if( returnStatus == 0 )
    {
        returnStatus = hmacFinal( pHmacContext, pOutput, outputLen );
    }

    return returnStatus;
}

static size_t writeStringToSignPrefix( char * pBufStart,
                                       const char * pAlgorithm,
                                       size_t algorithmLen,
                                       const char * pDateIso8601 )
{
    char * pBuffer = pBufStart;

    assert( pBufStart != NULL );
    assert( pAlgorithm != NULL );
    assert( pDateIso8601 != NULL );

    /* Need to write all substrings that come before the hash in the string to sign. */

    /* Write HMAC and hashing algorithm used for SigV4 authentication. */
    ( void ) memcpy( pBuffer, pAlgorithm, algorithmLen );
    pBuffer += algorithmLen;

    *pBuffer = LINEFEED_CHAR;
    pBuffer += 1U;

    /* Concatenate entire ISO 8601 date string. */
    ( void ) memcpy( pBuffer, pDateIso8601, SIGV4_ISO_STRING_LEN );
    pBuffer += SIGV4_ISO_STRING_LEN;

    *pBuffer = LINEFEED_CHAR;

    return algorithmLen + 1U + SIGV4_ISO_STRING_LEN + 1U;
}

static SigV4Status_t writeStringToSign( const SigV4Parameters_t * pParams,
                                        const char * pAlgorithm,
                                        size_t algorithmLen,
                                        CanonicalContext_t * pCanonicalContext )
{
    SigV4Status_t returnStatus = SigV4Success;
    char * pBufStart = ( char * ) pCanonicalContext->pBufProcessing;
    ptrdiff_t bufferLen = pCanonicalContext->pBufCur - pBufStart;
    /* An overestimate but sufficient memory is checked before proceeding. */
    size_t encodedLen = SIGV4_PROCESSING_BUFFER_LENGTH;

    /* The string to sign is composed of (+ means string concatenation):
     * Algorithm + \n +
     * RequestDateTime + \n +
     * CredentialScope + \n +
     * HashedCanonicalRequest
     *
     * The processing buffer is verified beforehand that it has enough
     * space to hold this string. */
    size_t sizeNeededBeforeHash = algorithmLen + 1U + \
                                  SIGV4_ISO_STRING_LEN + 1U;

    assert( pParams != NULL );
    assert( ( pAlgorithm != NULL ) && ( algorithmLen > 0 ) );
    assert( pCanonicalContext != NULL );

    sizeNeededBeforeHash += sizeNeededForCredentialScope( pParams ) + 1U;

    /* Check if there is enough space for the string to sign. */
    if( ( sizeNeededBeforeHash + ( pParams->pCryptoInterface->hashDigestLen * 2U ) ) >
        SIGV4_PROCESSING_BUFFER_LENGTH )
    {
        returnStatus = SigV4InsufficientMemory;
        LOG_INSUFFICIENT_MEMORY_ERROR( "for string to sign",
                                       sizeNeededBeforeHash + ( pParams->pCryptoInterface->hashDigestLen * 2U ) - SIGV4_PROCESSING_BUFFER_LENGTH );
    }
    else
    {
        /* Hash the canonical request to its precalculated location in the string to sign. */
        returnStatus = completeHashAndHexEncode( pBufStart,
                                                 ( size_t ) bufferLen,
                                                 pBufStart + sizeNeededBeforeHash,
                                                 &encodedLen,
                                                 pParams->pCryptoInterface );
    }

    if( returnStatus == SigV4Success )
    {
        size_t bytesWritten = 0U;
        SigV4String_t credentialScope;

        pCanonicalContext->pBufCur = pBufStart + sizeNeededBeforeHash + encodedLen;
        pCanonicalContext->bufRemaining = SIGV4_PROCESSING_BUFFER_LENGTH - encodedLen - sizeNeededBeforeHash;

        bytesWritten = writeStringToSignPrefix( pBufStart,
                                                pAlgorithm,
                                                algorithmLen,
                                                pParams->pDateIso8601 );
        pBufStart += bytesWritten;
        credentialScope.pData = pBufStart;
        credentialScope.dataLen = sizeNeededForCredentialScope( pParams );
        /* Concatenate credential scope. */
        ( void ) generateCredentialScope( pParams, &credentialScope );
        pBufStart += credentialScope.dataLen;
        /* Concatenate linefeed character. */
        *pBufStart = LINEFEED_CHAR;
    }

    if( returnStatus == SigV4Success )
    {
        LogDebug( ( "Generated String To Sign Key: %.*s",
                    ( unsigned int ) ( pCanonicalContext->pBufCur - pBufStart ),
                    pBufStart ) );
    }

    return returnStatus;
}

static SigV4Status_t generateCanonicalRequestUntilHeaders( const SigV4Parameters_t * pParams,
                                                           CanonicalContext_t * pCanonicalContext,
                                                           char ** pSignedHeaders,
                                                           size_t * pSignedHeadersLen )
{
    SigV4Status_t returnStatus = SigV4Success;
    const char * pPath = NULL;
    size_t pathLen = 0U;

    /* Set defaults for path and algorithm. */
    if( ( pParams->pHttpParameters->pPath == NULL ) ||
        ( pParams->pHttpParameters->pathLen == 0U ) )
    {
        /* If the absolute path is empty, use a forward slash (/). */
        pPath = HTTP_EMPTY_PATH;
        pathLen = HTTP_EMPTY_PATH_LEN;
    }
    else
    {
        pPath = pParams->pHttpParameters->pPath;
        pathLen = pParams->pHttpParameters->pathLen;
    }

    pCanonicalContext->pBufCur = ( char * ) pCanonicalContext->pBufProcessing;
    pCanonicalContext->bufRemaining = SIGV4_PROCESSING_BUFFER_LENGTH;

    /* Write the HTTP Request Method to the canonical request. */
    returnStatus = writeLineToCanonicalRequest( pParams->pHttpParameters->pHttpMethod,
                                                pParams->pHttpParameters->httpMethodLen,
                                                pCanonicalContext );

    if( returnStatus == SigV4Success )
    {
        /* Write the URI to the canonical request. */
        if( FLAG_IS_SET( pParams->pHttpParameters->flags, SIGV4_HTTP_PATH_IS_CANONICAL_FLAG ) )
        {
            /* URI is already canonicalized, so just write it to the buffer as is. */
            returnStatus = writeLineToCanonicalRequest( pPath,
                                                        pathLen,
                                                        pCanonicalContext );
        }
        else if( ( pParams->serviceLen == S3_SERVICE_NAME_LEN ) &&
                 ( strncmp( pParams->pService, S3_SERVICE_NAME, S3_SERVICE_NAME_LEN ) == 0 ) )
        {
            /* S3 is the only service in which the URI must only be encoded once. */
            returnStatus = generateCanonicalURI( pPath, pathLen,
                                                 false /* Do not encode twice. */,
                                                 pCanonicalContext );
        }
        else
        {
            returnStatus = generateCanonicalURI( pPath, pathLen,
                                                 true /* Encode twice */,
                                                 pCanonicalContext );
        }
    }

    if( returnStatus == SigV4Success )
    {
        /* Write the query to the canonical request. */
        if( FLAG_IS_SET( pParams->pHttpParameters->flags, SIGV4_HTTP_QUERY_IS_CANONICAL_FLAG ) &&
            ( pParams->pHttpParameters->pQuery != NULL ) )
        {
            /* HTTP query is already canonicalized, so just write it to the buffer as is. */
            returnStatus = writeLineToCanonicalRequest( pParams->pHttpParameters->pQuery,
                                                        pParams->pHttpParameters->queryLen,
                                                        pCanonicalContext );
        }
        else
        {
            returnStatus = generateCanonicalQuery( pParams->pHttpParameters->pQuery,
                                                   pParams->pHttpParameters->queryLen,
                                                   pCanonicalContext );
        }
    }

    if( returnStatus == SigV4Success )
    {
        /* Canonicalize original HTTP headers before writing to buffer. */
        returnStatus = generateCanonicalAndSignedHeaders( pParams->pHttpParameters->pHeaders,
                                                          pParams->pHttpParameters->headersLen,
                                                          pParams->pHttpParameters->flags,
                                                          pCanonicalContext,
                                                          pSignedHeaders,
                                                          pSignedHeadersLen );
    }

    return returnStatus;
}


static SigV4Status_t generateAuthorizationValuePrefix( const SigV4Parameters_t * pParams,
                                                       const char * pAlgorithm,
                                                       size_t algorithmLen,
                                                       const char * pSignedHeaders,
                                                       size_t signedHeadersLen,
                                                       char * pAuthBuf,
                                                       size_t * pAuthPrefixLen )
{
    SigV4Status_t returnStatus = SigV4Success;
    SigV4String_t credentialScope;
    size_t authPrefixLen = 0U;
    size_t numOfBytesWritten = 0U;

    assert( pParams != NULL );
    assert( pAlgorithm != NULL );
    assert( algorithmLen > 0 );
    assert( pSignedHeaders != NULL );
    assert( signedHeadersLen > 0 );
    assert( pAuthBuf != NULL );
    assert( pAuthPrefixLen != NULL );

    /* Since the signed headers are required to be a part of final Authorization header value,
     * we copy the signed headers onto the auth buffer before continuing to generate the signature
     * in order to prevent an additional copy and/or usage of extra space. */
    size_t encodedSignatureLen = ( pParams->pCryptoInterface->hashDigestLen * 2U );

    /* Check if the authorization buffer has enough space to hold the final SigV4 Authorization header value. */
    authPrefixLen = algorithmLen + SPACE_CHAR_LEN +                                            \
                    AUTH_CREDENTIAL_PREFIX_LEN + pParams->pCredentials->accessKeyIdLen +       \
                    CREDENTIAL_SCOPE_SEPARATOR_LEN + sizeNeededForCredentialScope( pParams ) + \
                    AUTH_SEPARATOR_LEN + AUTH_SIGNED_HEADERS_PREFIX_LEN + signedHeadersLen +   \
                    AUTH_SEPARATOR_LEN + AUTH_SIGNATURE_PREFIX_LEN;

    if( *pAuthPrefixLen < ( authPrefixLen + encodedSignatureLen ) )
    {
        LogError( ( "Insufficient memory provided to write the Authorization header value, bytesExceeded=%lu",
                    ( unsigned long ) ( authPrefixLen + encodedSignatureLen - *pAuthPrefixLen ) ) );
        returnStatus = SigV4InsufficientMemory;
    }
    else
    {
        /* START:  Writing of authorization value prefix. */
        /******************* Write <algorithm> *******************************************/
        ( void ) memcpy( pAuthBuf, pAlgorithm, algorithmLen );
        numOfBytesWritten += algorithmLen;

        /* Add space separator. */
        pAuthBuf[ numOfBytesWritten ] = SPACE_CHAR;
        numOfBytesWritten += SPACE_CHAR_LEN;

        /**************** Write "Credential=<access key ID>/<credential scope>, " ****************/
        numOfBytesWritten += copyString( ( pAuthBuf + numOfBytesWritten ), AUTH_CREDENTIAL_PREFIX, AUTH_CREDENTIAL_PREFIX_LEN );
        ( void ) memcpy( ( pAuthBuf + numOfBytesWritten ),
                         pParams->pCredentials->pAccessKeyId,
                         pParams->pCredentials->accessKeyIdLen );
        numOfBytesWritten += pParams->pCredentials->accessKeyIdLen;

        pAuthBuf[ numOfBytesWritten ] = CREDENTIAL_SCOPE_SEPARATOR;
        numOfBytesWritten += CREDENTIAL_SCOPE_SEPARATOR_LEN;
        credentialScope.pData = ( pAuthBuf + numOfBytesWritten );
        /* #authBufLen is an overestimate but the validation was already done earlier. */
        credentialScope.dataLen = *pAuthPrefixLen;
        ( void ) generateCredentialScope( pParams, &credentialScope );
        numOfBytesWritten += credentialScope.dataLen;

        /* Add separator before the Signed Headers information. */
        numOfBytesWritten += copyString( pAuthBuf + numOfBytesWritten, AUTH_SEPARATOR, AUTH_SEPARATOR_LEN );


        /************************ Write "SignedHeaders=<signedHeaders>, " *******************************/
        numOfBytesWritten += copyString( pAuthBuf + numOfBytesWritten, AUTH_SIGNED_HEADERS_PREFIX, AUTH_SIGNED_HEADERS_PREFIX_LEN );
        ( void ) memcpy( pAuthBuf + numOfBytesWritten, pSignedHeaders, signedHeadersLen );
        numOfBytesWritten += signedHeadersLen;

        /* Add separator before the Signature field name. */
        numOfBytesWritten += copyString( pAuthBuf + numOfBytesWritten, AUTH_SEPARATOR, AUTH_SEPARATOR_LEN );

        /****************************** Write "Signature=<signature>" *******************************/
        numOfBytesWritten += copyString( pAuthBuf + numOfBytesWritten, AUTH_SIGNATURE_PREFIX, AUTH_SIGNATURE_PREFIX_LEN );

        /* END: Writing of authorization value prefix. */

        /* Avoid warnings from last write if asserts are disabled. */
        ( void ) numOfBytesWritten;
        assert( authPrefixLen == numOfBytesWritten );
        *pAuthPrefixLen = authPrefixLen;
    }

    return returnStatus;
}


static SigV4Status_t generateSigningKey( const SigV4Parameters_t * pSigV4Params,
                                         HmacContext_t * pHmacContext,
                                         SigV4String_t * pSigningKey,
                                         size_t * pBytesRemaining )
{
    SigV4Status_t returnStatus = SigV4Success;
    int32_t hmacStatus = 0;
    char * pSigningKeyStart = NULL;

    assert( pSigV4Params != NULL );
    assert( pHmacContext != NULL );
    assert( pSigningKey != NULL );
    assert( pBytesRemaining != NULL );

    /* To calculate the final signing key, this function needs at least enough
     * buffer to hold the length of two digests since one digest is used to
     * calculate the other. */
    if( *pBytesRemaining < ( pSigV4Params->pCryptoInterface->hashDigestLen * 2U ) )
    {
        returnStatus = SigV4InsufficientMemory;
        LOG_INSUFFICIENT_MEMORY_ERROR( "generate signing key",
                                       ( pSigV4Params->pCryptoInterface->hashDigestLen * 2U ) - *pBytesRemaining );
    }

    if( returnStatus != SigV4InsufficientMemory )
    {
        /* Fill the key prefix, "AWS4" in the HMAC context cache.
         * The prefix is part of the key for the first round of HMAC operation:
         * HMAC("AWS4" + SecretKey, Date) */
        hmacStatus = hmacAddKey( pHmacContext,
                                 SIGV4_HMAC_SIGNING_KEY_PREFIX,
                                 SIGV4_HMAC_SIGNING_KEY_PREFIX_LEN,
                                 true /* Is key prefix. */ );

        /* The above call should always succeed as it only populates the HMAC key cache. */
        assert( hmacStatus == 0 );
    }

    if( ( returnStatus != SigV4InsufficientMemory ) && ( hmacStatus == 0 ) )
    {
        hmacStatus = completeHmac( pHmacContext,
                                   pSigV4Params->pCredentials->pSecretAccessKey,
                                   pSigV4Params->pCredentials->secretAccessKeyLen,
                                   pSigV4Params->pDateIso8601,
                                   ISO_DATE_SCOPE_LEN,
                                   pSigningKey->pData,
                                   pSigningKey->dataLen );
        *pBytesRemaining -= pSigV4Params->pCryptoInterface->hashDigestLen;
    }

    if( ( returnStatus != SigV4InsufficientMemory ) && ( hmacStatus == 0 ) )
    {
        pSigningKeyStart = pSigningKey->pData + pSigV4Params->pCryptoInterface->hashDigestLen + 1U;
        hmacStatus = completeHmac( pHmacContext,
                                   pSigningKey->pData,
                                   pSigV4Params->pCryptoInterface->hashDigestLen,
                                   pSigV4Params->pRegion,
                                   pSigV4Params->regionLen,
                                   pSigningKeyStart,
                                   *pBytesRemaining );
        *pBytesRemaining -= pSigV4Params->pCryptoInterface->hashDigestLen;
    }

    if( ( returnStatus != SigV4InsufficientMemory ) && ( hmacStatus == 0 ) )
    {
        hmacStatus = completeHmac( pHmacContext,
                                   pSigningKeyStart,
                                   pSigV4Params->pCryptoInterface->hashDigestLen,
                                   pSigV4Params->pService,
                                   pSigV4Params->serviceLen,
                                   pSigningKey->pData,
                                   pSigV4Params->pCryptoInterface->hashDigestLen );
    }

    if( ( returnStatus != SigV4InsufficientMemory ) && ( hmacStatus == 0 ) )
    {
        hmacStatus = completeHmac( pHmacContext,
                                   pSigningKey->pData,
                                   pSigV4Params->pCryptoInterface->hashDigestLen,
                                   CREDENTIAL_SCOPE_TERMINATOR,
                                   CREDENTIAL_SCOPE_TERMINATOR_LEN,
                                   pSigningKeyStart,
                                   pSigV4Params->pCryptoInterface->hashDigestLen );
    }

    if( ( returnStatus != SigV4InsufficientMemory ) && ( hmacStatus == 0 ) )
    {
        pSigningKey->pData = pSigningKeyStart;
        pSigningKey->dataLen = pSigV4Params->pCryptoInterface->hashDigestLen;
    }

    /* If there was a hashing error in HMAC operations, set the appropriate error code. */
    if( hmacStatus != 0 )
    {
        returnStatus = SigV4HashError;
    }

    return returnStatus;
}

static SigV4Status_t writePayloadHashToCanonicalRequest( const SigV4Parameters_t * pParams,
                                                         CanonicalContext_t * pCanonicalContext )
{
    size_t encodedLen = 0U;
    SigV4Status_t returnStatus = SigV4Success;

    assert( pParams != NULL );
    assert( pCanonicalContext != NULL );

    if( FLAG_IS_SET( pParams->pHttpParameters->flags, SIGV4_HTTP_PAYLOAD_IS_HASH ) )
    {
        /* Copy the hashed payload data supplied by the user in the headers data list. */
        returnStatus = copyHeaderStringToCanonicalBuffer( pCanonicalContext->pHashPayloadLoc, pCanonicalContext->hashPayloadLen, pParams->pHttpParameters->flags, '\n', pCanonicalContext );
        /* Remove new line at the end of the payload. */
        pCanonicalContext->pBufCur--;
    }
    else
    {
        encodedLen = pCanonicalContext->bufRemaining;
        /* Calculate hash of the request payload. */
        returnStatus = completeHashAndHexEncode( pParams->pHttpParameters->pPayload,
                                                 pParams->pHttpParameters->payloadLen,
                                                 pCanonicalContext->pBufCur,
                                                 &encodedLen,
                                                 pParams->pCryptoInterface );
        pCanonicalContext->pBufCur += encodedLen;
        pCanonicalContext->bufRemaining -= encodedLen;
    }

    return returnStatus;
}


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 )
    {
        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_LEN_RFC_3339 ) &&
             ( dateLen != SIGV4_EXPECTED_LEN_RFC_5322 ) )
    {
        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 )
    {
        LogError( ( "Parameter check failed: dateISO8601Len must be at least %u.",
                    SIGV4_ISO_STRING_LEN ) );
    }
    else
    {
        /* 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;
}

SigV4Status_t SigV4_GenerateHTTPAuthorization( const SigV4Parameters_t * pParams,
                                               char * pAuthBuf,
                                               size_t * authBufLen,
                                               char ** pSignature,
                                               size_t * signatureLen )
{
    SigV4Status_t returnStatus = SigV4Success;
    CanonicalContext_t canonicalContext;
    const char * pAlgorithm = NULL;
    char * pSignedHeaders = NULL;
    size_t algorithmLen = 0U, signedHeadersLen = 0U, authPrefixLen = 0U;
    HmacContext_t hmacContext = { 0 };

    SigV4String_t signingKey;
    ptrdiff_t bufferLen;

    returnStatus = verifyParamsToGenerateAuthHeaderApi( pParams,
                                                        pAuthBuf, authBufLen,
                                                        pSignature, signatureLen );

    if( returnStatus == SigV4Success )
    {
        assignDefaultArguments( pParams, &pAlgorithm, &algorithmLen );
    }

    if( returnStatus == SigV4Success )
    {
        returnStatus = generateCanonicalRequestUntilHeaders( pParams, &canonicalContext,
                                                             &pSignedHeaders,
                                                             &signedHeadersLen );
    }

    /* Hash and hex-encode the canonical request to the buffer. */
    if( returnStatus == SigV4Success )
    {
        /* Write HTTP request payload hash to the canonical request. */
        returnStatus = writePayloadHashToCanonicalRequest( pParams, &canonicalContext );
    }

    /* Write the prefix of the Authorizaton header value. */
    if( returnStatus == SigV4Success )
    {
        LogDebug( ( "Generated Canonical Request: %.*s",
                    ( unsigned int ) ( ( uint8_t * ) canonicalContext.pBufCur - canonicalContext.pBufProcessing ),
                    canonicalContext.pBufProcessing ) );

        authPrefixLen = *authBufLen;
        returnStatus = generateAuthorizationValuePrefix( pParams,
                                                         pAlgorithm, algorithmLen,
                                                         pSignedHeaders, signedHeadersLen,
                                                         pAuthBuf, &authPrefixLen );
    }

    /* Write string to sign. */
    if( returnStatus == SigV4Success )
    {
        returnStatus = writeStringToSign( pParams, pAlgorithm, algorithmLen, &canonicalContext );
    }

    /* Write the signing key. The is done by computing the following function
     * where the + operator means concatenation:
     * HMAC(HMAC(HMAC(HMAC("AWS4" + kSecret,pDate),pRegion),pService),"aws4_request") */
    if( returnStatus == SigV4Success )
    {
        hmacContext.pCryptoInterface = pParams->pCryptoInterface;
        signingKey.pData = canonicalContext.pBufCur;
        signingKey.dataLen = canonicalContext.bufRemaining;
        returnStatus = generateSigningKey( pParams,
                                           &hmacContext,
                                           &signingKey,
                                           &canonicalContext.bufRemaining );
    }

    /* Use the SigningKey and StringToSign to produce the final signature.
     * Note that the StringToSign starts from the beginning of the processing buffer. */
    if( returnStatus == SigV4Success )
    {
        bufferLen = canonicalContext.pBufCur - ( char * ) canonicalContext.pBufProcessing;
        returnStatus = ( completeHmac( &hmacContext,
                                       signingKey.pData,
                                       signingKey.dataLen,
                                       ( char * ) canonicalContext.pBufProcessing,
                                       ( size_t ) bufferLen,
                                       canonicalContext.pBufCur,
                                       pParams->pCryptoInterface->hashDigestLen ) != 0 )
                       ? SigV4HashError : SigV4Success;
    }

    /* Hex-encode the final signature beforehand to its precalculated
     * location in the buffer provided for the Authorizaton header value. */
    if( returnStatus == SigV4Success )
    {
        SigV4String_t originalHmac;
        SigV4String_t hexEncodedHmac;
        originalHmac.pData = canonicalContext.pBufCur;
        originalHmac.dataLen = pParams->pCryptoInterface->hashDigestLen;
        hexEncodedHmac.pData = pAuthBuf + authPrefixLen;
        /* #authBufLen is an overestimate but the validation was already done earlier. */
        hexEncodedHmac.dataLen = *authBufLen;
        returnStatus = lowercaseHexEncode( &originalHmac,
                                           &hexEncodedHmac );
        *pSignature = hexEncodedHmac.pData;
        *signatureLen = hexEncodedHmac.dataLen;
        *authBufLen = authPrefixLen + ( pParams->pCryptoInterface->hashDigestLen << 1U );
    }

    return returnStatus;
}
