/******************************************************************************
 * Copyright (c) 2004, 2008 IBM Corporation
 * All rights reserved.
 * This program and the accompanying materials
 * are made available under the terms of the BSD License
 * which accompanies this distribution, and is available at
 * http://www.opensource.org/licenses/bsd-license.php
 *
 * Contributors:
 *     IBM Corporation - initial implementation
 *****************************************************************************/

#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <cfgparse.h>
#include <time.h>
#include <calculatecrc.h>
#include <product.h>
#include "createcrc.h"
#include "crclib.h"

/* file length in bytes */
static uint64_t ui64globalFileSize = 0;
/* space for the file stream >= 4MB + 4bytes */
static unsigned char pucFileStream[4400000];
/* header length in bytes */
static uint64_t ui64globalHeaderSize = 0;
/* flag to filter detect the header in buildDataStream() */
static int iglobalHeaderFlag = 1;

static size_t min(size_t a, size_t b)
{
	return a < b ? a : b;
}

/**
 * Build the file image and store it as Data Stream of bytes
 * calculate a first CRC for the first file and
 * catch the position of this CRC
 */
int
buildDataStream(unsigned char *pucbuf, int size)
{
	if (ui64globalFileSize + size > sizeof(pucFileStream)) {
		printf("Error: File size is too big!\n");
		return -1;
	}

	/* copy the data into the destination buffer */
	memcpy(pucFileStream + ui64globalFileSize, pucbuf, size);
	ui64globalFileSize += size;

	if (iglobalHeaderFlag == 1) {	// catch header

		ui64globalHeaderSize = ui64globalFileSize;
		iglobalHeaderFlag = 0;
	}

	return 0;
}

/**
 * write Header.img
 */
int
createHeaderImage(int notime)
{
	int iCounter;
	uint64_t ui64RomAddr, ui64DataAddr;
	time_t caltime;
	struct tm *tm;
	char *pcVersion;
	char dastr[16] = { 0, };
	unsigned long long da = 0;

	struct stH stHeader = {
		.magic = FLASHFS_MAGIC,
		.platform_name = FLASHFS_PLATFORM_MAGIC,
		.platform_revision = FLASHFS_PLATFORM_REVISION,
		.ui64FileEnd = -1,
	};

	/* read driver info */
	pcVersion = getenv("DRIVER_NAME");
	if (!pcVersion)
		pcVersion = getenv("USER");
	if (!pcVersion)
		pcVersion = "unknown";
	memcpy(stHeader.version, pcVersion,
	       min(strlen(pcVersion), sizeof(stHeader.version)));

	if (!notime) {
		/* read time and write it into data stream */
		char *source_date_epoch;
		/* This assumes that (if set) the SOURCE_DATE_EPOCH environment variable
		   will contain a correct, positive integer in the time_t range */
		if ((source_date_epoch = getenv("SOURCE_DATE_EPOCH")) == NULL ||
		    (caltime = (time_t)strtoll(source_date_epoch, NULL, 10)) <= 0)
			caltime = time(NULL);
		if (caltime == -1) {
			printf("time error\n");
		}
		if ((tm = localtime(&caltime)) == NULL) {
			printf("local time error\n");
		}
		// length must be 13 instead 12 because of terminating
		// NUL. Therefore uH.stH.platform_revison must be
		// written later to overwrite the terminating NUL
		if (strftime(dastr, 15, "0x%Y%m%d%H%M", tm) == 0) {
			printf("strftime error\n");
		}
		da = cpu_to_be64(strtoll(dastr, NULL, 16));
	}
	memcpy(stHeader.date, &da, 8);

	/* read address of next file and address of header date, both are 64 bit values */
	ui64RomAddr = 0;
	ui64DataAddr = 0;
	for (iCounter = 0; iCounter < 8; iCounter++) {
		/* addr of next file */
		ui64RomAddr = (ui64RomAddr << 8) + pucFileStream[FLASHFS_ROMADDR + iCounter];
		/* addr of header data */
		ui64DataAddr = (ui64DataAddr << 8) + pucFileStream[FLASHFS_DATADDR + iCounter];
	}

	/* calculate final flash-header-size and flash-file-size */
	/* calculate end addr of header */
	ui64globalHeaderSize = (uint32_t) ui64DataAddr + sizeof(stHeader);
	/* cut 64 bit to place CRC for File-End */
	ui64globalHeaderSize -= 8;
	/* add 64 bit to place CRC behind File-End */
	ui64globalFileSize += 8;

	if (ui64globalHeaderSize >= ui64RomAddr) {
		printf("%s\n", "--- Header File to long");
		return 1;
	}

	/* fill free space in Header with zeros */
	memset(&pucFileStream[ui64DataAddr], 0, (ui64RomAddr - ui64DataAddr));
	/* place data to header */
	memcpy(&pucFileStream[ui64DataAddr], &stHeader, sizeof(stHeader));

	/* insert header length into data stream */
	*(uint64_t *) (pucFileStream + FLASHFS_HEADER_SIZE_ADDR) =
	    cpu_to_be64(ui64globalHeaderSize);

	/* insert flash length into data stream */
	*(uint64_t *) (pucFileStream + ui64DataAddr + FLASHFS_FILE_SIZE_ADDR) =
	    cpu_to_be64(ui64globalFileSize);

	/* insert zeros as placeholder for CRC */
	*(uint64_t *) (pucFileStream + ui64globalHeaderSize - 8) = 0;
	*(uint64_t *) (pucFileStream + ui64globalFileSize - 8) = 0;

	return 0;
}

/**
 *  insert header and file CRC into data stream
 *  do CRC check on header and file
 *  write data stream to disk
 */
int
writeDataStream(int iofd, int notime)
{
	uint64_t ui64FileCRC = 0, ui64HeaderCRC = 0, ui64RegisterMask;
	unsigned int uiRegisterLength;

	if (0 != createHeaderImage(notime)) {
		return 1;
	}

	createCRCParameter(&ui64RegisterMask, &uiRegisterLength);

	/* calculate CRC */
	ui64HeaderCRC = checkCRC(pucFileStream, ui64globalHeaderSize, 0);
	*(uint64_t *) (pucFileStream + ui64globalHeaderSize - 8) =
	    cpu_to_be64(ui64HeaderCRC);

	ui64FileCRC = checkCRC(pucFileStream, ui64globalFileSize, 0);
	*(uint64_t *) (pucFileStream + ui64globalFileSize - 8) =
	    cpu_to_be64(ui64FileCRC);

	/* check CRC-implementation */
	ui64HeaderCRC = calCRCword(pucFileStream, ui64globalHeaderSize, 0);
	ui64FileCRC = calCRCword(pucFileStream, ui64globalFileSize, 0);

	if ((ui64HeaderCRC != 0) || (ui64FileCRC != 0)) {
		printf("\n\n %s \n %s \n\n", "CRCs not correct implemented.",
		       " ---> Data will not be written do disk.");
		return -1;
	}

	/* write file image to disk */
	if (0 < write(iofd, pucFileStream, ui64globalFileSize))
		return 0;

	printf("<< write failed >>\n");
	return -1;
}
