#include <crypto/aes.h>
#include <crypto/skcipher.h>
#include <linux/err.h>
#include <linux/string.h>
#include <linux/init.h>
#include <linux/module.h>

#include "fips140.h"
#include "fips140_test.h"

// TODO: to be removed
#if defined (__clang__)
#pragma clang optimize off
#elif defined  (__GNUC__)
#pragma GCC push_options
#pragma GCC optimize ("O0")
#endif

int fips_enabled;
EXPORT_SYMBOL_GPL(fips_enabled);
#ifdef CONFIG_CRYPTO_FIPS_FUNC_TEST
static int functest_status;
#endif

static int __init fips140_post(void)
{
	int err_kat = -1;
	int err_integrity = -1;

	fips_enabled = 0;

	pr_info("FIPS : POST (%s)\n", SKC_VERSION_TEXT);

	err_kat = fips140_kat();
	err_integrity = do_integrity_check();

	if (err_kat || err_integrity) {
		pr_err("FIPS : POST - one or more selftests failed\n");

#ifdef CONFIG_CRYPTO_FIPS_FUNC_TEST
		if (functest_status) {
			pr_err("FIPS : POST - bypass panic because of functional test\n");
			return -1;
		}
		else
			panic("FIPS : POST - one or more selftests failed\n");
#else
		panic("FIPS : POST - one or more selftests failed\n");
#endif
	}

	fips_enabled = 1;
	pr_info("FIPS : POST - CRYPTO API started in FIPS approved mode : fips_enabled = %d\n", fips_enabled);

	return 0;
}

// When SKC_FUNC_TEST is defined, this function will be called instead of tcrypt_mode_init
// tcyprt_mode_init will be called as test case number
// after all tests are done, the normal POST test will start
#ifdef CONFIG_CRYPTO_FIPS_FUNC_TEST
static int __init fips140_post_func_test(void)
{
	int i;
	int err = -ENOMEM;
	struct crypto_skcipher *tfm = NULL;

	pr_info("FIPS FUNC : Functional test start\n");

	functest_status = 1;
	for (i = 0; i < SKC_FUNCTEST_KAT_CASE_NUM; i++) {
		set_fips_functest_KAT_mode(i);
		pr_info("FIPS FUNC : --------------------------------------------------\n");
		pr_info("FIPS FUNC : Failure inducement case %d - [%s]\n", i + 1, get_fips_functest_mode());
		pr_info("FIPS FUNC : --------------------------------------------------\n");

		err = fips140_post();

		pr_info("FIPS FUNC : (%d) POST done. SKC module FIPS status : fips140_post() returns %d | %s\n",
			i+1, err, fips_enabled ? "passed" : "failed");
	}
	functest_status = 0;

	for (i = 0; i < SKC_FUNCTEST_CONDITIONAL_CASE_NUM; i++) {
		set_fips_functest_conditional_mode(i);
		pr_info("FIPS FUNC : --------------------------------------------------\n");
		pr_info("FIPS FUNC : conditional test case %d - [%s]\n", i + 1, get_fips_functest_mode());
		pr_info("FIPS FUNC : --------------------------------------------------\n");

		if (!strcmp("zeroization", get_fips_functest_mode())) {
			uint8_t key[AES_KEYSIZE_256];
			memset(key, 0xFE, AES_KEYSIZE_256);
			tfm = crypto_alloc_skcipher("ecb(aes-ce)", 0, 0);
			if (tfm) {
				crypto_skcipher_setkey(tfm, key, AES_KEYSIZE_256);
				crypto_free_skcipher(tfm);
			}
		}
	}
	set_fips_functest_conditional_mode(-1);

	pr_info("FIPS FUNC : Functional test end\n");
	pr_info("FIPS FUNC : Normal POST start\n");

	return fips140_post();
}
#endif

/*
 * If an init function is provided, an exit function must also be provided
 * to allow module unload.
 */
static void __exit fips140_fini(void) { }

#ifdef CONFIG_CRYPTO_FIPS_FUNC_TEST
	late_initcall(fips140_post_func_test);
#else
	late_initcall(fips140_post);
#endif
module_exit(fips140_fini);

// TODO: to be removed
#if defined (__clang__)
#pragma clang optimize on
#elif defined  (__GNUC__)
#pragma GCC reset_options
#endif

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("FIPS140 POST");