/**
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0.
 */

#include <aws/checksums/crc.h>
#include <aws/checksums/private/crc_priv.h>
#include <aws/testing/aws_test_harness.h>

static const uint8_t DATA_32_ZEROS[32] = {0};
static const uint32_t KNOWN_CRC32_32_ZEROES = 0x190A55AD;
static const uint32_t KNOWN_CRC32C_32_ZEROES = 0x8A9136AA;

static const uint8_t DATA_32_VALUES[32] = {0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 15,
                                           16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
static const uint32_t KNOWN_CRC32C_32_VALUES = 0x46DD794E;

static const uint8_t TEST_VECTOR[] = {'1', '2', '3', '4', '5', '6', '7', '8', '9'};
static const uint32_t KNOWN_CRC32_TEST_VECTOR = 0xCBF43926;
static const uint32_t KNOWN_CRC32C_TEST_VECTOR = 0xE3069283;

static uint8_t *s_non_mem_aligned_vector;

typedef uint32_t(crc_fn)(const uint8_t *input, int length, uint32_t previousCrc32);
#define CRC_FUNC_NAME(crc_func) #crc_func, crc_func
#define DATA_NAME(dataset) #dataset, dataset, sizeof(dataset)

/* Makes sure that the specified crc function produces the expected results for known input and output*/
static int s_test_known_crc(
    const char *func_name,
    crc_fn *func,
    const char *data_name,
    const uint8_t *input,
    size_t length,
    uint32_t expected) {

    uint32_t result = func(input, (int)length, 0);
    ASSERT_HEX_EQUALS(expected, result, "%s(%s)", func_name, data_name);

    /* chain the crc computation so 2 calls each operate on about 1/2 of the buffer*/
    uint32_t crc1 = func(input, (int)(length / 2), 0);
    result = func(input + (length / 2), (int)(length - length / 2), crc1);
    ASSERT_HEX_EQUALS(expected, result, "chaining %s(%s)", func_name, data_name);

    crc1 = 0;
    for (size_t i = 0; i < length; ++i) {
        crc1 = func(input + i, 1, crc1);
    }

    ASSERT_HEX_EQUALS(expected, crc1, "one byte at a time %s(%s)", func_name, data_name);

    return AWS_OP_SUCCESS;
}

/* helper function that groups crc32 tests*/
static int s_test_known_crc32(const char *func_name, crc_fn *func) {
    int res = 0;
    res |= s_test_known_crc(func_name, func, DATA_NAME(DATA_32_ZEROS), KNOWN_CRC32_32_ZEROES);
    res |= s_test_known_crc(func_name, func, DATA_NAME(TEST_VECTOR), KNOWN_CRC32_TEST_VECTOR);
    return res;
}

/* helper function that groups crc32c tests*/
static int s_test_known_crc32c(const char *func_name, crc_fn *func) {
    int res = 0;

    res |= s_test_known_crc(func_name, func, DATA_NAME(DATA_32_ZEROS), KNOWN_CRC32C_32_ZEROES);
    res |= s_test_known_crc(func_name, func, DATA_NAME(DATA_32_VALUES), KNOWN_CRC32C_32_VALUES);
    res |= s_test_known_crc(func_name, func, DATA_NAME(TEST_VECTOR), KNOWN_CRC32C_TEST_VECTOR);

    /*this tests three things, first it tests the case where we aren't 8-byte aligned*/
    /*seconde, it tests that reads aren't performed before start of buffer*/
    /*third, it tests that writes aren't performed after the end of the buffer.*/
    /*if any of those things happen, then the checksum will be wrong and the assertion will fail */
    s_non_mem_aligned_vector = malloc(sizeof(DATA_32_VALUES) + 6);
    memset(s_non_mem_aligned_vector, 1, sizeof(DATA_32_VALUES) + 6);
    memcpy(s_non_mem_aligned_vector + 3, DATA_32_VALUES, sizeof(DATA_32_VALUES));
    res |= s_test_known_crc(
        func_name,
        func,
        "non_mem_aligned_vector",
        s_non_mem_aligned_vector + 3,
        sizeof(DATA_32_VALUES),
        KNOWN_CRC32C_32_VALUES);
    free(s_non_mem_aligned_vector);
    return res;
}

/**
 * Quick sanity check of some known CRC values for known input.
 * The reference functions are included in these tests to verify that they aren't obviously broken.
 */
static int s_test_crc32c(struct aws_allocator *allocator, void *ctx) {
    (void)allocator;
    (void)ctx;

    int res = 0;

    res |= s_test_known_crc32c(CRC_FUNC_NAME(aws_checksums_crc32c));
    res |= s_test_known_crc32c(CRC_FUNC_NAME(aws_checksums_crc32c_sw));

    return res;
}
AWS_TEST_CASE(test_crc32c, s_test_crc32c)

static int s_test_crc32(struct aws_allocator *allocator, void *ctx) {
    (void)allocator;
    (void)ctx;

    int res = 0;
    res |= s_test_known_crc32(CRC_FUNC_NAME(aws_checksums_crc32));

    return res;
}
AWS_TEST_CASE(test_crc32, s_test_crc32)
