/* SPDX-License-Identifier: LGPL-2.1-or-later */

#include "alloc-util.h"
#include "compress.h"
#include "fileio.h"
#include "tests.h"
#include "utf8.h"

/* Include the implementation directly, so we can poke at some internals. */
#include "bcd.c"

static void load_bcd(const char *path, void **ret_bcd, size_t *ret_bcd_len) {
        size_t len;
        _cleanup_free_ char *fn = NULL, *compressed = NULL;

        assert_se(get_testdata_dir(path, &fn) >= 0);
        assert_se(read_full_file_full(AT_FDCWD, fn, UINT64_MAX, SIZE_MAX, 0, NULL, &compressed, &len) >= 0);
        assert_se(decompress_blob_zstd(compressed, len, ret_bcd, ret_bcd_len, SIZE_MAX) >= 0);
}

static void test_get_bcd_title_one(
                const char *path,
                const char16_t *title_expect,
                size_t title_len_expect) {

        size_t len;
        _cleanup_free_ void *bcd = NULL;

        log_info("/* %s(%s) */", __func__, path);

        load_bcd(path, &bcd, &len);

        char16_t *title = get_bcd_title(bcd, len);
        if (title_expect) {
                assert_se(title);
                assert_se(memcmp(title, title_expect, title_len_expect) == 0);
        } else
                assert_se(!title);
}

TEST(get_bcd_title) {
        test_get_bcd_title_one("test-bcd/win10.bcd.zst", u"Windows 10", sizeof(u"Windows 10"));

        test_get_bcd_title_one("test-bcd/description-bad-type.bcd.zst", NULL, 0);
        test_get_bcd_title_one("test-bcd/description-empty.bcd.zst", NULL, 0);
        test_get_bcd_title_one("test-bcd/description-missing.bcd.zst", NULL, 0);
        test_get_bcd_title_one("test-bcd/description-too-small.bcd.zst", NULL, 0);
        test_get_bcd_title_one("test-bcd/displayorder-bad-name.bcd.zst", NULL, 0);
        test_get_bcd_title_one("test-bcd/displayorder-bad-size.bcd.zst", NULL, 0);
        test_get_bcd_title_one("test-bcd/displayorder-bad-type.bcd.zst", NULL, 0);
        test_get_bcd_title_one("test-bcd/empty.bcd.zst", NULL, 0);
}

TEST(base_block) {
        size_t len;
        BaseBlock backup;
        uint8_t *bcd_base;
        _cleanup_free_ BaseBlock *bcd = NULL;

        load_bcd("test-bcd/win10.bcd.zst", (void **) &bcd, &len);
        backup = *bcd;
        bcd_base = (uint8_t *) bcd;

        assert_se(get_bcd_title(bcd_base, len));

        /* Try various "corruptions" of the base block. */

        assert_se(!get_bcd_title(bcd_base, sizeof(BaseBlock) - 1));

        bcd->sig = 0;
        assert_se(!get_bcd_title(bcd_base, len));
        *bcd = backup;

        bcd->version_minor = 2;
        assert_se(!get_bcd_title(bcd_base, len));
        *bcd = backup;

        bcd->version_major = 4;
        assert_se(!get_bcd_title(bcd_base, len));
        *bcd = backup;

        bcd->type = 1;
        assert_se(!get_bcd_title(bcd_base, len));
        *bcd = backup;

        bcd->primary_seqnum++;
        assert_se(!get_bcd_title(bcd_base, len));
        *bcd = backup;
}

TEST(bad_bcd) {
        size_t len;
        uint8_t *hbins;
        uint32_t offset;
        _cleanup_free_ void *bcd = NULL;

        /* This BCD hive has been manipulated to have bad offsets/sizes at various places. */
        load_bcd("test-bcd/corrupt.bcd.zst", &bcd, &len);

        assert_se(len >= HIVE_CELL_OFFSET);
        hbins = (uint8_t *) bcd + HIVE_CELL_OFFSET;
        len -= HIVE_CELL_OFFSET;
        offset = ((BaseBlock *) bcd)->root_cell_offset;

        const Key *root = get_key(hbins, len, offset, "\0");
        assert_se(root);
        assert_se(!get_key(hbins, sizeof(Key) - 1, offset, "\0"));

        assert_se(!get_key(hbins, len, offset, "\0BadOffset\0"));
        assert_se(!get_key(hbins, len, offset, "\0BadSig\0"));
        assert_se(!get_key(hbins, len, offset, "\0BadKeyNameLen\0"));
        assert_se(!get_key(hbins, len, offset, "\0SubkeyBadOffset\0Dummy\0"));
        assert_se(!get_key(hbins, len, offset, "\0SubkeyBadSig\0Dummy\0"));
        assert_se(!get_key(hbins, len, offset, "\0SubkeyBadNEntries\0Dummy\0"));

        assert_se(!get_key_value(hbins, len, root, "Dummy"));

        const Key *kv_bad_offset = get_key(hbins, len, offset, "\0KeyValuesBadOffset\0");
        assert_se(kv_bad_offset);
        assert_se(!get_key_value(hbins, len, kv_bad_offset, "Dummy"));

        const Key *kv_bad_n_key_values = get_key(hbins, len, offset, "\0KeyValuesBadNKeyValues\0");
        assert_se(kv_bad_n_key_values);
        assert_se(!get_key_value(hbins, len, kv_bad_n_key_values, "Dummy"));

        const Key *kv = get_key(hbins, len, offset, "\0KeyValues\0");
        assert_se(kv);

        assert_se(!get_key_value(hbins, len, kv, "BadOffset"));
        assert_se(!get_key_value(hbins, len, kv, "BadSig"));
        assert_se(!get_key_value(hbins, len, kv, "BadNameLen"));
        assert_se(!get_key_value(hbins, len, kv, "InlineData"));
        assert_se(!get_key_value(hbins, len, kv, "BadDataOffset"));
        assert_se(!get_key_value(hbins, len, kv, "BadDataSize"));
}

TEST(argv_bcds) {
        for (int i = 1; i < saved_argc; i++) {
                size_t len;
                _cleanup_free_ void *bcd = NULL;

                assert_se(read_full_file_full(
                        AT_FDCWD,
                        saved_argv[i],
                        UINT64_MAX,
                        SIZE_MAX,
                        0,
                        NULL,
                        (char **) &bcd,
                        &len) >= 0);

                char16_t *title = get_bcd_title(bcd, len);
                if (title) {
                        _cleanup_free_ char *title_utf8 = utf16_to_utf8(title, char16_strlen(title) * 2);
                        log_info("%s: \"%s\"", saved_argv[i], title_utf8);
                } else
                        log_info("%s: Bad BCD", saved_argv[i]);
        }
}

DEFINE_TEST_MAIN(LOG_INFO);
