/*
 * EFI Boot Guard
 *
 * Copyright (c) Siemens AG, 2017
 *
 * Authors:
 *  Andreas Reichel <andreas.reichel.ext@siemens.com>
 *
 * This work is licensed under the terms of the GNU GPL, version 2.  See
 * the COPYING file in the top-level directory.
 *
 * SPDX-License-Identifier:	GPL-2.0-only
 */

#include <stdlib.h>
#include <check.h>
#include <fff.h>
#include <env_api.h>
#include <env_config_file.h>
#include <env_config_partitions.h>
#include <ebgenv.h>

DEFINE_FFF_GLOBALS;

static char *devpath = "/dev/nobrain";

Suite *ebg_test_suite(void);

extern bool write_env(CONFIG_PART *, BG_ENVDATA *);
extern EBGENVKEY bgenv_str2enum(char *);

bool write_env_custom_fake(CONFIG_PART *part, BG_ENVDATA *env);

bool write_env_custom_fake(CONFIG_PART *part, BG_ENVDATA *env)
{
	return true;
}

FAKE_VALUE_FUNC(bool, write_env, CONFIG_PART *, BG_ENVDATA *);

CONFIG_PART config_parts[ENV_NUM_CONFIG_PARTS];
BG_ENVDATA envdata[ENV_NUM_CONFIG_PARTS];

START_TEST(ebgenv_api_internal_strXtoY)
{
	char16_t *exp_res = L"This is a test";
	char16_t bufferw[16];
	char buffer[16];
	char *input = "This is a test";
	char16_t *resw;
	char *res;

	/* Test conversion from ASCII bits to 16 bit encoding
	 */
	resw = str8to16(bufferw, input);

	/* cannot use glibc for 16-bit wchar_t
	 * string compare since glibc has 32-bit wchar_t
	 */
	for (int i = 0; i < strlen(input); i++) {
		ck_assert(resw[i] == exp_res[i]);
	}

	/* Test conversion from 16 bit encoding to ASCII
	 */
	res = str16to8(buffer, exp_res);

	ck_assert(strcmp(res, input) == 0);
}
END_TEST

START_TEST(ebgenv_api_internal_bgenv_str2enum)
{
	EBGENVKEY e;

	/* Test bgenv_str2enum for correct key conversion
	 */
	e = bgenv_str2enum("kernelfile");
	ck_assert(e == EBGENV_KERNELFILE);

	e = bgenv_str2enum("kernelparams");
	ck_assert(e == EBGENV_KERNELPARAMS);

	e = bgenv_str2enum("watchdog_timeout_sec");
	ck_assert(e == EBGENV_WATCHDOG_TIMEOUT_SEC);

	e = bgenv_str2enum("revision");
	ck_assert(e == EBGENV_REVISION);

	e = bgenv_str2enum("ustate");
	ck_assert(e == EBGENV_USTATE);

	/* Test if bgenv_str2enum returns EBGENV_UNKNOWN for empty and invalid
	 * keys
	 */
	e = bgenv_str2enum("XZXOOZOOZIOFZOFZ");
	ck_assert(e == EBGENV_UNKNOWN);

	e = bgenv_str2enum("");
	ck_assert(e == EBGENV_UNKNOWN);
}
END_TEST

START_TEST(ebgenv_api_internal_bgenv_open_by_index)
{
	BGENV *handle;

	handle = bgenv_open_by_index(0);
	ck_assert(handle != NULL);
	ck_assert(handle->desc == &config_parts[0]);
	ck_assert(handle->data == &envdata[0]);
	free(handle);

	handle = bgenv_open_by_index(ENV_NUM_CONFIG_PARTS-1);
	ck_assert(handle != NULL);
	ck_assert(handle->desc == &config_parts[ENV_NUM_CONFIG_PARTS-1]);
	ck_assert(handle->data == &envdata[ENV_NUM_CONFIG_PARTS-1]);
	free(handle);

	/* Test if bgenv_open_by_index returns NULL if parameter is out of
	 * range
	 */
	handle = bgenv_open_by_index(ENV_NUM_CONFIG_PARTS);
	ck_assert(handle == NULL);
}
END_TEST

START_TEST(ebgenv_api_internal_bgenv_open_oldest)
{
	BGENV *handle;

	/* Test if bgenv_open_oldest returns a handle for the environment with
	 * the lowest revision
	 */
	for (int i = 0; i < ENV_NUM_CONFIG_PARTS; i++)
	{
		envdata[i].revision = ENV_NUM_CONFIG_PARTS - i;
	}
	handle = bgenv_open_oldest();
	ck_assert(handle != NULL);
	ck_assert(handle->desc == &config_parts[ENV_NUM_CONFIG_PARTS-1]);
	ck_assert(handle->data == &envdata[ENV_NUM_CONFIG_PARTS-1]);
	free(handle);
}
END_TEST

START_TEST(ebgenv_api_internal_bgenv_open_latest)
{
	BGENV *handle;

	/* Test if bgenv_open_latest returns a handle for the environment with
	 * the highest revision
	 */
	for (int i = 0; i < ENV_NUM_CONFIG_PARTS; i++)
	{
		envdata[i].revision = ENV_NUM_CONFIG_PARTS - i;
	}
	handle = bgenv_open_latest();
	ck_assert(handle != NULL);
	ck_assert(handle->desc == &config_parts[0]);
	ck_assert(handle->data == &envdata[0]);
	free(handle);
}
END_TEST

START_TEST(ebgenv_api_internal_bgenv_write)
{
	bool err = true;

	bool res;
	BGENV *dummy_env;

	dummy_env = calloc(1, sizeof(BGENV));
	if (!dummy_env) {
		goto finally;
	}

	RESET_FAKE(write_env);
	write_env_fake.custom_fake = write_env_custom_fake;

	/* Test if writing with a NULL-handle fails
	 */
	res = bgenv_write(NULL);
	ck_assert(write_env_fake.call_count == 0);
	ck_assert(res == false);

	/* Test if writing with a handle describing no partition
	 * and no environment data fails
	 */
	res = bgenv_write(dummy_env);
	ck_assert(write_env_fake.call_count == 0);
	ck_assert(res == false);

	/* Test if writing with a handle describing both partition
	 * and envrionment data succeeds
	 */
	dummy_env->desc = calloc(1, sizeof(CONFIG_PART));
	if (!dummy_env->desc) {
		goto finally;
	}

	dummy_env->data = calloc(1, sizeof(BG_ENVDATA));
	if (!dummy_env->data) {
		goto finally;
	}

	res = bgenv_write(dummy_env);
	ck_assert(write_env_fake.call_count == 1);
	ck_assert(res == true);

	err = false;

finally:
	if (dummy_env) {
		free(dummy_env->data);
		free(dummy_env->desc);
		free(dummy_env);
	}

	if (err)
		ck_abort();
}
END_TEST

START_TEST(ebgenv_api_internal_bgenv_read)
{
	BGENV env;
	BG_ENVDATA data;

	env.data = &data;

	/* Test if bgenv_read returns a pointer to the environment data
	 */
	BG_ENVDATA *res = bgenv_read(&env);
	ck_assert(res == env.data);
}
END_TEST

START_TEST(ebgenv_api_internal_bgenv_create_new)
{
	BGENV *handle;
	int max = ENV_NUM_CONFIG_PARTS;

	for (int i = 0; i < max; i++)
	{
		envdata[i].revision = max - i;
	}

	/* Test if bgenv_create_new updates the oldest environment with default
	 * values and sets its revision to revision(latest)+1
	 */
	handle = bgenv_create_new();

	ck_assert(handle != NULL);
	ck_assert(handle->data == &envdata[max-1]);
	ck_assert(envdata[max-1].revision == max+1);
	ck_assert(envdata[max-1].watchdog_timeout_sec == (max == 1 ? 0 : 30));

	free(handle);
}
END_TEST

START_TEST(ebgenv_api_internal_bgenv_get)
{
	BGENV *handle = bgenv_open_latest();
	ck_assert(handle != NULL);

	char16_t buffer[ENV_STRING_LENGTH];
	char *test_strings[] = {
		"kernelfile_test123",
		"kernelparams_test123",
	};
	void *dests[] = {
		&handle->data->kernelfile,
		&handle->data->kernelparams,
	};

	for (int i = 0; i < sizeof(test_strings)/sizeof(void*); i++)
	{
		memcpy(dests[i], str8to16(buffer, test_strings[i]),
		       strlen(test_strings[i]) * 2 + 2);
	}
	handle->data->watchdog_timeout_sec = 44;
	handle->data->revision = 10000;
	handle->data->ustate = USTATE_INSTALLED;

	char *data = NULL;
	char buffera[22];
	int res;

	/* Test if bgenv_get fails if maxlen is set to 0
	 */
	res = bgenv_get(handle, "kernelfile", NULL, data, 0);
	ck_assert_int_eq(res, -EINVAL);

	/* Test if bgenv_get fails if key is NULL
	 */
	res = bgenv_get(handle, NULL, NULL, data, 1000);
	ck_assert_int_eq(res, -EINVAL);

	/* Test if bgenv_get fails if no environment is provided
	 */
	res = bgenv_get(NULL, "kernelfile", NULL, NULL, 1000);
	ck_assert_int_eq(res, -EPERM);

	/* Test if bgenv_get returns the correct size of the needed
	 * buffer if provided with a NULL buffer
	 */
	res = bgenv_get(handle, "kernelfile", NULL, NULL, 1000);
	ck_assert_int_eq(res, strlen(test_strings[0]) + 1);

	/* Test if bgenv_get returns the correct value
	 */
	res = bgenv_get(handle, "kernelfile", NULL, buffera, res);
	ck_assert_int_eq(res, 0);
	ck_assert_int_eq(strcmp(buffera, test_strings[0]), 0);

	res = bgenv_get(handle, "kernelparams", NULL, NULL, 1000);
	ck_assert_int_eq(res, strlen(test_strings[1]) + 1);

	res = bgenv_get(handle, "kernelparams", NULL, buffera, res);
	ck_assert_int_eq(res, 0);
	ck_assert_int_eq(strcmp(buffera, test_strings[1]), 0);

	free(handle);
}
END_TEST

START_TEST(ebgenv_api_internal_bgenv_set)
{
	int res;

	BGENV *handle = bgenv_open_latest();
	ck_assert(handle != NULL);
	ck_assert(handle->data != NULL);

	/* Test if bgenv_set returns -EINVAL if the handle is invalid
	 */
	res = bgenv_set(NULL, "kernelfile", 0, NULL, 0);
	ck_assert_int_eq(res, -EINVAL);

	/* Test if bgenv_set returns -EINVAL if the key is invalid
	 */
	res = bgenv_set(handle, "AOFIJAOEGIHA", 0, NULL, 0);
	ck_assert_int_eq(res, -EINVAL);

	/* Test if bgenv_set works correctly for valid parameters
	 */
	res = bgenv_set(handle, "kernelfile", 0, "vmlinuz", 8);
	ck_assert_int_eq(res, 0);

	char buffer[8];
	char *kfile = str16to8(buffer, handle->data->kernelfile);

	ck_assert(strcmp(kfile, "vmlinuz") == 0);

	res = bgenv_set(handle, "watchdog_timeout_sec", 0, "-0", 2);
	ck_assert_int_eq(res, 0);
	ck_assert_int_eq(handle->data->watchdog_timeout_sec, 0);

	res = bgenv_set(handle, "watchdog_timeout_sec", 0, "311", 4);
	ck_assert_int_eq(res, 0);
	ck_assert_int_eq(handle->data->watchdog_timeout_sec, 311);

	res = bgenv_set(handle, "kernelparams", 0, "root=", 6);
	ck_assert_int_eq(res, 0);

	char *kparm = str16to8(buffer, handle->data->kernelparams);

	ck_assert(strcmp(kparm, "root=") == 0);

	res = bgenv_set(handle, "ustate", 0, "2", 2);
	ck_assert_int_eq(res, 0);

	ck_assert_int_eq(handle->data->ustate, 2);

	res = bgenv_set(handle, "revision", 0, "0", 2);
	ck_assert_int_eq(res, 0);
	ck_assert_int_eq(handle->data->revision, 0);

	res = bgenv_set(handle, "revision", 0, "10301", 6);
	ck_assert_int_eq(res, 0);
	ck_assert_int_eq(handle->data->revision, 10301);

	free(handle);
}
END_TEST

START_TEST(ebgenv_api_internal_uservars)
{
	RESET_FAKE(write_env);
	write_env_fake.custom_fake = write_env_custom_fake;

	int res;
	BGENV *handle = bgenv_open_latest();

	ck_assert(handle != NULL);
	ck_assert(handle->data != NULL);

	uint64_t type;
	uint8_t *data;
	/* Test a user variable using a user-defined data type
	 */
	type = 1ULL << 36;
	res = bgenv_set(handle, "myvar", type, "mydata", 5);
	ck_assert_int_eq(res, 0);

	for (int i = 0; i < ENV_NUM_CONFIG_PARTS; i++) {
		data = bgenv_find_uservar((uint8_t *)&(envdata[i].userdata),
					  "myvar");
		if (handle->data != &envdata[i]) {
			ck_assert(data == NULL);
		} else
		{
			ck_assert(data != NULL);
		}
	}

	res = bgenv_set(handle, "myvar", USERVAR_TYPE_DELETED, "mydata", 5);
	ck_assert_int_eq(res, 0);

	for (int i = 0; i < ENV_NUM_CONFIG_PARTS; i++) {
		data = bgenv_find_uservar((uint8_t *)&(envdata[i].userdata),
					  "myvar");
		if (handle->data == &envdata[i]) {
			ck_assert(data == NULL);
		}
	}

	write_env_fake.call_count = 0;

	bgenv_close(handle);
}
END_TEST

Suite *ebg_test_suite(void)
{
	Suite *s;
	TCase *tc_core;

	s = suite_create("ebgenv_api");

	tc_core = tcase_create("Core");

	tcase_add_test(tc_core, ebgenv_api_internal_strXtoY);
	tcase_add_test(tc_core, ebgenv_api_internal_bgenv_str2enum);
	tcase_add_test(tc_core, ebgenv_api_internal_bgenv_open_by_index);
	tcase_add_test(tc_core, ebgenv_api_internal_bgenv_open_oldest);
	tcase_add_test(tc_core, ebgenv_api_internal_bgenv_open_latest);
	tcase_add_test(tc_core, ebgenv_api_internal_bgenv_write);
	tcase_add_test(tc_core, ebgenv_api_internal_bgenv_read);
	tcase_add_test(tc_core, ebgenv_api_internal_bgenv_create_new);
	tcase_add_test(tc_core, ebgenv_api_internal_bgenv_get);
	tcase_add_test(tc_core, ebgenv_api_internal_bgenv_set);
	tcase_add_test(tc_core, ebgenv_api_internal_uservars);

	suite_add_tcase(s, tc_core);

	return s;
}
