/* Copyright 2016 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.
 */

#include "config.h"

#include "board_id.h"
#include "byteorder.h"
#include "compile_time_macros.h"
#include "console.h"
#include "cryptoc/sha.h"
#include "dcrypto/dcrypto.h"
#include "extension.h"
#include "flash.h"
#include "flash_info.h"
#include "hooks.h"
#include "registers.h"
#include "signed_header.h"
#include "system.h"
#include "system_chip.h"
#include "uart.h"
#include "upgrade_fw.h"

#define CPRINTF(format, args...) cprintf(CC_EXTENSION, format, ## args)

/*
 * This structure defines flash offset ranges of the RO and RW images which
 * are not currently active and as such could be overwritten with an update.
 */
struct {
	uint32_t ro_base_offset;
	uint32_t ro_top_offset;
	uint32_t rw_base_offset;
	uint32_t rw_top_offset;
} valid_sections;

/* Pick sections where updates can go to based on current code addresses. */
static void set_valid_sections(void)
{
	 switch (system_get_ro_image_copy()) {
	 case SYSTEM_IMAGE_RO:
		 valid_sections.ro_base_offset = CHIP_RO_B_MEM_OFF;
		 break;
	 case SYSTEM_IMAGE_RO_B:
		 valid_sections.ro_base_offset = CONFIG_RO_MEM_OFF;
		 break;
	 default:
		 CPRINTF("Failed to set RO image offsets\n");
		 return;
	 }

	switch (system_get_image_copy()) {
	case SYSTEM_IMAGE_RW:
		valid_sections.rw_base_offset = CONFIG_RW_B_MEM_OFF;
		break;
	case SYSTEM_IMAGE_RW_B:
		valid_sections.rw_base_offset = CONFIG_RW_MEM_OFF;
		break;
	default:
		CPRINTF("Failed to set RW image offsets\n");
		return;
	}

	valid_sections.ro_top_offset = valid_sections.ro_base_offset +
		CONFIG_RO_SIZE - 0x800; /* 2K for certs! */

	valid_sections.rw_top_offset = valid_sections.rw_base_offset +
		CONFIG_RW_SIZE;
}

/*
 * Verify that the passed in block fits into the valid area. If it does, and
 * is destined to the base address of the area - erase the area contents.
 *
 * Return success, or indication of an erase failure or chunk not fitting into
 * valid area.
 */
static uint8_t check_update_chunk(uint32_t block_offset, size_t body_size)
{
	uint32_t base;
	uint32_t size;

	/* Is this an RW chunk? */
	if (valid_sections.rw_top_offset &&
	    (block_offset >= valid_sections.rw_base_offset) &&
	    ((block_offset + body_size) <= valid_sections.rw_top_offset)) {

		base = valid_sections.rw_base_offset;
		size = valid_sections.rw_top_offset -
			 valid_sections.rw_base_offset;
		/*
		 * If this is the first chunk for this section, it needs to
		 * be erased.
		 */
		if (block_offset == valid_sections.rw_base_offset) {
			if (flash_physical_erase(base, size) != EC_SUCCESS) {
				CPRINTF("%s:%d erase failure of 0x%x..+0x%x\n",
					__func__, __LINE__, base, size);
				return UPGRADE_ERASE_FAILURE;
			}
		}

		return UPGRADE_SUCCESS;
	}

	/* Is this an RO chunk? */
	if (valid_sections.ro_top_offset &&
	    (block_offset >= valid_sections.ro_base_offset) &&
	    ((block_offset + body_size) <= valid_sections.ro_top_offset)) {
		/*
		 * If this is the first chunk for this section, it needs to
		 * be erased.
		 */
		if (block_offset == valid_sections.ro_base_offset) {
			uint32_t base;
			uint32_t size;

			base = valid_sections.ro_base_offset;
			size = valid_sections.ro_top_offset -
				valid_sections.ro_base_offset;
			/* backup RO area write access needs to be enabled. */
			flash_open_ro_window(base, size);
			if (flash_physical_erase(base, size) != EC_SUCCESS) {
				CPRINTF("%s:%d erase failure of 0x%x..+0x%x\n",
					__func__, __LINE__, base, size);
				return UPGRADE_ERASE_FAILURE;
			}
		}
		return UPGRADE_SUCCESS;
	}

	CPRINTF("%s:%d %x, %d ro base %x top %x, rw base %x top %x\n",
		__func__, __LINE__,
		block_offset, body_size,
		valid_sections.ro_base_offset,
		valid_sections.ro_top_offset,
		valid_sections.rw_base_offset,
		valid_sections.rw_top_offset);

	return UPGRADE_BAD_ADDR;
}

int usb_pdu_valid(struct upgrade_command *cmd_body,  size_t cmd_size)
{
	uint8_t sha1_digest[SHA_DIGEST_SIZE];
	size_t body_size = cmd_size - offsetof(struct update_frame_header,
					       cmd.block_base);

	/* Check if the block was received properly. */
	DCRYPTO_SHA1_hash((uint8_t *)&cmd_body->block_base,
			  body_size + sizeof(cmd_body->block_base),
			  sha1_digest);
	if (memcmp(sha1_digest, &cmd_body->block_digest,
		   sizeof(cmd_body->block_digest))) {
		CPRINTF("%s:%d sha1 %x not equal received %x\n",
			__func__, __LINE__,
			*(uint32_t *)sha1_digest, cmd_body->block_digest);
		return 0;
	}

	return 1;
}

#ifdef CR50_DEV
#ifndef CONFIG_IGNORE_G_UPDATE_CHECKS
#define CONFIG_IGNORE_G_UPDATE_CHECKS
#endif
#endif

#ifndef CONFIG_IGNORE_G_UPDATE_CHECKS
/* Compare two versions, return True if the new version is older. */
static int new_is_older(const struct SignedHeader *new,
			const struct SignedHeader *old)
{
	if (new->epoch_ != old->epoch_)
		return new->epoch_ < old->epoch_;

	if (new->major_ != old->major_)
		return new->major_ < old->major_;


	return new->minor_ < old->minor_;
}

/*
 * Check if this chunk of data is a rollback attempt, or is unaligned,
 * overlaps RO or RW header, or would cause a board ID mismatch if attempted
 * to run.
 *
 * Return False if there is any of the above problems and set the passed in
 * error_code pointer to the proper error_code.
 */
static int contents_allowed(uint32_t block_offset,
			    size_t body_size, void *upgrade_data,
			    uint8_t *error_code)
{
	/* Pointer to RO or RW header in flash, to compare against. */
	const struct SignedHeader *header;
	int is_rw_header = 0;

	if (block_offset == valid_sections.ro_base_offset) {
		header = (const struct SignedHeader *)
			get_program_memory_addr(system_get_ro_image_copy());
	} else if (block_offset == valid_sections.rw_base_offset) {
		header = (const struct SignedHeader *)
			get_program_memory_addr(system_get_image_copy());
		is_rw_header = 1;
	} else {

		/*
		 * The received block is not destined to a header directly,
		 * but does it overlap with a header by any chance?
		 */
		int i;
		/* Base offsets of valid headers in flash. */
		uint32_t bases[] = { valid_sections.ro_base_offset,
				     valid_sections.rw_base_offset };
		/* Range of offsets this block is covering. */
		uint32_t range[] = { block_offset, block_offset + body_size };

		for (i = 0; i < ARRAY_SIZE(bases); i++) {
			int j;

			for (j = 0; j < ARRAY_SIZE(range); j++) {
				if ((range[j] >= bases[i]) &&
				    (range[j] <
				     (bases[i] +
				      sizeof(struct SignedHeader)))) {
					CPRINTF("%s:"
						" unaligned block overlaps\n",
						__func__);
					*error_code =
						UPGRADE_UNALIGNED_BLOCK_ERROR;
					return 0;
				}
			}
		}

		return 1;
	}

	/* This block is a header (ro or rw) of the new image. */
	if (body_size < sizeof(struct SignedHeader)) {
		CPRINTF("%s: block too short\n", __func__);
		*error_code = UPGRADE_TRUNCATED_HEADER_ERROR;
		return 0;
	}

	/* upgrade_data is the new header. */
	if (new_is_older(upgrade_data, header)) {
		CPRINTF("%s: rejecting an older header.\n", __func__);
		*error_code = UPGRADE_ROLLBACK_ERROR;
		return 0;
	}

	if (is_rw_header && board_id_mismatch(upgrade_data)) {
		CPRINTF("%s: rejecting Board ID mismatch.\n", __func__);
		*error_code = UPGRADE_BOARD_ID_ERROR;
		return 0;
	}

	return 1;
}


static uint32_t prev_offset;
static uint64_t prev_timestamp;
#define BACKOFF_TIME (60 * SECOND)

static int chunk_came_too_soon(uint32_t block_offset)
{
	int hard_reset = system_get_reset_flags() & RESET_FLAG_HARD;

	/*
	 * If it has been BACKOFF_TIME since the last time we wrote to a block
	 * or since the last boot, the write is ok.
	 */
	if ((get_time().val - prev_timestamp) > BACKOFF_TIME)
		return 0;

	if (!prev_timestamp) {
		/*
		 * If we just recovered from a hard reset, we have to wait until
		 * backoff time to accept an update. All other resets can accept
		 * updates immediately.
		 */
		if (hard_reset)
			CPRINTF("%s: rejecting a write after hard reset\n",
				__func__);
		return hard_reset;
	}

	if (!prev_offset ||
	    (block_offset >= (prev_offset + SIGNED_TRANSFER_SIZE)))
		return 0;

	CPRINTF("%s: rejecting a write to the same block\n", __func__);
	return 1;
}

static void new_chunk_written(uint32_t block_offset)
{
	prev_timestamp = get_time().val;
	prev_offset = block_offset;
}
#else
static int chunk_came_too_soon(uint32_t block_offset)
{
	return 0;
}

static void new_chunk_written(uint32_t block_offset)
{
}

static int contents_allowed(uint32_t block_offset,
			    size_t body_size, void *upgrade_data,
			    uint8_t *error_code)
{
	return 1;
}
#endif

void fw_upgrade_command_handler(void *body,
				size_t cmd_size,
				size_t *response_size)
{
	struct upgrade_command *cmd_body = body;
	void *upgrade_data;
	uint8_t *error_code = body;  /* Cache the address for code clarity. */
	size_t body_size;
	uint32_t block_offset;

	*response_size = 1; /* One byte response unless this is a start PDU. */

	if (cmd_size < sizeof(struct upgrade_command)) {
		CPRINTF("%s:%d\n", __func__, __LINE__);
		*error_code = UPGRADE_GEN_ERROR;
		return;
	}
	body_size = cmd_size - sizeof(struct upgrade_command);

	if (!cmd_body->block_base && !body_size) {
		struct first_response_pdu *rpdu = body;
		const struct SignedHeader *header;

		/*
		 * This is the connection establishment request, the response
		 * allows the server to decide what sections of the image to
		 * send to program into the flash.
		 */

		/* First, prepare the response structure. */
		memset(rpdu, 0, sizeof(*rpdu));
		*response_size = sizeof(*rpdu);
		rpdu->protocol_version = htobe32(UPGRADE_PROTOCOL_VERSION);

		/*
		 * Determine the valid upgrade sections.
		 */
		set_valid_sections();

		/*
		 * If there have been any problems when determining the valid
		 * Sections offsets/sizes - return an error code.
		 */
		if (!valid_sections.ro_top_offset ||
		    !valid_sections.rw_top_offset) {
			CPRINTF("%s:%d\n", __func__, __LINE__);
			rpdu->return_value = htobe32(UPGRADE_GEN_ERROR);
			return;
		}

		rpdu->backup_ro_offset =
			htobe32(valid_sections.ro_base_offset);

		rpdu->backup_rw_offset =
			htobe32(valid_sections.rw_base_offset);

		/* RO header information. */
		header = (const struct SignedHeader *)
			get_program_memory_addr(system_get_ro_image_copy());
		rpdu->shv[0].minor = htobe32(header->minor_);
		rpdu->shv[0].major = htobe32(header->major_);
		rpdu->shv[0].epoch = htobe32(header->epoch_);
		/* New with protocol version 5 */
		rpdu->keyid[0] = htobe32(header->keyid);

		/* RW header information. */
		header = (const struct SignedHeader *)
			get_program_memory_addr(system_get_image_copy());
		rpdu->shv[1].minor = htobe32(header->minor_);
		rpdu->shv[1].major = htobe32(header->major_);
		rpdu->shv[1].epoch = htobe32(header->epoch_);
		/* New with protocol version 5 */
		rpdu->keyid[1] = htobe32(header->keyid);
		return;
	}

	block_offset = be32toh(cmd_body->block_base);

	if (!usb_pdu_valid(cmd_body, cmd_size)) {
		*error_code = UPGRADE_DATA_ERROR;
		return;
	}

	upgrade_data = cmd_body + 1;
	if (!contents_allowed(block_offset, body_size,
			      upgrade_data, error_code))
		return;

	/* Check if the block will fit into the valid area. */
	*error_code = check_update_chunk(block_offset, body_size);
	if (*error_code)
		return;

	if (chunk_came_too_soon(block_offset)) {
		*error_code = UPGRADE_RATE_LIMIT_ERROR;
		return;
	}

	if ((block_offset == valid_sections.ro_base_offset) ||
	    (block_offset == valid_sections.rw_base_offset)) {
		/*
		 * This is the header coming, let's corrupt it so that it does
		 * not run until it's time to switch.
		 */
		struct SignedHeader *header;

		header = (struct SignedHeader *) upgrade_data;

		/*
		 * Set the top bit of the size field. It will be impossible to
		 * run this image until this bit is erased.
		 */
		header->image_size |= TOP_IMAGE_SIZE_BIT;
	}

	CPRINTF("%s: programming at address 0x%x\n", __func__,
		block_offset + CONFIG_PROGRAM_MEMORY_BASE);
	if (flash_physical_write(block_offset, body_size, upgrade_data)
	    != EC_SUCCESS) {
		*error_code = UPGRADE_WRITE_FAILURE;
		CPRINTF("%s:%d upgrade write error\n",	__func__, __LINE__);
		return;
	}

	new_chunk_written(block_offset);

	/* Verify that data was written properly. */
	if (memcmp(upgrade_data, (void *)
		   (block_offset + CONFIG_PROGRAM_MEMORY_BASE),
		   body_size)) {
		*error_code = UPGRADE_VERIFY_ERROR;
		CPRINTF("%s:%d upgrade verification error\n",
			__func__, __LINE__);
		return;
	}

	*error_code = UPGRADE_SUCCESS;
}

void fw_upgrade_complete(void)
{
	system_clear_retry_counter();
}
