// SPDX-License-Identifier: BSD-2-Clause-Patent
/*
 * test-mock-variables.c - test our mock variable implementation (irony)
 * Copyright Peter Jones <pjones@redhat.com>
 */

#include "shim.h"
#include "mock-variables.h"

#include <errno.h>
#include <efivar/efivar.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

#include "test-data-efivars-0.h"

#pragma GCC diagnostic ignored "-Wunused-label"

static const size_t guidstr_size = sizeof("8be4df61-93ca-11d2-aa0d-00e098032b8c");

void mock_print_guidname(EFI_GUID *guid, CHAR16 *name);
void mock_print_var_list(list_t *head);

static int
test_filter_out_helper(size_t nvars, const CHAR16 *varnames[nvars],
		       bool filter_out, UINTN expected_count)
{
	const char *mok_rt_vars[n_mok_state_variables];
	EFI_STATUS status;
	EFI_GUID guid = SHIM_LOCK_GUID;
	CHAR16 name[1024] = L"";
	UINTN sz;
	char asciiname[1024];
	bool found = false;
	int ret = 0;
	UINTN count = 0;

	for (size_t i = 0; i < n_mok_state_variables; i++) {
		mok_rt_vars[i] = mok_state_variables[i].rtname8;
	}

	sz = sizeof(name);
	status = RT->GetNextVariableName(&sz, name, &guid);
	assert_equal_return(status, EFI_NOT_FOUND, -1, "got %lx, expected %lx");

	mock_load_variables("test-data/efivars-1", mok_rt_vars, filter_out);

	while (true) {
		int rc = 0;

		sz = sizeof(name);
		status = RT->GetNextVariableName(&sz, name, &guid);
		if (status == EFI_NOT_FOUND)
			break;
		if (EFI_ERROR(status))
			return -1;

		count += 1;
		SetMem(asciiname, sizeof(asciiname), 0);
		for (UINTN i = 0; i < sizeof(asciiname); i++)
			asciiname[i] = name[i];
		for (UINTN i = 0; varnames[i] != NULL; i++) {
			if (sz == 0 || StrLen(varnames[i]) != sz-1)
				continue;
			if (StrCmp(name, varnames[i]) == 0) {
				found = true;
				if (filter_out) {
					rc = assert_false_as_expr(found, -1,
						"found=%u for undesired variable \"%s\"\n",
						asciiname);
					break;
				}
			}
		}
		if (!filter_out)
			rc = assert_true_as_expr(found, -1,
				"found=%u for undesired variable \"%s\"\n",
				asciiname);
		if (ret >= 0 && rc < 0)
			ret = rc;
	}

	mock_reset_variables();
	assert_equal_return(count, expected_count, -1, "%lu != %lu\n");
	assert_true_return(list_empty(&mock_variables), -1, "%lu != %lu\n");

	return ret;
}

static int
test_filter_out_true(void)
{
	const CHAR16 *varnames[] = {
		L"MokListRT",
		L"MokListXRT",
		L"SbatLevelRT",
		NULL
	};
	size_t nvars = sizeof(varnames) / sizeof(varnames[0]);

	return test_filter_out_helper(nvars, varnames, true, 3);
}

static int
test_filter_out_false(void)
{
	const CHAR16 *varnames[] = {
		L"MokListRT",
		L"MokListXRT",
		L"SbatLevelRT",
		NULL
	};
	size_t nvars = sizeof(varnames) / sizeof(varnames[0]);

	return test_filter_out_helper(nvars, varnames, false, 3);
}

static int
test_gnvn_buf_size_0(void)
{
	UINTN size = 0;
	CHAR16 buf[6] = { 0, };
	EFI_STATUS status;
	EFI_GUID empty_guid = { 0, };
	int ret = -1;

	status = RT->GetNextVariableName(&size, &buf[0], &GV_GUID);
	assert_equal_return(status, EFI_INVALID_PARAMETER, -1, "0x%lx != 0x%lx\n");

	size = 1;
	status = RT->GetNextVariableName(&size, &buf[0], &GV_GUID);
	assert_equal_return(status, EFI_NOT_FOUND, -1, "0x%lx != 0x%lx\n");

	status = RT->SetVariable(L"test", &GV_GUID,
				 EFI_VARIABLE_BOOTSERVICE_ACCESS, 5, "test");
	assert_equal_return(status, EFI_SUCCESS, -1, "0x%lx != 0x%lx\n");

	size = 1;
	status = RT->GetNextVariableName(&size, &buf[0], &empty_guid);
	assert_equal_goto(status, EFI_NOT_FOUND, err, "0x%lx != 0x%lx\n");

	size = 1;
	status = RT->GetNextVariableName(&size, &buf[0], &GV_GUID);
	assert_equal_goto(status, EFI_BUFFER_TOO_SMALL, err, "0x%lx != 0x%lx\n");
	assert_equal_goto(size, StrSize(L"test"), err, "%zu != %zu\n");

	size = StrSize(L"test");
	status = RT->GetNextVariableName(&size, &buf[0], &GV_GUID);
	assert_equal_goto(status, EFI_SUCCESS, err, "0x%lx != 0x%lx\n");

	status = RT->SetVariable(L"testa", &GV_GUID,
				 EFI_VARIABLE_BOOTSERVICE_ACCESS, 5, "test");
	assert_equal_return(status, EFI_SUCCESS, -1, "0x%lx != 0x%lx\n");

	buf[0] = 0;
	size = 1;
	status = RT->GetNextVariableName(&size, &buf[0], &empty_guid);
	assert_equal_goto(status, EFI_NOT_FOUND, err, "0x%lx != 0x%lx\n");

	size = StrSize(L"test");
	StrCpy(buf, L"test");
	status = RT->GetNextVariableName(&size, &buf[0], &GV_GUID);
	switch (mock_variable_sort_policy) {
	case MOCK_SORT_DESCENDING:
	case MOCK_SORT_PREPEND:
		assert_equal_goto(status, EFI_NOT_FOUND, err, "0x%lx != 0x%lx\n");
		break;
	case MOCK_SORT_APPEND:
	case MOCK_SORT_ASCENDING:
		assert_equal_goto(status, EFI_BUFFER_TOO_SMALL, err, "0x%lx != 0x%lx\n");
		break;
	default:
		break;
	}

	size = StrSize(L"testa");
	StrCpy(buf, L"test");
	status = RT->GetNextVariableName(&size, &buf[0], &GV_GUID);
	switch (mock_variable_sort_policy) {
	case MOCK_SORT_DESCENDING:
	case MOCK_SORT_PREPEND:
		assert_equal_goto(status, EFI_NOT_FOUND, err, "0x%lx != 0x%lx\n");
		break;
	case MOCK_SORT_APPEND:
	case MOCK_SORT_ASCENDING:
		assert_equal_goto(status, EFI_SUCCESS, err, "0x%lx != 0x%lx\n");
		break;
	default:
		break;
	}

	ret = 0;
err:
	mock_reset_variables();
	return ret;
}

static int
test_gnvn_helper(char *testvars)
{
	UINTN size = 0;
	CHAR16 buf[8192] = { 0, };
	EFI_STATUS status;
	EFI_GUID empty_guid = { 0, };
	int ret = -1;
	const char *mok_rt_vars[n_mok_state_variables];

	for (size_t i = 0; i < n_mok_state_variables; i++) {
		/*
		 * We don't want to filter out the variables we've added to
		 * mok mirroring that aren't really from mok; right now
		 * this is a reasonable heuristic for that.
		 */
		if (mok_state_variables[i].flags & MOK_VARIABLE_CONFIG_ONLY) {
			mok_rt_vars[i] = "";
			continue;
		}
		mok_rt_vars[i] = mok_state_variables[i].rtname8;
	}

	mock_load_variables(testvars, mok_rt_vars, true);

#if defined(SHIM_DEBUG) && SHIM_DEBUG != 0
	dump_mock_variables(__FILE__, __LINE__, __func__);
#endif

	/*
	 * This tests the sort policy, filtering for only variables in the
	 * EFI "global" namespace.  If ascending the first thing should
	 * be Boot0000, if descending it should be dbxDefault
	 */
#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 1
	printf("Testing mock variable sorting in the global namespace\n");
#endif
	size = sizeof(buf);
	buf[0] = L'\0';
	status = RT->GetNextVariableName(&size, buf, &GV_GUID);
	assert_equal_goto(status, EFI_SUCCESS, err, "0x%lx != 0x%lx\n");

	switch (mock_variable_sort_policy) {
	case MOCK_SORT_DESCENDING:
		dump_mock_variables_if_wrong(__FILE__, __LINE__, __func__,
					     &GV_GUID, L"dbxDefault");
		assert_zero_goto(StrCmp(buf, L"dbxDefault"), err, "0x%lx != 0x%lx\n");
		break;
	case MOCK_SORT_ASCENDING:
		dump_mock_variables_if_wrong(__FILE__, __LINE__, __func__,
					     &GV_GUID, L"Boot0000");
		assert_zero_goto(StrCmp(buf, L"Boot0000"), err, "0x%lx != 0x%lx buf:\"%s\"\n",
				 0, Str2str(buf));
		break;
	default:
		break;
	}

	/*
	 * Do it again but test for only variables in the Secure Boot
	 * policy guid namespace.  Ascending should be "db", descending
	 * "dbx".
	 */
#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 1
	printf("Testing mock variable sorting in the Secure Boot GUID namespace\n");
#endif
	size = sizeof(buf);
	buf[0] = 0;
	status = RT->GetNextVariableName(&size, buf, &EFI_SECURE_BOOT_DB_GUID);
	assert_equal_goto(status, EFI_SUCCESS, err, "0x%lx != 0x%lx\n");
	switch (mock_variable_sort_policy) {
	case MOCK_SORT_DESCENDING:
		assert_zero_goto(StrCmp(buf, L"dbx"), err, "0x%lx != 0x%lx\n");
		break;
	case MOCK_SORT_ASCENDING:
		assert_zero_goto(StrCmp(buf, L"db"), err, "0x%lx != 0x%lx\n");
		break;
	default:
		break;
	}

	ret = 0;
err:
	if (ret)
		mock_print_var_list(&mock_variables);
	mock_reset_variables();
	return ret;
}

static int
test_gnvn_0(void)
{
	return test_gnvn_helper("test-data/efivars-0");
}

static int
test_gnvn_1(void)
{
	return test_gnvn_helper("test-data/efivars-1");
}

static int
test_get_variable_0(void)
{
	UINTN size = 0;
	uint8_t buf[8192] = { 0, };
	EFI_STATUS status;
	EFI_GUID empty_guid = { 0, };
	UINT32 attrs = 0;
	int ret = -1;
	int cmp;
	const char *mok_rt_vars[n_mok_state_variables];

	for (size_t i = 0; i < n_mok_state_variables; i++) {
		/*
		 * We don't want to filter out the variables we've added to
		 * mok mirroring that aren't really from mok; right now
		 * this is a reasonable heuristic for that.
		 */
		if (mok_state_variables[i].flags & MOK_VARIABLE_CONFIG_ONLY) {
			mok_rt_vars[i] = "";
			continue;
		}
		mok_rt_vars[i] = mok_state_variables[i].rtname8;
	}

	mock_load_variables("test-data/efivars-1", mok_rt_vars, true);

	size = 0;
	status = RT->GetVariable(L"Boot0000", &GV_GUID, &attrs, &size, buf);
	assert_equal_goto(status, EFI_BUFFER_TOO_SMALL, err, "0x%lx != 0x%lx\n");

	size = sizeof(buf);
	status = RT->GetVariable(L"Boot0000", &GV_GUID, &attrs, &size, buf);
	assert_equal_goto(status, EFI_SUCCESS, err, "0x%lx != 0x%lx\n");
	assert_equal_goto(size, sizeof(test_data_efivars_0_Boot0000), err, "%zu != %zu\n");
	assert_zero_goto(memcmp(buf, test_data_efivars_0_Boot0000, size), err, "%zu != %zu\n");

	ret = 0;
err:
	if (ret)
		mock_print_var_list(&mock_variables);
	mock_reset_variables();
	return ret;
}

static int
test_set_variable_0(void)
{
	UINTN size = 0;
	uint8_t buf[8192] = { 0, };
	EFI_STATUS status;
	EFI_GUID empty_guid = { 0, };
	UINT32 attrs = 0;
	int ret = -1;
	UINT32 bs_rt_nv = EFI_VARIABLE_BOOTSERVICE_ACCESS |
			  EFI_VARIABLE_RUNTIME_ACCESS |
			  EFI_VARIABLE_NON_VOLATILE;

	size = 4;
	strcpy(buf, "foo");
	status = RT->SetVariable(L"tmp", &GV_GUID, bs_rt_nv, size, buf);
	assert_equal_goto(status, EFI_SUCCESS, err, "0x%lx != 0x%lx\n");

	size = sizeof(buf);
	SetMem(buf, sizeof(buf), 0);
	status = RT->GetVariable(L"tmp", &GV_GUID, &attrs, &size, buf);
	assert_equal_goto(status, EFI_SUCCESS, err, "0x%lx != 0x%lx\n");
	assert_equal_goto(size, 4, err, "%zu != %zu\n");
	assert_zero_goto(memcmp(buf, "foo", 4), err, "0x%lx != 0x%lx\n");

	size = 5;
	strcpy(buf, "bang");
	status = RT->SetVariable(L"tmp", &GV_GUID,
				 EFI_VARIABLE_NON_VOLATILE |
				 EFI_VARIABLE_BOOTSERVICE_ACCESS |
				 EFI_VARIABLE_RUNTIME_ACCESS,
				 size, buf);
	size = sizeof(buf);
	SetMem(buf, sizeof(buf), 0);
	status = RT->GetVariable(L"tmp", &GV_GUID, &attrs, &size, buf);
	assert_equal_goto(status, EFI_SUCCESS, err, "0x%lx != 0x%lx\n");
	assert_equal_goto(size, 5, err, "%zu != %zu\n");
	assert_zero_goto(memcmp(buf, "bang", 5), err, "%d != %d\n");

	size = 5;
	strcpy(buf, "foop");
	status = RT->SetVariable(L"tmp", &GV_GUID, bs_rt_nv, size, buf);
	assert_equal_goto(status, EFI_SUCCESS, err, "0x%lx != 0x%lx\n");

	size = sizeof(buf);
	SetMem(buf, sizeof(buf), 0);
	status = RT->GetVariable(L"tmp", &GV_GUID, &attrs, &size, buf);
	assert_equal_goto(status, EFI_SUCCESS, err, "0x%lx != 0x%lx\n");
	assert_equal_goto(size, 5, err, "%zu != %zu\n");
	assert_zero_goto(memcmp(buf, "foop", 5), err, "%d != %d\n");

	size = 0;
	strcpy(buf, "");
	status = RT->SetVariable(L"tmp", &GV_GUID, bs_rt_nv | EFI_VARIABLE_APPEND_WRITE, size, buf);
	assert_equal_goto(status, EFI_SUCCESS, err, "0x%lx != 0x%lx\n");

	size = sizeof(buf);
	SetMem(buf, sizeof(buf), 0);
	status = RT->GetVariable(L"tmp", &GV_GUID, &attrs, &size, buf);
	assert_equal_goto(status, EFI_SUCCESS, err, "0x%lx != 0x%lx\n");
	assert_equal_goto(size, 5, err, "%zu != %zu\n");
	assert_zero_goto(memcmp(buf, "foop", 5), err, "%d != %d\n");

	size = 5;
	strcpy(buf, "poof");
	status = RT->SetVariable(L"tmp", &GV_GUID, bs_rt_nv | EFI_VARIABLE_APPEND_WRITE, size, buf);
	assert_equal_goto(status, EFI_SUCCESS, err, "0x%lx != 0x%lx\n");

	size = sizeof(buf);
	SetMem(buf, sizeof(buf), 0);
	status = RT->GetVariable(L"tmp", &GV_GUID, &attrs, &size, buf);
	assert_equal_goto(status, EFI_SUCCESS, err, "0x%lx != 0x%lx\n");
	assert_equal_goto(size, 10, err, "%zu != %zu\n");
	assert_zero_goto(memcmp(buf, "foop\0poof", 10), err, "%d != %d\n");
	ret = 0;
err:
	if (ret)
		mock_print_var_list(&mock_variables);
	mock_reset_variables();
	return ret;
}

static void
dump_config_table_if_wrong(const char * const func, int line, ...)
{
	va_list alist, blist;
	bool okay = true;
	size_t n = 0, m = 0;

	va_start(alist, line);
	va_copy(blist, alist);

	int idx = va_arg(alist, int);
	EFI_GUID *guid = va_arg(alist, EFI_GUID *);
	while (idx >= 0 && guid != NULL) {
		EFI_CONFIGURATION_TABLE *entry;
		if (idx < 0)
			goto nexta;

		n += 1;
		if (idx >= (int)ST->NumberOfTableEntries) {
			okay = false;
			goto nexta;
		}

		entry = &ST->ConfigurationTable[idx];
		if (CompareGuid(guid, &entry->VendorGuid) != 0)
			okay = false;

nexta:
		idx = va_arg(alist, int);
		guid = va_arg(alist, EFI_GUID *);
	}
	va_end(alist);

	if (okay)
		return;

	printf("%s:%d:%s(): %d entries:\n", __FILE__, line, func, ST->NumberOfTableEntries);
	idx = va_arg(blist, int);
	guid = va_arg(blist, EFI_GUID *);
	while (idx >= 0 && guid != NULL) {
		EFI_CONFIGURATION_TABLE *entry;

		if (idx >= (int)ST->NumberOfTableEntries) {
			printf("\t[%d]: invalid index for " GUID_FMT "\n",
			       idx, GUID_ARGS(*guid));
			goto nexta;
		}

		if (idx < 0) {
			printf("\t[%d]: " GUID_FMT "\n", idx, GUID_ARGS(*guid));
		} else {
			entry = &ST->ConfigurationTable[idx];
			printf("\t[%d]: %p ", idx, entry);
			printf("{.VendorGuid:" GUID_FMT ",", GUID_ARGS(entry->VendorGuid));
			printf("&.VendorTable:%p}\n", entry->VendorTable);
			if (CompareGuid(guid, &entry->VendorGuid) != 0)
				printf("\t\t\t  expected:" GUID_FMT "\n", GUID_ARGS(*guid));
		}
next:
		idx = va_arg(blist, int);
		guid = va_arg(blist, EFI_GUID *);
	}
	va_end(blist);

	if ((int)ST->NumberOfTableEntries - n == 0)
		return;

	printf("%d extra table entries:\n", ST->NumberOfTableEntries - n);
	for (m = n; m < ST->NumberOfTableEntries; m++) {
		EFI_CONFIGURATION_TABLE *entry;

		entry = &ST->ConfigurationTable[m];

		printf("\t[%d]: %p ", m, entry);
		printf("{.VendorGuid:" GUID_FMT ",", GUID_ARGS(entry->VendorGuid));
		printf("&.VendorTable:%p}\n", entry->VendorTable);
	}
}

static int
test_install_config_table_0(void)
{
	int ret = -1;
	EFI_STATUS status;

	/*
	 * These three guids are chosen on purpose: they start with "a",
	 * "b", and "c", respective to their variable names, so you can
	 * identify them when dumped.
	 */
	EFI_GUID aguid = SECURITY_PROTOCOL_GUID;
	char astr[guidstr_size];
	void *astrp = &astr[0];
	int aidx = -1;

	EFI_GUID bguid = EFI_HTTP_BINDING_GUID;
	char bstr[guidstr_size];
	void *bstrp = &bstr[0];
	int bidx = -1;

	EFI_GUID cguid = MOK_VARIABLE_STORE;
	char cstr[guidstr_size];
	void *cstrp = &cstr[0];
	int cidx = -1;

	EFI_GUID lip = LOADED_IMAGE_PROTOCOL;

	EFI_GUID guids[3];

	char tmpstr[guidstr_size];

	sprintf(astrp, GUID_FMT, GUID_ARGS(aguid));
	sprintf(bstrp, GUID_FMT, GUID_ARGS(bguid));
	sprintf(cstrp, GUID_FMT, GUID_ARGS(cguid));

	assert_equal_return(ST->NumberOfTableEntries, 0, -1, "%lu != %lu\n");

	/*
	 * test installing one
	 */
	status = BS->InstallConfigurationTable(&bguid, bstrp);
	assert_equal_return(status, EFI_SUCCESS, -1, "%lx != %lx\n");
	assert_equal_goto(ST->NumberOfTableEntries, 1, err, "%lu != %lu\n");

	sprintf(tmpstr, GUID_FMT, GUID_ARGS(ST->ConfigurationTable[0].VendorGuid));
	assert_zero_goto(CompareGuid(&ST->ConfigurationTable[0].VendorGuid, &bguid),
			 err, "%d != 0 (%s != %s)\n", tmpstr, bstr);
	assert_equal_goto(ST->ConfigurationTable[0].VendorTable,
			  bstrp, err, "%p != %p\n");

	/*
	 * test re-installing the same one
	 */
	status = BS->InstallConfigurationTable(&bguid, bstrp);
	assert_equal_goto(status, EFI_SUCCESS, err, "%lx != %lx\n");
	assert_equal_goto(ST->NumberOfTableEntries, 1, err, "%lu != %lu\n");

	sprintf(tmpstr, GUID_FMT, GUID_ARGS(ST->ConfigurationTable[0].VendorGuid));
	assert_zero_goto(CompareGuid(&ST->ConfigurationTable[0].VendorGuid, &bguid),
			 err, "%d != 0 (%s != %s)\n", tmpstr, bstr);
	assert_equal_goto(ST->ConfigurationTable[0].VendorTable,
			  bstrp, err, "%p != %p\n");

	/*
	 * Test installing a second one
	 */
	status = BS->InstallConfigurationTable(&aguid, astrp);
	assert_equal_goto(status, EFI_SUCCESS, err, "%lx != %lx\n");
	assert_equal_goto(ST->NumberOfTableEntries, 2, err, "%lu != %lu\n");

	switch (mock_config_table_sort_policy) {
		case MOCK_SORT_DESCENDING:
			aidx = 1;
			bidx = 0;
			break;
		case MOCK_SORT_PREPEND:
			aidx = 0;
			bidx = 1;
			break;
		case MOCK_SORT_APPEND:
			aidx = 1;
			bidx = 0;
			break;
		case MOCK_SORT_ASCENDING:
			aidx = 0;
			bidx = 1;
			break;
		default:
			break;
	}

	dump_config_table_if_wrong(__func__, __LINE__,
				   aidx, &aguid,
				   bidx, &bguid,
				   cidx, &cguid,
				   -1, NULL);

	sprintf(tmpstr, GUID_FMT, GUID_ARGS(ST->ConfigurationTable[aidx].VendorGuid));
	assert_zero_goto(CompareGuid(&ST->ConfigurationTable[aidx].VendorGuid, &aguid),
			 err, "%d != 0 (%s != %s)\n", tmpstr, astr);
	assert_equal_goto(ST->ConfigurationTable[aidx].VendorTable, astrp,
			  err, "%p != %p\n");

	sprintf(tmpstr, GUID_FMT, GUID_ARGS(ST->ConfigurationTable[bidx].VendorGuid));
	assert_zero_goto(CompareGuid(&ST->ConfigurationTable[bidx].VendorGuid, &bguid),
			 err, "%d != 0 (%s != %s)\n", tmpstr, bstr);
	assert_equal_goto(ST->ConfigurationTable[bidx].VendorTable, bstrp,
			  err, "%p != %p\n");

	/*
	 * Test installing a third one
	 */
	status = BS->InstallConfigurationTable(&cguid, cstrp);
	assert_equal_goto(status, EFI_SUCCESS, err, "%lx != %lx\n");
	assert_equal_goto(ST->NumberOfTableEntries, 3, err, "%lu != %lu\n");

	switch (mock_config_table_sort_policy) {
		case MOCK_SORT_DESCENDING:
			aidx = 2;
			bidx = 1;
			cidx = 0;
			break;
		case MOCK_SORT_PREPEND:
			aidx = 1;
			bidx = 2;
			cidx = 0;
			break;
		case MOCK_SORT_APPEND:
			aidx = 1;
			bidx = 0;
			cidx = 2;
			break;
		case MOCK_SORT_ASCENDING:
			aidx = 0;
			bidx = 1;
			cidx = 2;
			break;
		default:
			break;
	}

	dump_config_table_if_wrong(__func__, __LINE__,
				   aidx, &aguid,
				   bidx, &bguid,
				   cidx, &cguid,
				   -1, NULL);

	sprintf(tmpstr, GUID_FMT, GUID_ARGS(ST->ConfigurationTable[aidx].VendorGuid));
	assert_zero_goto(CompareGuid(&ST->ConfigurationTable[aidx].VendorGuid, &aguid),
			 err, "%d != 0 (%s != %s)\n", tmpstr, astr);
	assert_equal_goto(ST->ConfigurationTable[aidx].VendorTable, astrp,
			  err, "%p != %p\n");
	memcpy(&guids[aidx], &aguid, sizeof(EFI_GUID));

	sprintf(tmpstr, GUID_FMT, GUID_ARGS(ST->ConfigurationTable[bidx].VendorGuid));
	assert_zero_goto(CompareGuid(&ST->ConfigurationTable[bidx].VendorGuid, &bguid),
			 err, "%d != 0 (%s != %s)\n", tmpstr, bstr);
	assert_equal_goto(ST->ConfigurationTable[bidx].VendorTable, bstrp,
			  err, "%p != %p\n");
	memcpy(&guids[bidx], &bguid, sizeof(EFI_GUID));

	sprintf(tmpstr, GUID_FMT, GUID_ARGS(ST->ConfigurationTable[cidx].VendorGuid));
	assert_zero_goto(CompareGuid(&ST->ConfigurationTable[cidx].VendorGuid, &cguid),
			 err, "%d != 0 (%s != %s)\n", tmpstr, cstr);
	assert_equal_goto(ST->ConfigurationTable[cidx].VendorTable, cstrp,
			  err, "%p != %p\n");
	memcpy(&guids[cidx], &cguid, sizeof(EFI_GUID));

	/*
	 * Test removing NULL guid
	 */
	status = BS->InstallConfigurationTable(NULL, NULL);
	assert_equal_goto(status, EFI_INVALID_PARAMETER, err, "%lx != %lx\n");
	assert_equal_goto(ST->NumberOfTableEntries, 3, err, "%lu != %lu\n");

	/*
	 * Test removing a guid that's not present
	 */
	status = BS->InstallConfigurationTable(&lip, NULL);
	assert_equal_goto(status, EFI_NOT_FOUND, err, "%lx != %lx\n");
	assert_equal_goto(ST->NumberOfTableEntries, 3, err, "%lu != %lu\n");

	/*
	 * Test removing the middle one
	 */
	status = BS->InstallConfigurationTable(&guids[1], NULL);
	assert_equal_goto(status, EFI_SUCCESS, err, "%lx != %lx\n");
	assert_equal_goto(ST->NumberOfTableEntries, 2, err, "%lu != %lu\n");

	switch (mock_config_table_sort_policy) {
		case MOCK_SORT_DESCENDING:
			aidx = 1;
			bidx = -1;
			cidx = 0;
			break;
		case MOCK_SORT_PREPEND:
			aidx = -1;
			bidx = 1;
			cidx = 0;
			break;
		case MOCK_SORT_APPEND:
			aidx = -1;
			bidx = 0;
			cidx = 1;
			break;
		case MOCK_SORT_ASCENDING:
			aidx = 0;
			bidx = -1;
			cidx = 1;
			break;
		default:
			break;
	}

	dump_config_table_if_wrong(__func__, __LINE__,
				   aidx, &aguid,
				   bidx, &bguid,
				   cidx, &cguid,
				   -1, NULL);

	if (aidx >= 0) {
		sprintf(tmpstr, GUID_FMT, GUID_ARGS(ST->ConfigurationTable[aidx].VendorGuid));
		assert_zero_goto(CompareGuid(&ST->ConfigurationTable[aidx].VendorGuid, &aguid),
				 err, "%d != 0 (%s != %s)\n", tmpstr, astr);
		assert_equal_goto(ST->ConfigurationTable[aidx].VendorTable, astrp,
				  err, "%p != %p\n");
		memcpy(&guids[aidx], &aguid, sizeof(EFI_GUID));
	}

	if (bidx >= 0) {
		sprintf(tmpstr, GUID_FMT, GUID_ARGS(ST->ConfigurationTable[bidx].VendorGuid));
		assert_zero_goto(CompareGuid(&ST->ConfigurationTable[bidx].VendorGuid, &bguid),
				 err, "%d != 0 (%s != %s)\n", tmpstr, bstr);
		assert_equal_goto(ST->ConfigurationTable[bidx].VendorTable, bstrp,
				  err, "%p != %p\n");
		memcpy(&guids[bidx], &bguid, sizeof(EFI_GUID));
	}

	if (cidx >= 0) {
		sprintf(tmpstr, GUID_FMT, GUID_ARGS(ST->ConfigurationTable[cidx].VendorGuid));
		assert_zero_goto(CompareGuid(&ST->ConfigurationTable[cidx].VendorGuid, &cguid),
				 err, "%d != 0 (%s != %s)\n", tmpstr, cstr);
		assert_equal_goto(ST->ConfigurationTable[cidx].VendorTable, cstrp,
				  err, "%p != %p\n");
		memcpy(&guids[cidx], &cguid, sizeof(EFI_GUID));
	}

	/*
	 * Test removing the lowest one
	 */
	status = BS->InstallConfigurationTable(&guids[0], NULL);
	assert_equal_goto(status, EFI_SUCCESS, err, "%lx != %lx\n");
	assert_equal_goto(ST->NumberOfTableEntries, 1, err, "%lu != %lu\n");

	switch (mock_config_table_sort_policy) {
		case MOCK_SORT_DESCENDING:
			aidx = 0;
			bidx = -1;
			cidx = -1;
			break;
		case MOCK_SORT_PREPEND:
			aidx = -1;
			bidx = 0;
			cidx = -1;
			break;
		case MOCK_SORT_APPEND:
			aidx = -1;
			bidx = -1;
			cidx = 0;
			break;
		case MOCK_SORT_ASCENDING:
			aidx = -1;
			bidx = -1;
			cidx = 0;
			break;
		default:
			break;
	}

	dump_config_table_if_wrong(__func__, __LINE__,
				   aidx, &aguid,
				   bidx, &bguid,
				   cidx, &cguid,
				   -1, NULL);

	if (aidx >= 0) {
		sprintf(tmpstr, GUID_FMT, GUID_ARGS(ST->ConfigurationTable[aidx].VendorGuid));
		assert_zero_goto(CompareGuid(&ST->ConfigurationTable[aidx].VendorGuid, &aguid),
				 err, "%d != 0 (%s != %s)\n", tmpstr, astr);
		assert_equal_goto(ST->ConfigurationTable[aidx].VendorTable, astrp,
				  err, "%p != %p\n");
		memcpy(&guids[aidx], &aguid, sizeof(EFI_GUID));
	}

	if (bidx >= 0) {
		sprintf(tmpstr, GUID_FMT, GUID_ARGS(ST->ConfigurationTable[bidx].VendorGuid));
		assert_zero_goto(CompareGuid(&ST->ConfigurationTable[bidx].VendorGuid, &bguid),
				 err, "%d != 0 (%s != %s)\n", tmpstr, bstr);
		assert_equal_goto(ST->ConfigurationTable[bidx].VendorTable, bstrp,
				  err, "%p != %p\n");
		memcpy(&guids[bidx], &bguid, sizeof(EFI_GUID));
	}

	if (cidx >= 0) {
		sprintf(tmpstr, GUID_FMT, GUID_ARGS(ST->ConfigurationTable[cidx].VendorGuid));
		assert_zero_goto(CompareGuid(&ST->ConfigurationTable[cidx].VendorGuid, &cguid),
				 err, "%d != 0 (%s != %s)\n", tmpstr, cstr);
		assert_equal_goto(ST->ConfigurationTable[cidx].VendorTable, cstrp,
				  err, "%p != %p\n");
		memcpy(&guids[cidx], &cguid, sizeof(EFI_GUID));
	}

	/*
	 * Test removing the last one
	 */
	status = BS->InstallConfigurationTable(&guids[0], NULL);
	assert_equal_goto(status, EFI_SUCCESS, err, "%lx != %lx\n");
	assert_equal_goto(ST->NumberOfTableEntries, 0, err, "%lu != %lu\n");

	/*
	 * Test removing it again
	 */
	status = BS->InstallConfigurationTable(&guids[0], NULL);
	assert_equal_goto(status, EFI_NOT_FOUND, err, "%lx != %lx\n");
	assert_equal_goto(ST->NumberOfTableEntries, 0, err, "%lu != %lu\n");

	ret = 0;
err:
	while (ST->NumberOfTableEntries)
		BS->InstallConfigurationTable(&ST->ConfigurationTable[0].VendorGuid, NULL);
	mock_reset_config_table();

	return ret;
}

int
main(void)
{
	int status = 0;
	setbuf(stdout, NULL);

	const char *policies[] = {
		"MOCK_SORT_DESCENDING",
		"MOCK_SORT_PREPEND",
		"MOCK_SORT_APPEND",
		"MOCK_SORT_ASCENDING",
		"MOCK_SORT_MAX_SENTINEL"
	};

	test(test_filter_out_true);
	test(test_filter_out_false);

	for (int i = 0; i < MOCK_SORT_MAX_SENTINEL; i++) {
		mock_variable_sort_policy = i;
		mock_config_table_sort_policy = i;
		printf("%s: setting variable sort policy to %s\n",
		       program_invocation_short_name, policies[i]);

		test(test_gnvn_buf_size_0);
		test(test_gnvn_0);
		test(test_gnvn_1);

		test(test_install_config_table_0);
	}

	test(test_get_variable_0);
	test(test_set_variable_0);
	return status;
}

// vim:fenc=utf-8:tw=75:noet
