/* Copyright (c) (2018,2019,2020) Apple Inc. All rights reserved.
 *
 * corecrypto is licensed under Apple Inc.’s Internal Use License Agreement (which
 * is contained in the License.txt file distributed with corecrypto) and only to
 * people who accept that license. IMPORTANT:  Any license rights granted to you by
 * Apple Inc. (if any) are limited to internal use within your organization only on
 * devices and computers you own or control, for the sole purpose of verifying the
 * security characteristics and correct functioning of the Apple Software.  You may
 * not, directly or indirectly, redistribute the Apple Software or any portions thereof.
 */

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

#include <corecrypto/cc_priv.h>

#include "testbyteBuffer.h"
#include "cctestvector_parser.h"
#include "cctest_driver.h"
#include "ccdict.h"

#include "crypto_test_chacha20poly1305_runner.h"
#include "crypto_test_aes_gcm_runner.h"
#include "crypto_test_aes_ccm_runner.h"
#include "crypto_test_cmac_runner.h"
#include "crypto_test_ecdh_runner.h"
#include "crypto_test_x25519_runner.h"
#include "crypto_test_ecdsa_runner.h"
#include "crypto_test_eddsa_runner.h"
#include "crypto_test_rsassa_runner.h"
#include "crypto_test_rsaes_runner.h"
#include "crypto_test_primality_runner.h"

struct cctest_algorithm_driver {
    /*!
     * @abstract Name of the test runner
     */
    const char *algorithm;

    /*!
     * @function run
     * @abstract Pointer to a runner function.
     * @param test_vector_data A `ccdict_t` containing all the relevant test vector data.
     * @return True if the test passed, and false otherwise.
     */
    bool (*run)(ccdict_t vector);
};

struct cctest_driver {
    /*!
     * @abstract Name of the algorithm family in which this test should run.
     */
    const char *family;

    /*!
     * @abstract List of drivers for this particular family.
     */
    struct cctest_algorithm_driver *drivers;
    size_t drivers_len;

    /*!
     * Number of passed and failed tests for this family.
     */
    int num_tests;
    int num_fails;
};

static struct cctest_algorithm_driver ccchacha_runners[] = {
    { .algorithm = "CHACHA20-POLY1305", .run = crypto_test_chacha20poly1305_runner },
};
static const size_t ccchacha_runners_len = CC_ARRAY_LEN(ccchacha_runners);

static struct cctest_algorithm_driver ccaesmode_runners[] = {
    { .algorithm = "AES-GCM", .run = crypto_test_aes_gcm_runner },
    { .algorithm = "AES-CCM", .run = crypto_test_aes_ccm_runner },
};
static const size_t ccaesmode_runners_count = CC_ARRAY_LEN(ccaesmode_runners);

static struct cctest_algorithm_driver cccmac_runners[] = {
    { .algorithm = "AES-CMAC", .run = crypto_test_cmac_runner },
};
static const size_t cccmac_runners_count = CC_ARRAY_LEN(cccmac_runners);

static struct cctest_algorithm_driver ccec_runners[] = {
    { .algorithm = "ECDH", .run = crypto_test_ecdh_runner },
    { .algorithm = "ECDSA", .run = crypto_test_ecdsa_runner },
};
static const size_t ccec_runners_count = CC_ARRAY_LEN(ccec_runners);

static struct cctest_algorithm_driver cceddsa_runners[] = {
#ifndef _MSC_VER
    { .algorithm = "EDDSA", .run = crypto_test_eddsa_runner },
#endif
};
static const size_t cceddsa_runners_count = CC_ARRAY_LEN(cceddsa_runners);

static struct cctest_algorithm_driver ccx25519_runners[] = {
    { .algorithm = "X25519", .run = crypto_test_x25519_runner },
};
static const size_t ccx25519_runners_count = CC_ARRAY_LEN(ccx25519_runners);

static struct cctest_algorithm_driver ccrsa_runners[] = {
    { .algorithm = "RSASSA-PKCS1-v1_5", .run = crypto_test_rsassa_runner },
    { .algorithm = "RSASSA-PSS", .run = crypto_test_rsassa_runner },
    { .algorithm = "RSAES-PKCS1-v1_5", .run = crypto_test_rsaes_runner },
    { .algorithm = "RSAES-OAEP", .run = crypto_test_rsaes_runner },
};
static const size_t ccrsa_runners_count = CC_ARRAY_LEN(ccrsa_runners);

static struct cctest_algorithm_driver ccprime_runners[] = {
    { .algorithm = "PrimalityTest", .run = crypto_test_primality_runner },
};
static const size_t ccprime_runners_count = CC_ARRAY_LEN(ccprime_runners);

static struct cctest_driver cctestvector_family_runners[] = {
    { .family = "ccchacha", .drivers = ccchacha_runners, .drivers_len = ccchacha_runners_len },
    { .family = "ccaes_modes", .drivers = ccaesmode_runners, .drivers_len = ccaesmode_runners_count },
    { .family = "cccmac", .drivers = cccmac_runners, .drivers_len = cccmac_runners_count },
    { .family = "ccec", .drivers = ccec_runners, .drivers_len = ccec_runners_count },
    { .family = "cced25519", .drivers = cceddsa_runners, .drivers_len = cceddsa_runners_count },
    { .family = "cccurve25519", .drivers = ccx25519_runners, .drivers_len = ccx25519_runners_count },
    { .family = "ccrsa", .drivers = ccrsa_runners, .drivers_len = ccrsa_runners_count },
    { .family = "ccprime_rabin_miller", .drivers = ccprime_runners, .drivers_len = ccprime_runners_count },
};
static const size_t cctestvector_family_runners_count = CC_ARRAY_LEN(cctestvector_family_runners);

cctest_driver_t
cctest_driver_for_family(const char *family)
{
    size_t family_len = strlen(family);
    for (size_t i = 0; i < cctestvector_family_runners_count; i++) {
        const char *runner_family = cctestvector_family_runners[i].family;
        size_t runner_family_len = strlen(runner_family);
        if (family_len == runner_family_len && strncmp(runner_family, family, runner_family_len) == 0) {
            return &cctestvector_family_runners[i];
        }
    }
    return NULL;
}

static struct cctest_algorithm_driver *
cctest_get_algorithm_driver(cctest_driver_t driver, ccdict_t vector)
{
    size_t algorithm_len = 0;
    const char *algorithm = ccdict_get_value(vector, cctestvector_key_algorithm, &algorithm_len);
    if (algorithm != NULL) {
        for (size_t i = 0; i < driver->drivers_len; i++) {
            const char *runner_algorithm = driver->drivers[i].algorithm;
            size_t runner_algorithm_len = strlen(runner_algorithm);
            if (strncmp(algorithm, runner_algorithm, CC_MIN(algorithm_len, runner_algorithm_len)) == 0) {
                return &driver->drivers[i];
            }
        }
    }
    return NULL;
}

bool
cctest_driver_can_run(cctest_driver_t driver, ccdict_t vector)
{
    return cctest_get_algorithm_driver(driver, vector) != NULL;
}

bool
cctest_driver_run(cctest_driver_t test_driver, ccdict_t vector)
{
    struct cctest_algorithm_driver *driver = cctest_get_algorithm_driver(test_driver, vector);
    test_driver->num_tests++;
    bool result = driver->run(vector);
    if (!result) {
        test_driver->num_fails++;
    }
    return result;
}

int
cctest_driver_get_num_tests(cctest_driver_t driver)
{
    return driver->num_tests;
}

int
cctest_driver_get_num_failed_tests(cctest_driver_t driver)
{
    return driver->num_fails;
}
