/* Copyright 2021 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 *
 * Tests for miniOS kernel selection, loading, verification, and booting.
 */

#include "2api.h"
#include "2common.h"
#include "2misc.h"
#include "2nvstorage.h"
#include "2secdata.h"
#include "load_kernel_fw.h"
#include "test_common.h"
#include "vboot_api.h"

#define MAX_MOCK_KERNELS 10
#define KBUF_SIZE 65536

/* Internal struct to simulate a stream for sector-based disks */
struct disk_stream {
	/* Disk handle */
	VbExDiskHandle_t handle;

	/* Next sector to read */
	uint64_t sector;

	/* Number of sectors left */
	uint64_t sectors_left;
};

/* Represent a "kernel" located on the disk */
struct mock_kernel {
	/* Sector where the kernel begins */
	uint64_t sector;

	/* Return value from vb2_load_partition */
	vb2_error_t rv;

	/* Number of times the sector was read */
	int read_count;
};

/* Mock data */
static struct vb2_context *ctx;
static struct vb2_shared_data *sd;
static struct vb2_workbuf wb;
static uint8_t workbuf[VB2_KERNEL_WORKBUF_RECOMMENDED_SIZE]
	__attribute__((aligned(VB2_WORKBUF_ALIGN)));

static VbSelectAndLoadKernelParams lkp;
static VbDiskInfo disk_info;
static struct vb2_keyblock kbh;
static struct vb2_kernel_preamble kph;
static uint8_t kernel_buffer[80000];

static struct mock_kernel kernels[MAX_MOCK_KERNELS];
static int kernel_count;
static struct mock_kernel *cur_kernel;

static int mock_tpm_set_mode_calls;

static void add_mock_kernel(uint64_t sector, vb2_error_t rv)
{
	if (kernel_count >= ARRAY_SIZE(kernels)) {
		TEST_TRUE(0, "  kernel_count ran out of entries!");
		return;
	}

	kernels[kernel_count].sector = sector;
	kernels[kernel_count].rv = rv;
	kernel_count++;
}

static void reset_common_data(void)
{
	TEST_SUCC(vb2api_init(workbuf, sizeof(workbuf), &ctx),
		  "vb2api_init failed");
	vb2_workbuf_from_ctx(ctx, &wb);
	vb2_nv_init(ctx);
	vb2api_secdata_kernel_create(ctx);
	vb2_secdata_kernel_init(ctx);
	ctx->flags = VB2_CONTEXT_RECOVERY_MODE;

	sd = vb2_get_sd(ctx);
	sd->kernel_version_secdata = 0xabcdef | (1 << 24);

	memset(&lkp, 0, sizeof(lkp));
	lkp.kernel_buffer = kernel_buffer;
	lkp.kernel_buffer_size = sizeof(kernel_buffer);
	lkp.disk_handle = (VbExDiskHandle_t)1;

	memset(&disk_info, 0, sizeof(disk_info));
	disk_info.bytes_per_lba = 512;
	disk_info.lba_count = 1024;
	disk_info.handle = lkp.disk_handle;

	memset(&kbh, 0, sizeof(kbh));
	kbh.data_key.key_version = 2;
	kbh.keyblock_flags = VB2_KEYBLOCK_FLAG_DEVELOPER_0
		| VB2_KEYBLOCK_FLAG_DEVELOPER_1
		| VB2_KEYBLOCK_FLAG_RECOVERY_1
		| VB2_KEYBLOCK_FLAG_MINIOS_1;
	kbh.keyblock_size = sizeof(kbh);

	memset(&kph, 0, sizeof(kph));
	kph.kernel_version = 1;
	kph.preamble_size = 4096 - kbh.keyblock_size;
	kph.body_signature.data_size = 0;
	kph.bootloader_address = 0xbeadd008;
	kph.bootloader_size = 0x1234;


	memset(&kernels, 0, sizeof(kernels));
	kernel_count = 0;
	cur_kernel = NULL;

	mock_tpm_set_mode_calls = 0;
}

/* Mocks */

vb2_error_t VbExStreamOpen(VbExDiskHandle_t handle, uint64_t lba_start,
			   uint64_t lba_count, VbExStream_t *stream)
{
	struct disk_stream *s;
	uint64_t i;

	if (!handle) {
		*stream = NULL;
		return VB2_ERROR_UNKNOWN;
	}

	if (lba_start + lba_count > disk_info.lba_count)
		return VB2_ERROR_UNKNOWN;

	s = malloc(sizeof(*s));
	s->handle = handle;
	s->sector = lba_start;
	s->sectors_left = lba_count;

	*stream = (void *)s;

	for (i = 0; i < kernel_count; i++) {
		if (kernels[i].sector == lba_start)
			cur_kernel = &kernels[i];
	}

	return VB2_SUCCESS;
}

vb2_error_t VbExStreamRead(VbExStream_t stream, uint32_t bytes, void *buffer)
{
	struct disk_stream *s = (struct disk_stream *)stream;
	uint64_t sectors;
	uint64_t i;

	if (!s)
		return VB2_ERROR_UNKNOWN;

	/* For now, require reads to be a multiple of the LBA size */
	if (bytes % disk_info.bytes_per_lba)
		return VB2_ERROR_UNKNOWN;

	/* Fail on overflow */
	sectors = bytes / disk_info.bytes_per_lba;
	if (sectors > s->sectors_left)
		return VB2_ERROR_UNKNOWN;

	memset(buffer, 0, bytes);
	for (i = 0; i < kernel_count; i++) {
		if (kernels[i].sector >= s->sector &&
		    kernels[i].sector < s->sector + sectors) {
			VB2_DEBUG("Simulating kernel %" PRIu64 " match\n", i);
			uint64_t buf_offset = (kernels[i].sector - s->sector)
				* disk_info.bytes_per_lba;
			memcpy(buffer + buf_offset, VB2_KEYBLOCK_MAGIC,
			       VB2_KEYBLOCK_MAGIC_SIZE);
			kernels[i].read_count++;
			TEST_TRUE(kernels[i].read_count <= 2,
				  "  Max read count exceeded");
		}
	}

	s->sector += sectors;
	s->sectors_left -= sectors;

	return VB2_SUCCESS;
}

void VbExStreamClose(VbExStream_t stream)
{
	free(stream);
}

vb2_error_t vb2_unpack_key_buffer(struct vb2_public_key *key,
				  const uint8_t *buf, uint32_t size)
{
	return cur_kernel->rv;
}

vb2_error_t vb2_verify_keyblock(struct vb2_keyblock *block, uint32_t size,
				const struct vb2_public_key *key,
				const struct vb2_workbuf *w)
{
	/* Use this as an opportunity to override the keyblock */
	memcpy((void *)block, &kbh, sizeof(kbh));

	return cur_kernel->rv;
}

vb2_error_t vb2_verify_keyblock_hash(const struct vb2_keyblock *block,
				     uint32_t size,
				     const struct vb2_workbuf *w)
{
	/* Use this as an opportunity to override the keyblock */
	memcpy((void *)block, &kbh, sizeof(kbh));

	return cur_kernel->rv;
}

vb2_error_t vb2_verify_kernel_preamble(struct vb2_kernel_preamble *preamble,
			       uint32_t size, const struct vb2_public_key *key,
			       const struct vb2_workbuf *w)
{
	/* Use this as an opportunity to override the preamble */
	memcpy((void *)preamble, &kph, sizeof(kph));

	return cur_kernel->rv;
}

vb2_error_t vb2_verify_data(const uint8_t *data, uint32_t size,
			    struct vb2_signature *sig,
			    const struct vb2_public_key *key,
			    const struct vb2_workbuf *w)
{
	return cur_kernel->rv;
}

vb2_error_t vb2_digest_buffer(const uint8_t *buf, uint32_t size,
			      enum vb2_hash_algorithm hash_alg, uint8_t *digest,
			      uint32_t digest_size)
{
	return cur_kernel->rv;
}

vb2_error_t vb2ex_tpm_set_mode(enum vb2_tpm_mode mode_val)
{
	mock_tpm_set_mode_calls++;
	return VB2_SUCCESS;
}

/* Make sure nothing tested here ever calls this directly. */
void vb2api_fail(struct vb2_context *c, uint8_t reason, uint8_t subcode)
{
	TEST_TRUE(0, "  called vb2api_fail()");
}

/* Tests */

static void load_minios_kernel_tests(void)
{
	reset_common_data();
	disk_info.bytes_per_lba = KBUF_SIZE;
	disk_info.lba_count = 1;
	add_mock_kernel(0, VB2_SUCCESS);
	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info),
		  "{valid kernel}");
	TEST_EQ(mock_tpm_set_mode_calls, 1,
		"  TPM disabled");

	reset_common_data();
	disk_info.bytes_per_lba = KBUF_SIZE;
	disk_info.lba_count = 1;
	TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info),
		VB2_ERROR_LK_NO_KERNEL_FOUND, "{no kernel}");
	TEST_EQ(mock_tpm_set_mode_calls, 0,
		"  TPM not disabled");

	reset_common_data();
	disk_info.bytes_per_lba = KBUF_SIZE;
	disk_info.lba_count = 2;
	add_mock_kernel(1, VB2_SUCCESS);
	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info),
		  "{no kernel, valid kernel}");
	TEST_EQ(cur_kernel->sector, 1, "  select kernel");

	reset_common_data();
	disk_info.bytes_per_lba = KBUF_SIZE;
	disk_info.lba_count = 2;
	add_mock_kernel(0, VB2_ERROR_MOCK);
	add_mock_kernel(1, VB2_SUCCESS);
	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info),
		  "{invalid kernel, valid kernel}");
	TEST_EQ(cur_kernel->sector, 1, "  select second kernel");

	reset_common_data();
	disk_info.bytes_per_lba = KBUF_SIZE;
	disk_info.lba_count = 2;
	add_mock_kernel(0, VB2_ERROR_MOCK);
	add_mock_kernel(1, VB2_ERROR_MOCK);
	TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info),
		VB2_ERROR_LK_NO_KERNEL_FOUND,
		"{invalid kernel, invalid kernel}");
	TEST_EQ(mock_tpm_set_mode_calls, 0,
		"  TPM not disabled");

	reset_common_data();
	disk_info.bytes_per_lba = KBUF_SIZE;
	disk_info.lba_count = 2;
	add_mock_kernel(0, VB2_SUCCESS);
	add_mock_kernel(1, VB2_SUCCESS);
	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info),
		  "{valid kernel, valid kernel} minios_priority=0");
	TEST_EQ(cur_kernel->sector, 0, "  select first kernel");

	reset_common_data();
	disk_info.bytes_per_lba = KBUF_SIZE;
	disk_info.lba_count = 2;
	add_mock_kernel(0, VB2_SUCCESS);
	add_mock_kernel(1, VB2_SUCCESS);
	vb2_nv_set(ctx, VB2_NV_MINIOS_PRIORITY, 1);
	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info),
		  "{valid kernel, valid kernel} minios_priority=1");
	TEST_EQ(cur_kernel->sector, 1, "  select second kernel");

	reset_common_data();
	disk_info.bytes_per_lba = VB2_KEYBLOCK_MAGIC_SIZE;
	disk_info.lba_count = 4;
	add_mock_kernel(1, VB2_SUCCESS);
	TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info),
		VB2_ERROR_LK_NO_KERNEL_FOUND,
		"valid kernel header near start of disk (disk too small)");

	reset_common_data();
	disk_info.bytes_per_lba = VB2_KEYBLOCK_MAGIC_SIZE;
	disk_info.lba_count = 1000;
	add_mock_kernel(999, VB2_SUCCESS);
	TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info),
		VB2_ERROR_LK_NO_KERNEL_FOUND,
		"valid kernel header near end of disk");

	reset_common_data();
	disk_info.bytes_per_lba = 1024;
	disk_info.lba_count = 128;
	add_mock_kernel(63, VB2_SUCCESS);
	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info),
		  "start/end overlap assuming >128 MB search range (start)");

	reset_common_data();
	disk_info.bytes_per_lba = 1024;
	disk_info.lba_count = 128;
	add_mock_kernel(64, VB2_SUCCESS);
	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info),
		  "start/end overlap assuming >128 MB search range (end)");

	reset_common_data();
	disk_info.bytes_per_lba = 128;
	disk_info.lba_count = 1024;
	add_mock_kernel(3, VB2_SUCCESS);
	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info),
		  "kernel at last sector in batch assuming 512 KB batches");

	reset_common_data();
	disk_info.bytes_per_lba = 256;
	disk_info.lba_count = 1024;
	add_mock_kernel(3, VB2_SUCCESS);
	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info),
		  "kernel at last sector in batch assuming 1 MB batches");

	reset_common_data();
	disk_info.bytes_per_lba = 512;
	disk_info.lba_count = 1024;
	add_mock_kernel(3, VB2_SUCCESS);
	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info),
		  "kernel at last sector in batch assuming 2 MB batches");

	reset_common_data();
	kbh.keyblock_flags = VB2_KEYBLOCK_FLAG_DEVELOPER_0
		| VB2_KEYBLOCK_FLAG_RECOVERY_1
		| VB2_KEYBLOCK_FLAG_MINIOS_1;
	disk_info.bytes_per_lba = KBUF_SIZE;
	disk_info.lba_count = 2;
	add_mock_kernel(0, VB2_SUCCESS);
	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info),
		  "kernel with minios keyblock flag");

	reset_common_data();
	kbh.keyblock_flags = VB2_KEYBLOCK_FLAG_DEVELOPER_0
		| VB2_KEYBLOCK_FLAG_RECOVERY_1
		| VB2_KEYBLOCK_FLAG_MINIOS_0;
	disk_info.bytes_per_lba = KBUF_SIZE;
	disk_info.lba_count = 2;
	add_mock_kernel(0, VB2_SUCCESS);
	TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info),
		VB2_ERROR_LK_NO_KERNEL_FOUND,
		"kernel with !minios keyblock flag");

	reset_common_data();
	disk_info.bytes_per_lba = KBUF_SIZE;
	disk_info.lba_count = 2;
	add_mock_kernel(0, VB2_SUCCESS);
	sd->kernel_version_secdata = 5 << 24;
	kph.kernel_version = 4;
	TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info),
		VB2_ERROR_LK_NO_KERNEL_FOUND,
		"kernel version too old");

	reset_common_data();
	disk_info.bytes_per_lba = KBUF_SIZE;
	disk_info.lba_count = 2;
	add_mock_kernel(0, VB2_SUCCESS);
	sd->kernel_version_secdata = 5 << 24;
	kph.kernel_version = 0x100;
	TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info),
		VB2_ERROR_LK_NO_KERNEL_FOUND,
		"kernel version greater than 0xff");

	reset_common_data();
	disk_info.bytes_per_lba = KBUF_SIZE;
	disk_info.lba_count = 2;
	add_mock_kernel(0, VB2_SUCCESS);
	sd->kernel_version_secdata = 5 << 24;
	kph.kernel_version = 6;
	TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info),
		 "newer kernel version");
}

int main(void)
{
	load_minios_kernel_tests();

	return gTestSuccess ? 0 : 255;
}
