AWS S3 HTTPS error SignatureDoesNotMatch

Sorry for the late reply, I have attached my .cpp file with several utility functions.

/******************************************************************************
 Includes
 *****************************************************************************/
#include "aws_http.h"

/******************************************************************************
 Function prototypes
 *****************************************************************************/
void hashByteToHex(unsigned char *hashBuffer, size_t bufferLength, char *result);

/******************************************************************************
 Function definitions
 *****************************************************************************/
/**
 * @brief Converts an unsigned 32-bit integer to a hexadecimal string.
 *
 * @param integer The integer to be converted.
 * @param result A pointer to the buffer that will contain the hexadecimal string.
 * @return None
 */
void uintToHex(uint32_t integer, char *result)
{
    char hexStr[MAX_UINT32_TO_HEX_LENGTH] = {0};
    sprintf(hexStr, "%04X", integer);
    /* Remove leading zeros from hex string */
    int i;
    for (i = 0; i < 3; i++)
    {
        if (hexStr[i] != '0')
        {
            break;
        }
    }
    strcpy(result, &hexStr[i]);
}

/**
 * @brief Converts a byte buffer to a hexadecimal string representation.
 *
 * @param hashBuffer A pointer to the buffer containing the bytes to be converted.
 * @param bufferLength The length of the byte buffer.
 * @param result A pointer to the buffer that will contain the hexadecimal string.
 * @return None
 */
void hashByteToHex(unsigned char *hashBuffer, size_t bufferLength, char *result)
{
    for (int i = 0; i < bufferLength; i++)
    {
        /* Converting the bytes and storing them in hex buffer */
        sprintf(result + (i * 2), "%02x", (int)hashBuffer[i]);
    }
    // Serial.printf("Hex hash: %s\r\n", result);
}

// https://techtutorialsx.com/2018/01/25/esp32-arduino-applying-the-hmac-sha-256-mechanism/
/**
 * @brief Generates a SHA256 hash of a given payload.
 *
 * @param payload A pointer to the payload to be hashed.
 * @param payloadLength The length of the payload.
 * @param result A pointer to the buffer that will contain the generated hash.
 * @return None
 */
void generateSHA256Hash(const char *payload, size_t payloadLength, char *result)
{
    /* Instantiating hash context structure */
    mbedtls_md_context_t ctx;
    /* Defining hash type */
    mbedtls_md_type_t mdType = MBEDTLS_MD_SHA256;

    /* Initialize the hash calculator structur */
    mbedtls_md_init(&ctx);
    /* Setup the hash type */
    mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(mdType), 0);
    /* Start the message-digest computation */
    mbedtls_md_starts(&ctx);
    /* Provide the payload to be hashed */
    mbedtls_md_update(&ctx, (const unsigned char *)payload, payloadLength);
    /* Perform the hash process */
    mbedtls_md_finish(&ctx, (unsigned char *)result);
    /* Free memory allocated for has process */
    mbedtls_md_free(&ctx);
}

/**
 * @brief Generates an HMAC-SHA256 hash of the provided payload using the provided key.
 *
 * @param hmacKey A pointer to the key used for hashing.
 * @param keyLength The length of the key used for hashing.
 * @param payload A pointer to the payload to be hashed.
 * @param payloadLength The length of the payload to be hashed.
 * @param result A pointer to the buffer that will contain the generated hash.
 * @return None
 */
void generateHmacSHA256Hash(const char *hmacKey, size_t keyLength, const char *payload, size_t payloadLength, char *result)
{
    /* Instantiating hash context structure */
    mbedtls_md_context_t ctx;
    /* Defining hash type */
    mbedtls_md_type_t mdType = MBEDTLS_MD_SHA256;

    /* Initialize the hash calculator structur */
    mbedtls_md_init(&ctx);
    /* Setup the hash type */
    mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(mdType), 1);
    /* Proivde the key used for hashing */
    mbedtls_md_hmac_starts(&ctx, (const unsigned char *)hmacKey, keyLength);
    /* Provide the payload to be hashed */
    mbedtls_md_hmac_update(&ctx, (const unsigned char *)payload, payloadLength);
    /* Perform the hash process */
    mbedtls_md_hmac_finish(&ctx, (unsigned char *)result);
    /* Free memory allocated for has process */
    mbedtls_md_free(&ctx);
}

/**
 * @brief Extracts the hexadecimal payload hash from a canonical request.
 *
 * @param canonicalRequest A string containing the canonical request.
 * @param result A pointer to the buffer that will contain the extracted hash.
 * @return None
 */
void extractPayloadHexHash(const char *canonicalRequest, char *result)
{
    /* Find the start of the payload hash */
    const char *payloadHashStart = strstr(canonicalRequest, "x-amz-content-sha256:");
    const char *payloadHashEnd;
    size_t payloadHashLength = 0;
    if (!payloadHashStart)
    {
        /* Payload hash header not found */
        return;
    }
    /* Move pointer to start of the hash */
    payloadHashStart += strlen("x-amz-content-sha256:");
    payloadHashEnd = payloadHashStart;
    /* Move pointer to the end of the line */
    while (*payloadHashEnd != '\n')
    {
        payloadHashEnd++;
        payloadHashLength++;
    }
    /* Extract the payload hash into a new C string and add 0 pointer */
    memcpy(result, payloadHashStart, payloadHashLength);
    result[payloadHashLength] = '\0';
}

/**
 * @brief Extracts the signed headers list from the canonical request.
 *
 * @param canonicalRequest The canonical request to extract the signed headers from
 * @param canonicalRequestLength The length of the canonical request
 * @param endLine The position of the last newline in the canonical request header section
 * @param result Pointer to a buffer to store the extracted signed headers list
 */
void extractSignedHeaderList(char *canonicalRequest, size_t canonicalRequestLength, size_t endLine, char *result)
{
    char *signedHeaderListPos = canonicalRequest + canonicalRequestLength - endLine - 2;
    int signedHeaderLength = 0;
    /* Moving backwards through the signed header list counting the chars until the previous \n is found */
    while (*signedHeaderListPos != '\n')
    {
        signedHeaderListPos--;
        signedHeaderLength++;
    }
    /* The while loop iterates one iteration too often */
    signedHeaderListPos++;
    memcpy(result, signedHeaderListPos, signedHeaderLength);
    result[signedHeaderLength] = '\0';
}

// https://docs.aws.amazon.com/general/latest/gr/create-signed-request.html
// https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
/* Step 1: Create a canonical request */
/**
 * @brief Creates the canonical GET request
 * 
 * @param payload The request payload
 * @param object The S3 object name
 * @param query The S3 query string
 * @param contentType The content type of the payload
 * @param host The S3 bucket endpoint
 * @param date The request date
 * @param time The request time
 * @param result The output string containing the canonical request
 * @param sizeResult The size of the output string
 * @return true if the canonical request was created successfully, false otherwise
 */
bool canonicalGetRequest(const char *payload, const char *object, const char *query, const char *contentType,
                         const char *host, const char *date, const char *time, char *result, size_t sizeResult)
{
    bool ret = false;
    char payloadHash[MAX_BYTE_SHA256_HASH_LENGTH] = {0};
    char payloadHexHash[MAX_HEX_HASH_LENGTH] = {0};
    int snprintf_ret = 0;

    /* Stores an SHA256 hash */
    generateSHA256Hash(payload, strlen(payload), payloadHash);
    /* Convert the bytes into a hexadecimal string */
    hashByteToHex((unsigned char *)payloadHash, sizeof(payloadHash), payloadHexHash);

    snprintf_ret = snprintf(result,
                            sizeResult,
                            "GET\n"
                            "/%s\n"
                            "%s\n"
                            "content-type:%s\n"
                            "host:%s\n"
                            "x-amz-content-sha256:%s\n"
                            "x-amz-date:%sT%sZ\n"
                            "\n"
                            "content-type;host;x-amz-content-sha256;x-amz-date\n"
                            "%s",
                            object, query, contentType, host, payloadHexHash, date, time, payloadHexHash);
    if (snprintf_ret < 0)
    {
        return ret;
    }
    else if (snprintf_ret >= sizeResult)
    {
        Serial.printf("Canonical request failed: %d\r\n", snprintf_ret);
        return ret;
    }
    else
    {
        ret = true;
        return ret;
    }
}

/* Step 1: Create a canonical request */
/**
 * @brief Generates a canonical request for a PUT operation and stores it in the result buffer.
 *
 * @param payload        the payload of the PUT request.
 * @param object         the S3 object being PUT.
 * @param query          the query string (without the '?') to add to the object name.
 * @param host           the S3 endpoint being accessed.
 * @param date           the date in ISO 8601 format (YYYYMMDD).
 * @param time           the time in ISO 8601 format (HHMMSSZ).
 * @param storageClass   the S3 storage class to use for the object.
 * @param result         the buffer to store the canonical request in.
 * @param sizeResult     the size of the result buffer.
 *
 * @return True if the canonical request was generated successfully, false otherwise.
 */
bool canonicalPutRequest(const char *payload, const char *object, const char *query, const char *host,
                         const char *date, const char *time, const char *storageClass, char *result,
                         size_t sizeResult)
{
    bool ret = false;
    char payloadHash[32] = {0};
    char payloadHexHash[65] = {0};
    int snprintf_ret = 0;
    /* Stores an SHA256 hash */
    generateSHA256Hash(payload, strlen(payload), payloadHash);
    /* Convert the bytes into a hexadecimal string */
    hashByteToHex((unsigned char *)payloadHash, sizeof(payloadHash), payloadHexHash);
    snprintf_ret = snprintf(result,
                            sizeResult,
                            "PUT\n"
                            "/%s\n"
                            "%s\n"
                            "host:%s\n"
                            "x-amz-content-sha256:%s\n"
                            "x-amz-date:%sT%sZ\n"
                            "x-amz-storage-class:%s\n"
                            "\n"
                            "host;x-amz-content-sha256;x-amz-date;x-amz-storage-class\n"
                            "%s",
                            object, query, host, payloadHexHash, date, time, storageClass, payloadHexHash);
    if (snprintf_ret < 0)
    {
        return ret;
    }
    else if (snprintf_ret >= sizeResult)
    {
        Serial.printf("Canonical request failed: %d\r\n", snprintf_ret);
        return ret;
    }
    else
    {
        ret = true;
        return ret;
    }
}

/* Step 1: Create a canonical request */
/**
 * @brief Constructs the canonical request for an HTTP Put request (e.g. to AWS S3).
 *
 * @param object The S3 object key
 * @param query Query string to include in the request
 * @param contentLength Total content length of the PUT request
 * @param host The S3 bucket host address
 * @param date The date of the request in ISO8601 format
 * @param time The time of the request in ISO8601 format
 * @param payloadLength The length of the actual data to be stored in the destination file
 * @param storageClass The AWS storage class
 * @param result The buffer to store the canonical request
 * @param sizeResult The size of the buffer for the canonical request
 *
 * @return True if successful, false otherwise
 */
bool seedCanonicalPutRequest(const char *object, const char *query, uint32_t contentLength, const char *host,
                             const char *date, const char *time, uint32_t payloadLength, const char *storageClass,
                             char *result, size_t sizeResult)
{
    bool ret = false;
    int snprintf_ret = 0;
    /* Payload lenght relates to the the actual data to be stored in the destination file
    Content length is payload length + meta data length! */
    snprintf_ret = snprintf(result,
                            sizeResult,
                            "PUT\n"
                            "/%s\n"
                            "%s\n"
                            "content-encoding:aws-chunked\n"
                            "content-length:%lu\n"
                            "host:%s\n"
                            "x-amz-content-sha256:STREAMING-AWS4-HMAC-SHA256-PAYLOAD\n"
                            "x-amz-date:%sT%sZ\n"
                            "x-amz-decoded-content-length:%lu\n"
                            "x-amz-storage-class:%s\n"
                            "\n"
                            "content-encoding;content-length;host;x-amz-content-sha256;x-amz-date;x-amz-decoded-content-length;x-amz-storage-class\n"
                            "STREAMING-AWS4-HMAC-SHA256-PAYLOAD",
                            object, query, contentLength, host, date, time, payloadLength, storageClass);
    if (snprintf_ret < 0)
    {
        return ret;
    }
    else if (snprintf_ret >= sizeResult)
    {
        Serial.printf("Canonical request failed: %d\r\n", snprintf_ret);
        return ret;
    }
    else
    {
        ret = true;
        return ret;
    }
}

/* Step 2: Create a canonical request hash */
/**
 * @brief Generates an SHA256 hash for the canonical request string and
 *        stores the resulting hash as a hexadecimal string.
 * 
 * @param canoncialRequest The canonical request string
 * @param result The buffer to store the resulting hash as a hexadecimal string
 */
void canoncialRequestHash(const char *canoncialRequest, char *result)
{
    char payloadHash[MAX_BYTE_SHA256_HASH_LENGTH] = {0};
    /* Stores an SHA256 hash in hex format */
    generateSHA256Hash(canoncialRequest, strlen(canoncialRequest), payloadHash);
    /* Convert the bytes into a hexadecimal string */
    hashByteToHex((unsigned char *)payloadHash, sizeof(payloadHash), result);
}

/**
 * @brief Calculates the string to sign for AWS S3 REST requests with streaming body.
 *        The string to sign is required for the signature calculation of the REST request.
 * 
 * @param payload pointer to the buffer containing the payload to be sent in the request.
 * @param payloadLength length of the payload in bytes.
 * @param prevSignature the signature calculated for previous chunks, or for the full payload.
 * @param date pointer to the date string in ISO8601 format, as required by AWS.
 * @param time pointer to the time string in ISO8601 format, as required by AWS.
 * @param region pointer to the region string, as required by AWS.
 * @param service pointer to the service string, as required by AWS.
 * @param result pointer to the buffer where the result string will be stored.
 * 
 * @note The result buffer must have at least MAX_STRING2SIGN_STREAM_LENGTH bytes of space.
 */
void stringToSignStream(const char *payload, size_t payloadLength, const char *prevSignature, const char *date,
                        const char *time, const char *region, const char *service, char *result)
{
    char payloadHash[MAX_BYTE_SHA256_HASH_LENGTH] = {0};
    char payloadHexHash[MAX_HEX_HASH_LENGTH] = {0};

    /* Stores an SHA256 hash */
    generateSHA256Hash(payload, payloadLength, payloadHash);
    /* Convert the bytes into a hexadecimal string */
    hashByteToHex((unsigned char *)payloadHash, sizeof(payloadHash), payloadHexHash);

    snprintf(result, MAX_STRING2SIGN_STREAM_LENGTH,
             "AWS4-HMAC-SHA256-PAYLOAD\n"
             "%sT%sZ\n"
             "%s/%s/%s/aws4_request\n"
             "%s\n"
             "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\n"
             "%s",
             date, time, date, region, service, prevSignature, payloadHexHash);
}

/* Step 3: Create a string to sign */
/**
 * @brief Generates the string to sign for AWS request signing.
 * 
 * @param algorithm The signing algorithm.
 * @param date The date of the request.
 * @param time The time of the request.
 * @param region The AWS region of the request.
 * @param service The AWS service of the request.
 * @param hashedCanonicalRequest The hashed canonical request of the request.
 * @param result The resulting string to sign.
 * @param sizeResult The size of the result buffer.
 */
void stringToSign(const char *algorithm, const char *date, const char *time, const char *region, const char *service,
                  const char *hashedCanonicalRequest, char *result, size_t sizeResult)
{
    snprintf(result,
             sizeResult,
             "%s\n%sT%sZ\n%s/%s/%s/aws4_request\n%s",
             algorithm, date, time, date, region, service, hashedCanonicalRequest);
}

/* Step 4: Calculate the signature */
/**
 * @brief Calculates the signature for an AWS4 request
 *
 * This function takes in the AWS4 secret key, date, region, service, and stringToSign and uses them
 * to calculate the signature for an AWS4 request. The signature is stored in the result argument.
 *
 * @param AWS4secretKey The AWS4 secret key to be used for signature calculation
 * @param date The date to be used for signature calculation
 * @param region The region to be used for signature calculation
 * @param service The service to be used for signature calculation
 * @param stringToSign The string to be used for signature calculation
 * @param result The result string to store the signature
 */
void signatureCalculation(const char *AWS4secretKey, const char *date, const char *region, const char *service,
                          const char *stringToSign, char *result)
{
    char kDate[MAX_BYTE_SHA256_HASH_LENGTH] = {0};
    char kRegion[MAX_BYTE_SHA256_HASH_LENGTH] = {0};
    char kService[MAX_BYTE_SHA256_HASH_LENGTH] = {0};
    char kSigning[MAX_BYTE_SHA256_HASH_LENGTH] = {0};
    char kSignature[MAX_BYTE_SHA256_HASH_LENGTH] = {0};
    /* Calculate date key */
    generateHmacSHA256Hash(AWS4secretKey, strlen(AWS4secretKey), date, strlen(date), kDate);
    /* Convert hash result into hex string and print it */
    hashByteToHex((unsigned char *)kDate, MAX_BYTE_SHA256_HASH_LENGTH, result);

    /* Calculate region key */
    generateHmacSHA256Hash(kDate, MAX_BYTE_SHA256_HASH_LENGTH, region, strlen(region), kRegion);
    /* Convert hash result into hex string and print it */
    hashByteToHex((unsigned char *)kRegion, MAX_BYTE_SHA256_HASH_LENGTH, result);

    /* Calculate service key */
    generateHmacSHA256Hash(kRegion, MAX_BYTE_SHA256_HASH_LENGTH, service, strlen(service), kService);
    /* Convert hash result into hex string and print it */
    hashByteToHex((unsigned char *)kService, MAX_BYTE_SHA256_HASH_LENGTH, result);

    /* Calculate signing key */
    generateHmacSHA256Hash(kService, MAX_BYTE_SHA256_HASH_LENGTH, "aws4_request", strlen("aws4_request"), kSigning);
    /* Convert hash result into hex string and print it */
    hashByteToHex((unsigned char *)kSigning, MAX_BYTE_SHA256_HASH_LENGTH, result);

    /* Calculate signature */
    generateHmacSHA256Hash(kSigning, MAX_BYTE_SHA256_HASH_LENGTH, stringToSign, strlen(stringToSign), kSignature);
    /* Convert hash result into hex string and print it */
    hashByteToHex((unsigned char *)kSignature, MAX_BYTE_SHA256_HASH_LENGTH, result);
}

/**
 * @brief Creates the chunk body definition string for a given chunk size and signature.
 * 
 * @param chunkSize The size of the chunk in bytes.
 * @param signature The signature of the chunk.
 * @param payload The payload to which the chunk body will be added.
 * 
 * @note The chunk body will be added at the beginning of the payload and the payload will be shifted
 * to make space for it. The resulting payload will end with CRLF and a 0 terminator.
 */
void getChunkPayload(uint32_t chunkSize, const char *signature, char *payload)
{
    char chunkHexString[UNSIGNED_INT_HEX_STR_LENGTH] = {0};
    char chunkBody[MAX_CHUNK_BODY_LENGTH] = {0};
    /* Convert the decoded payload length from int into hex */
    uintToHex(chunkSize, chunkHexString);
    /* Create the chunk body string */
    snprintf(chunkBody, MAX_CHUNK_BODY_LENGTH, "%s;chunk-signature=%s\r\n", chunkHexString, signature);
    size_t chunkBodyLen = strlen(chunkBody);
    /* Shift the payload to make space for the chunk body
    since the destination/source are overlapping memmove() is used */
    memmove(payload + strlen(chunkBody), payload, chunkSize);
    /* Copy the chunk body at the beginning of the payload */
    memcpy(payload, chunkBody, strlen(chunkBody));
    /* Add CRLF and 0 termiantor at the end of the payload */
    payload[chunkSize + strlen(chunkBody)] = '\r';
    payload[chunkSize + strlen(chunkBody) + 1] = '\n';
    payload[chunkSize + strlen(chunkBody) + 2] = '\0';
}

/* Step 5: Add the signature to the request */
/**
 * @brief Generates an AWS authorization header string for a given set of credentials and request parameters
 *
 * This function generates an authorization header string as required by AWS for authenticated requests. The generated
 * authorization header is added to the HTTP request headers to authenticate the request. The AWS signature version 4
 * signing process is used to generate the signature part of the authorization header. This function is used to sign
 * requests to AWS S3 and AWS API Gateway endpoints.
 *
 * @param algorithm       The algorithm used for generating the signature. This should be 'AWS4-HMAC-SHA256'.
 * @param awsAccessKey    The AWS access key to be used for authentication.
 * @param date            The date and time of the request in the format yyyyMMdd'T'HHmmss'Z'.
 * @param region          The AWS region of the service being requested.
 * @param service         The AWS service to be requested.
 * @param signedHeaders   A string with all header names and values that should be included in the signature
 *                        separated by semicolons. This should include all headers that are being sent with the
 *                        request except for 'Authorization'.
 * @param signature       The signature generated using the signing key and string to sign.
 * @param result          A pointer to a buffer to store the generated authorization header.
 * @param sizeResult      The size of the buffer to store the generated authorization header.
 *
 * @note The size of the buffer pointed to by 'result' should be at least 600 bytes to accommodate the longest possible
 * authorization header string.
 *
 * @return None.
 */
void authorizationHeader(const char *algorithm, const char *awsAccessKey, const char *date, const char *region,
                         const char *service, const char *signedHeaders, const char *signature,
                         char *result, size_t sizeResult)
{
    snprintf(result,
             sizeResult,
             "%s Credential=%s/%s/%s/%s/aws4_request, "
             "SignedHeaders=%s, "
             "Signature=%s",
             algorithm, awsAccessKey, date, region, service, signedHeaders, signature);
}

// https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObject.html
/* Step 6: Create an http request */
/**
 * @brief Creates an HTTP GET request header string for AWS S3.
 *
 * @param object The name of the object to get.
 * @param contentType The MIME type of the content to get.
 * @param host The S3 bucket host URL.
 * @param payloadHexHash The hexadecimal hash of the payload.
 * @param date The current date in ISO8601 format.
 * @param time The current time in ISO8601 format.
 * @param authorizationHeader The AWS signature authorization header.
 * @param result The output string for the HTTP GET request header.
 * @param sizeResult The size of the output string.
 *
 * @return true if the function executed successfully, false otherwise.
 *
 * @note The size of the result string must be at least 256 bytes.
 */
bool httpGetReq(const char *object, const char *contentType, const char *host, const char *payloadHexHash,
                const char *date, const char *time, const char *authorizationHeader, char *result,
                size_t sizeResult)
{
    bool ret = false;
    int snprintf_ret = snprintf(result,
                                sizeResult,
                                "GET /%s HTTP/1.1\r\n"
                                "Content-Type: %s\r\n"
                                "Host: %s\r\n"
                                "x-amz-content-sha256: %s\r\n"
                                "x-amz-date: %sT%sZ\r\n"
                                "Authorization: %s\r\n"
                                "User-Agent: ESP-32\r\n"
                                "Cache-Control: no-cache\r\n"
                                "Accept: */*\r\n"
                                "Connection: close\r\n"
                                "\r\n",
                                object, contentType, host, payloadHexHash, date, time, authorizationHeader);
    if (snprintf_ret < 0)
    {
        return ret;
    }
    else if (snprintf_ret >= sizeResult)
    {
        Serial.printf("httpGetReq failed: %d\r\n", snprintf_ret);
        return ret;
    }
    else
    {
        ret = true;
        return ret;
    }
}

// https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html
/* Step 6: Create an http request */
/**
 * @brief Creates an HTTP PUT request header for single chunk data transmission.
 *
 * @param object The name of the object to store in the destination file
 * @param contentType The type of the content being uploaded
 * @param contentLength The length of the content being uploaded (including metadata)
 * @param host The host address to connect to
 * @param payloadHexHash The C-string holding the 32 byte SHA256 hash.
 * @param date The current date in ISO8601 format.
 * @param time The current time in ISO8601 format.
 * @param storageClass The class of storage to be used
 * @param authorizationHeader The authorization header for the request
 * @param result The buffer to write the HTTP request header into
 * @param sizeResult The size of the result buffer
 *
 * @return Returns true if the function is successful, false otherwise
 *
 * @note This function assumes that the result buffer is large enough to hold the entire header
 */
bool httpPutReq(const char *object, const char *contentType, uint32_t contentLength, const char *host,
                const char *payloadHexHash, const char *date, const char *time, const char *storageClass,
                const char *authorizationHeader, char *result, size_t sizeResult)
{
    bool ret = false;
    int snprintf_ret = snprintf(result,
                                sizeResult,
                                "PUT /%s HTTP/1.1\r\n"
                                "Content-Length: %lu\r\n"
                                "Content-Type: %s\r\n"
                                "Host: %s\r\n"
                                "X-Amz-Content-Sha256: %s\r\n"
                                "X-Amz-Date: %sT%sZ\r\n"
                                "X-Amz-Storage-Class: %s\r\n"
                                "Authorization: %s\r\n"
                                "User-Agent: ESP32\r\n"
                                "Accept: */*\r\n"
                                "Cache-Control: no-cache\r\n"
                                "Expect: 100-continue\r\n"
                                "Connection: keep-alive\r\n"
                                "\r\n",
                                object, contentLength, contentType, host, payloadHexHash,
                                date, time, storageClass, authorizationHeader);
    Serial.printf("HTTP header length: %d\r\n", snprintf_ret);
    if (snprintf_ret < 0)
    {
        return ret;
    }
    else if (snprintf_ret >= sizeResult)
    {
        Serial.printf("httpPutReq failed: %d\r\n", snprintf_ret);
        return ret;
    }
    else
    {
        ret = true;
        return ret;
    }
}

// https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html
/* Step 6: Create an http request */
/**
 * @brief Creates an HTTP PUT request header with necessary metadata for chunked data transmission.
 *
 * This function creates an HTTP PUT request header with the necessary metadata including content length,
 * content type, host, date, time, and authorization headers. The header is written into the result buffer.
 * The function returns true if successful, false otherwise.
 *
 * @param object The name of the object to store in the destination file
 * @param contentType The type of the content being uploaded
 * @param contentLength The length of the content being uploaded (including metadata)
 * @param host The host address to connect to
 * @param date The date in YYYYMMDD format
 * @param time The time in HHMMSSZ format
 * @param payloadLength The length of the actual data to be stored in the destination file
 * @param storageClass The class of storage to be used
 * @param authorizationHeader The authorization header for the request
 * @param result The buffer to write the HTTP request header into
 * @param sizeResult The size of the result buffer
 *
 * @return Returns true if the function is successful, false otherwise
 *
 * @note This function assumes that the result buffer is large enough to hold the entire header
 */
bool httpPutReqStream(const char *object, const char *contentType, uint32_t contentLength, const char *host,
                      const char *date, const char *time, uint32_t payloadLength, const char *storageClass,
                      const char *authorizationHeader, char *result, size_t sizeResult)
{
    /* Payload lenght relates to the the actual data to be stored in the destination file
    Content length is payload length + meta data length! */
    bool ret = false;
    int snprintf_ret = snprintf(result,
                                sizeResult,
                                "PUT /%s HTTP/1.1\r\n"
                                "Content-Encoding: aws-chunked\r\n"
                                "Content-Length: %lu\r\n"
                                "Content-Type: %s\r\n"
                                "Host: %s\r\n"
                                "X-Amz-Content-Sha256: STREAMING-AWS4-HMAC-SHA256-PAYLOAD\r\n"
                                "X-Amz-Date: %sT%sZ\r\n"
                                "X-Amz-Decoded-Content-Length: %lu\r\n"
                                "X-Amz-Storage-Class: %s\r\n"
                                "Authorization: %s\r\n"
                                "User-Agent: ESP32\r\n"
                                "Accept: */*\r\n"
                                "Cache-Control: no-cache\r\n"
                                "Expect: 100-continue\r\n"
                                "Connection: keep-alive\r\n"
                                "\r\n",
                                object, contentLength, contentType, host, date, time,
                                payloadLength, storageClass, authorizationHeader);
    Serial.printf("HTTP header length: %d\r\n", snprintf_ret);
    if (snprintf_ret < 0)
    {
        return ret;
    }
    else if (snprintf_ret >= sizeResult)
    {
        Serial.printf("httpPutReq failed: %d\r\n", snprintf_ret);
        return ret;
    }
    else
    {
        ret = true;
        return ret;
    }
}