/*
 * Copyright 2017 Richard Hughes <richard@hughsie.com>
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

#include "config.h"

#include <fwupdplugin.h>

#include "fu-test.h"

static void
fu_common_bcd_u8_func(void)
{
	struct {
		guint8 dec;
		guint8 bcd;
	} map[] = {
	    {0, 0},
	    {16, 0b00010110},
	    {99, 0b10011001},
	};
	for (guint i = 0; i < G_N_ELEMENTS(map); i++) {
		g_assert_cmpint(fu_common_to_bcd_u8(map[i].dec), ==, map[i].bcd);
		g_assert_cmpint(fu_common_from_bcd_u8(map[i].bcd), ==, map[i].dec);
	}
}

static void
fu_common_bcd_u16_func(void)
{
	struct {
		guint16 dec;
		guint16 bcd;
	} map[] = {
	    {0, 0},
	    {16, 0b00010110},
	    {1234, 0b0001001000110100},
	};
	for (guint i = 0; i < G_N_ELEMENTS(map); i++) {
		g_assert_cmpint(fu_common_to_bcd_u16(map[i].dec), ==, map[i].bcd);
		g_assert_cmpint(fu_common_from_bcd_u16(map[i].bcd), ==, map[i].dec);
	}
}

static void
fu_common_align_up_func(void)
{
	g_assert_cmpint(fu_common_align_up(0, 0), ==, 0);
	g_assert_cmpint(fu_common_align_up(5, 0), ==, 5);
	g_assert_cmpint(fu_common_align_up(5, 3), ==, 8);
	g_assert_cmpint(fu_common_align_up(1023, 10), ==, 1024);
	g_assert_cmpint(fu_common_align_up(1024, 10), ==, 1024);
	g_assert_cmpint(fu_common_align_up(G_MAXSIZE - 1, 10), ==, G_MAXSIZE);
}

static void
fu_common_checked_add_func(void)
{
	g_assert_cmpint(fu_size_checked_add(0, 0), ==, 0);
	g_assert_cmpint(fu_size_checked_add(0, 42), ==, 42);
	g_assert_cmpint(fu_size_checked_add(42, 0), ==, 42);
	g_assert_cmpint(fu_size_checked_add(G_MAXSIZE / 2, G_MAXSIZE / 2), ==, G_MAXSIZE - 1);
	g_assert_cmpint(fu_size_checked_add(G_MAXSIZE, 1), ==, G_MAXSIZE);
	g_assert_cmpint(fu_size_checked_add(G_MAXSIZE, G_MAXSIZE), ==, G_MAXSIZE);
}

static void
fu_common_checked_inc_func(void)
{
	gsize value = 0;
	gboolean ret;
	g_autoptr(GError) error = NULL;

	ret = fu_size_checked_inc(&value, 0, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	g_assert_cmpint(value, ==, 0);

	ret = fu_size_checked_inc(&value, 42, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	g_assert_cmpint(value, ==, 42);

	value = G_MAXSIZE / 2;
	ret = fu_size_checked_inc(&value, G_MAXSIZE / 2, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	g_assert_cmpint(value, ==, G_MAXSIZE - 1);

	value = G_MAXSIZE;
	ret = fu_size_checked_inc(&value, 1, &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA);
	g_assert_false(ret);
	g_assert_cmpint(value, ==, G_MAXSIZE);
}

static void
fu_common_checked_inc_product_func(void)
{
	gsize value = 123;
	gboolean ret;
	g_autoptr(GError) error = NULL;

	ret = fu_size_checked_inc_product(&value, 0, 0, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	g_assert_cmpint(value, ==, 123);

	ret = fu_size_checked_inc_product(&value, 0, 10, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	g_assert_cmpint(value, ==, 123);

	ret = fu_size_checked_inc_product(&value, 10, 10, &error);
	g_assert_no_error(error);
	g_assert_true(ret);
	g_assert_cmpint(value, ==, 223);

	ret = fu_size_checked_inc_product(&value, 2, G_MAXSIZE / 2, &error);
	g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA);
	g_assert_false(ret);
	g_assert_cmpint(value, ==, 223);
}

static void
fu_common_error_map_func(void)
{
	const FuErrorMapEntry entries[] = {
	    {0, FWUPD_ERROR_LAST, NULL},
	    {1, FWUPD_ERROR_NOT_SUPPORTED, "not supported"},
	};
	gboolean ret;
	g_autoptr(GError) error1 = NULL;
	g_autoptr(GError) error2 = NULL;
	g_autoptr(GError) error3 = NULL;

	ret = fu_error_map_entry_to_gerror(0, entries, G_N_ELEMENTS(entries), &error1);
	g_assert_no_error(error1);
	g_assert_true(ret);
	ret = fu_error_map_entry_to_gerror(1, entries, G_N_ELEMENTS(entries), &error2);
	g_assert_error(error2, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED);
	g_assert_false(ret);
	ret = fu_error_map_entry_to_gerror(255, entries, G_N_ELEMENTS(entries), &error3);
	g_assert_error(error3, FWUPD_ERROR, FWUPD_ERROR_INTERNAL);
	g_assert_false(ret);
}

static void
fu_common_bitwise_func(void)
{
	guint64 val = 0;

	g_assert_true(FU_BIT_IS_CLEAR(val, 1));
	g_assert_true(FU_BIT_IS_CLEAR(val, 63));
	g_assert_false(FU_BIT_IS_SET(val, 1));
	g_assert_false(FU_BIT_IS_SET(val, 63));

	FU_BIT_SET(val, 1);
	FU_BIT_SET(val, 63);
	g_assert_true(FU_BIT_IS_SET(val, 1));
	g_assert_true(FU_BIT_IS_SET(val, 63));
	g_assert_cmpint(val, ==, 0x8000000000000002ull);

	FU_BIT_CLEAR(val, 1);
	g_assert_cmpint(val, ==, 0x8000000000000000ull);
	FU_BIT_CLEAR(val, 63);
	g_assert_cmpint(val, ==, 0);
}

static void
fu_common_crc_func(void)
{
	guint8 buf[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09};

	g_assert_cmpint(fu_crc8(FU_CRC_KIND_B8_STANDARD, buf, sizeof(buf)), ==, (guint8)~0x7A);
	g_assert_cmpint(fu_crc16(FU_CRC_KIND_B16_USB, buf, sizeof(buf)), ==, 0x4DF1);
	g_assert_cmpint(fu_crc_misr16(0, buf, (sizeof(buf) / 2) * 2), ==, 0x40D);
	g_assert_cmpint(fu_crc_misr16(0xFFFF, buf, (sizeof(buf) / 2) * 2), ==, 0xFBFA);

	/* all the CRC32 variants, verified using https://crccalc.com/?method=CRC-32 */
	g_assert_cmpint(fu_crc32(FU_CRC_KIND_B32_STANDARD, buf, sizeof(buf)), ==, 0x40EFAB9E);
	g_assert_cmpint(fu_crc32(FU_CRC_KIND_B32_BZIP2, buf, sizeof(buf)), ==, 0x89AE7A5C);
	g_assert_cmpint(fu_crc32(FU_CRC_KIND_B32_JAMCRC, buf, sizeof(buf)), ==, 0xBF105461);
	g_assert_cmpint(fu_crc32(FU_CRC_KIND_B32_MPEG2, buf, sizeof(buf)), ==, 0x765185A3);
	g_assert_cmpint(fu_crc32(FU_CRC_KIND_B32_POSIX, buf, sizeof(buf)), ==, 0x037915C4);
	g_assert_cmpint(fu_crc32(FU_CRC_KIND_B32_SATA, buf, sizeof(buf)), ==, 0xBA55CCAC);
	g_assert_cmpint(fu_crc32(FU_CRC_KIND_B32_XFER, buf, sizeof(buf)), ==, 0x868E70FC);
	g_assert_cmpint(fu_crc32(FU_CRC_KIND_B32C, buf, sizeof(buf)), ==, 0x5A14B9F9);
	g_assert_cmpint(fu_crc32(FU_CRC_KIND_B32D, buf, sizeof(buf)), ==, 0x68AD8D3C);
	g_assert_cmpint(fu_crc32(FU_CRC_KIND_B32Q, buf, sizeof(buf)), ==, 0xE955C875);
}

static void
fu_common_guid_func(void)
{
	gboolean ret;
	guint8 buf[16] = {0};

	ret = fu_common_guid_is_plausible(buf);
	g_assert_false(ret);

	buf[0] = 0x5;
	ret = fu_common_guid_is_plausible(buf);
	g_assert_false(ret);

	for (guint i = 0; i < sizeof(buf); i++)
		buf[i] = 0xFF;
	ret = fu_common_guid_is_plausible(buf);
	g_assert_true(ret);
}

static void
fu_common_olson_timezone_id_func(void)
{
	g_autofree gchar *localtime = NULL;
	g_autofree gchar *timezone_id = NULL;
	g_autoptr(FuPathStore) pstore = fu_path_store_new();
	g_autoptr(GError) error = NULL;

#ifdef HOST_MACHINE_SYSTEM_DARWIN
	g_test_skip("not supported on Darwin");
	return;
#endif

	/* set up test harness */
	localtime = g_test_build_filename(G_TEST_DIST, "tests", "localtime", NULL);
	fu_path_store_set_path(pstore, FU_PATH_KIND_LOCALTIME, localtime);

	timezone_id = fu_common_get_olson_timezone_id(pstore, &error);
	g_assert_no_error(error);
#ifdef _WIN32
	/* we do not emulate this on Windows, so just check for anything */
	g_assert_nonnull(timezone_id);
#else
	g_assert_cmpstr(timezone_id, ==, "America/New_York");
#endif
}

static void
fu_common_random_func(void)
{
	guint cnt = 0;
	guint valid = 0;
	g_autofree gchar *passwd = NULL;
	g_autofree gchar *str = NULL;
	g_autoptr(GByteArray) buf = NULL;
	g_autoptr(GError) error = NULL;

	/* make sure the RNG is at least plausible */
	buf = fu_common_get_random(20, &error);
	g_assert_no_error(error);
	g_assert_nonnull(buf);
	g_assert_cmpint(buf->len, ==, 20);
	for (guint i = 0; i < buf->len; i++) {
		guint8 data = buf->data[i];
		if (data == 0x00 || data == 0xFF)
			cnt++;
	}
	str = fu_byte_array_to_string(buf);
	g_debug("%s", str);
	g_assert_cmpint(cnt, <, 10);

	/* generate a random string to be used as a password */
	passwd = fu_common_get_random_string(20, &error);
	g_assert_no_error(error);
	g_assert_nonnull(passwd);
	g_assert_cmpint(strlen(passwd), ==, 20);
	for (guint i = 0; passwd[i] != '\0'; i++) {
		gchar data = passwd[i];
		if (g_ascii_isalnum(data))
			valid++;
	}
	g_debug("%s", passwd);
	g_assert_cmpint(valid, ==, 20);
}

static void
fu_cpuid_func(void)
{
	g_autofree gchar *testdatadir = g_test_build_filename(G_TEST_DIST, "tests", NULL);
	g_autoptr(FuPathStore) pstore = fu_path_store_new();
	g_autoptr(GError) error = NULL;
	g_autoptr(GHashTable) cpu_attrs = NULL;

	fu_path_store_set_path(pstore, FU_PATH_KIND_PROCFS, testdatadir);
	cpu_attrs = fu_cpu_get_attrs(pstore, &error);
	g_assert_no_error(error);
	g_assert_nonnull(cpu_attrs);
	g_assert_cmpstr(g_hash_table_lookup(cpu_attrs, "vendor_id"), ==, "AuthenticAMD");
	g_assert_cmpstr(g_hash_table_lookup(cpu_attrs, "fpu_exception"), ==, "yes");
}

int
main(int argc, char **argv)
{
	(void)g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE);
	g_test_init(&argc, &argv, NULL);
	g_test_add_func("/fwupd/common/bcd/u8", fu_common_bcd_u8_func);
	g_test_add_func("/fwupd/common/bcd/u16", fu_common_bcd_u16_func);
	g_test_add_func("/fwupd/common/checked-add", fu_common_checked_add_func);
	g_test_add_func("/fwupd/common/checked-inc", fu_common_checked_inc_func);
	g_test_add_func("/fwupd/common/checked-inc-product", fu_common_checked_inc_product_func);
	g_test_add_func("/fwupd/common/error-map", fu_common_error_map_func);
	g_test_add_func("/fwupd/common/align-up", fu_common_align_up_func);
	g_test_add_func("/fwupd/common/bitwise", fu_common_bitwise_func);
	g_test_add_func("/fwupd/common/crc", fu_common_crc_func);
	g_test_add_func("/fwupd/common/guid", fu_common_guid_func);
	g_test_add_func("/fwupd/common/olson-timezone-id", fu_common_olson_timezone_id_func);
	g_test_add_func("/fwupd/common/random", fu_common_random_func);
	g_test_add_func("/fwupd/common/cpuid", fu_cpuid_func);
	return g_test_run();
}
