/* Copyright (c) 2014 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.
 *
 * Routines for verifying a kernel or disk image
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "2sysincludes.h"
#include "2api.h"
#include "2misc.h"
#include "host_common.h"
#include "util_misc.h"
#include "vboot_common.h"
#include "vboot_api.h"
#include "vboot_kernel.h"

static uint8_t *diskbuf;

static uint8_t shared_data[VB_SHARED_DATA_MIN_SIZE];
static VbSharedDataHeader *shared = (VbSharedDataHeader *)shared_data;
static VbNvContext nvc;

static LoadKernelParams params;
static VbCommonParams cparams;

VbError_t VbExDiskRead(VbExDiskHandle_t handle, uint64_t lba_start,
		       uint64_t lba_count, void *buffer)
{
	if (handle != (VbExDiskHandle_t)1)
		return VBERROR_UNKNOWN;
	if (lba_start >= params.streaming_lba_count)
		return VBERROR_UNKNOWN;
	if (lba_start + lba_count > params.streaming_lba_count)
		return VBERROR_UNKNOWN;

	memcpy(buffer, diskbuf + lba_start * 512, lba_count * 512);
	return VBERROR_SUCCESS;
}

VbError_t VbExDiskWrite(VbExDiskHandle_t handle, uint64_t lba_start,
			uint64_t lba_count, const void *buffer)
{
	if (handle != (VbExDiskHandle_t)1)
		return VBERROR_UNKNOWN;
	if (lba_start >= params.streaming_lba_count)
		return VBERROR_UNKNOWN;
	if (lba_start + lba_count > params.streaming_lba_count)
		return VBERROR_UNKNOWN;

	memcpy(diskbuf + lba_start * 512, buffer, lba_count * 512);
	return VBERROR_SUCCESS;
}

static void print_help(const char *progname)
{
	printf("\nUsage: %s <disk_image> <kernel.vbpubk>\n\n",
	       progname);
}

int main(int argc, char *argv[])
{
	VbPublicKey *kernkey;
	uint64_t disk_bytes = 0;
	int rv;

	if (argc < 3) {
		print_help(argv[0]);
		return 1;
	}

	/* Load disk file */
	/* TODO: is it better to mmap() in the long run? */
	diskbuf = ReadFile(argv[1], &disk_bytes);
	if (!diskbuf) {
		fprintf(stderr, "Can't read disk file %s\n", argv[1]);
		return 1;
	}

	/* Read public key */
	kernkey = (VbPublicKey *)vb2_read_packed_key(argv[2]);
	if (!kernkey) {
		fprintf(stderr, "Can't read key file %s\n", argv[2]);
		return 1;
	}

	/* Set up shared data blob */
	VbSharedDataInit(shared, sizeof(shared_data));
	VbSharedDataSetKernelKey(shared, kernkey);
	/* TODO: optional TPM current kernel version */

	/* Set up params */
	cparams.shared_data_blob = shared_data;
	cparams.shared_data_size = sizeof(shared_data);
	params.disk_handle = (VbExDiskHandle_t)1;
	params.bytes_per_lba = 512;
	params.streaming_lba_count = disk_bytes / 512;
	params.gpt_lba_count = params.streaming_lba_count;

	params.kernel_buffer_size = 16 * 1024 * 1024;
	params.kernel_buffer = malloc(params.kernel_buffer_size);
	if (!params.kernel_buffer) {
		fprintf(stderr, "Can't allocate kernel buffer\n");
		return 1;
	}

	/* TODO(chromium:441893): support dev-mode flag and external gpt flag */
	params.boot_flags = 0;

	/*
	 * LoadKernel() cares only about VBNV_DEV_BOOT_SIGNED_ONLY, and only in
	 * dev mode.  So just use defaults.
	 */
	VbNvSetup(&nvc);
	params.nv_context = &nvc;

	/*
	 * Set up vboot context.
	 *
	 * TODO: Propagate this up to higher API levels
	 */
	struct vb2_context ctx;
	memset(&ctx, 0, sizeof(ctx));
	/* No need to initialize ctx->nvdata[]; defaults are fine */
	/* TODO(chromium:441893): support dev-mode flag and external gpt flag */
	ctx.workbuf = malloc(VB2_KERNEL_WORKBUF_RECOMMENDED_SIZE);
	if (!ctx.workbuf) {
		fprintf(stderr, "Can't allocate workbuf\n");
		return 1;
	}
	ctx.workbuf_size = VB2_KERNEL_WORKBUF_RECOMMENDED_SIZE;

	if (VB2_SUCCESS != vb2_init_context(&ctx)) {
		free(ctx.workbuf);
		fprintf(stderr, "Can't init context\n");
		return 1;
	}

	/* Try loading kernel */
	rv = LoadKernel(&ctx, &params, &cparams);
	if (rv != VBERROR_SUCCESS) {
		fprintf(stderr, "LoadKernel() failed with code %d\n", rv);
		return 1;
	}

	printf("Found a good kernel.\n");
	printf("Partition number:   %u\n", params.partition_number);
	printf("Bootloader address: 0x%" PRIx64 "\n",
	       params.bootloader_address);

	/* TODO: print other things (partition GUID, nv_context, shared_data) */

	printf("Yaay!\n");
	return 0;
}
