/* Copyright 2011 The ChromiumOS Authors
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 *
 * Routines for verifying a firmware image's signature.
 */

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

#include "2common.h"
#include "2misc.h"
#include "2secdata.h"
#include "2sysincludes.h"

const char *gbb_fname;
const char *vblock_fname;
const char *body_fname;

#if defined(ENABLE_HWCRYPTO_RSA_TESTS)
bool vb2api_hwcrypto_allowed(struct vb2_context *ctx)
{
	printf("hwcrypto is allowed.\n");
	return true;
}
#endif

/**
 * Local implementation which reads resources from individual files.  Could be
 * more elegant and read from image.bin, if we understood the fmap.
 */
vb2_error_t vb2ex_read_resource(struct vb2_context *c,
				enum vb2_resource_index index, uint32_t offset,
				void *buf, uint32_t size)
{
	const char *fname;
	FILE *f;
	int got_size;

	/* Get the filename for the resource */
	switch (index) {
	case VB2_RES_GBB:
		fname = gbb_fname;
		break;
	case VB2_RES_FW_VBLOCK:
		fname = vblock_fname;
		break;
	default:
		return VB2_ERROR_UNKNOWN;
	}

	/* Open file and seek to the requested offset */
	f = fopen(fname, "rb");
	if (!f)
		return VB2_ERROR_UNKNOWN;

	if (fseek(f, offset, SEEK_SET)) {
		fclose(f);
		return VB2_ERROR_UNKNOWN;
	}

	/* Read data and close file */
	got_size = fread(buf, 1, size, f);
	fclose(f);

	/* Return success if we read everything */
	return got_size == size ? VB2_SUCCESS : VB2_ERROR_UNKNOWN;
}

vb2_error_t vb2ex_tpm_clear_owner(struct vb2_context *c)
{
	// TODO: implement
	return VB2_SUCCESS;
}

/**
 * Save non-volatile and/or secure data if needed.
 */
static void save_if_needed(struct vb2_context *c)
{

	if (c->flags & VB2_CONTEXT_NVDATA_CHANGED) {
		// TODO: implement
		c->flags &= ~VB2_CONTEXT_NVDATA_CHANGED;
	}

	if (c->flags & VB2_CONTEXT_SECDATA_FIRMWARE_CHANGED) {
		// TODO: implement
		c->flags &= ~VB2_CONTEXT_SECDATA_FIRMWARE_CHANGED;
	}
}

/**
 * Verify firmware body
 */
static vb2_error_t hash_body(struct vb2_context *c)
{
	uint32_t remaining;
	uint8_t block[8192];
	uint32_t size;
	FILE *f;
	vb2_error_t rv;

	/* Open the body data */
	f = fopen(body_fname, "rb");
	if (!f)
		return VB2_ERROR_TEST_INPUT_FILE;

	/* Start the body hash */
	rv = vb2api_init_hash(c, VB2_HASH_TAG_FW_BODY);
	if (rv) {
		fclose(f);
		return rv;
	}

	remaining = vb2api_get_firmware_size(c);
	printf("Expect %d bytes of body...\n", remaining);

	/* Extend over the body */
	while (remaining) {
		size = sizeof(block);
		if (size > remaining)
			size = remaining;

		/* Read next body block */
		size = fread(block, 1, size, f);
		if (size <= 0)
			break;

		/* Hash it */
		rv = vb2api_extend_hash(c, block, size);
		if (rv) {
			fclose(f);
			return rv;
		}

		remaining -= size;
	}

	fclose(f);

	/* Check the result */
	rv = vb2api_check_hash(c);
	if (rv)
		return rv;

	return VB2_SUCCESS;
}

static void print_help(const char *progname)
{
	printf("Usage: %s <gbb> <vblock> <body>\n", progname);
}

int main(int argc, char *argv[])
{
	uint8_t workbuf[VB2_FIRMWARE_WORKBUF_RECOMMENDED_SIZE]
		__attribute__((aligned(VB2_WORKBUF_ALIGN)));
	struct vb2_context *ctx;
	struct vb2_shared_data *sd;
	vb2_error_t rv;

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

	/* Save filenames */
	gbb_fname = argv[1];
	vblock_fname = argv[2];
	body_fname = argv[3];

	/* Intialize workbuf with sentinel value to see how much we'll use. */
	uint32_t *ptr = (uint32_t *)workbuf;
	while ((uint8_t *)ptr + sizeof(*ptr) <= workbuf + sizeof(workbuf))
		*ptr++ = 0xbeefdead;

	/* Set up context */
	if (vb2api_init(workbuf, sizeof(workbuf), &ctx)) {
		printf("Failed to initialize workbuf.\n");
		return 1;
	}
	sd = vb2_get_sd(ctx);

	/* Initialize secure context */
	vb2api_secdata_firmware_create(ctx);
	vb2api_secdata_kernel_create(ctx);

	// TODO: optional args to set contents for nvdata, secdata?

	/* Do early init */
	printf("Phase 1...\n");
	rv = vb2api_fw_phase1(ctx);
	if (rv) {
		printf("Phase 1 wants recovery mode.\n");
		save_if_needed(ctx);
		return rv;
	}

	/* Determine which firmware slot to boot */
	printf("Phase 2...\n");
	rv = vb2api_fw_phase2(ctx);
	if (rv) {
		printf("Phase 2 wants reboot.\n");
		save_if_needed(ctx);
		return rv;
	}

	/* Try that slot */
	printf("Phase 3...\n");
	rv = vb2api_fw_phase3(ctx);
	if (rv) {
		printf("Phase 3 wants reboot.\n");
		save_if_needed(ctx);
		return rv;
	}

	/* Verify body */
	printf("Hash body...\n");
	rv = hash_body(ctx);
	save_if_needed(ctx);
	if (rv) {
		printf("Phase 4 wants reboot.\n");
		return rv;
	}

	printf("Yaay!\n");

	while ((uint8_t *)ptr > workbuf && *--ptr == 0xbeefdead)
		/* find last used workbuf offset */;
	printf("Workbuf used = %d bytes, high watermark = %zu bytes\n",
		sd->workbuf_used, (uint8_t *)ptr + sizeof(*ptr) - workbuf);

	return 0;
}
