/*
 * Utility functions called from fips_fmp_hmac.sh.
 *
 * executed during Kernel build
 *
 * Copyright (c) 2014 Samsung Electronics
 *
 */

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

#define MAX_SIZE	0x100000

int update_fmp_hmac(const char *vmlinux_path, const char *hmac_path, unsigned long offset);
int collect_fmp_bytes(const char *in_file, const char *section_name, unsigned long offset,
			unsigned long size, const char *out_file);

int main(int argc, char **argv)
{
	if (argc < 2) {
		printf("\nUsage : \n");
		printf("fips_fmp_utils -u vmlinux_file hmac_file offset");
		printf("fips_fmp_utils -g vmlinux_file section_name offset size out_file");
		printf("\n");

		return -1;
	}

	if (!strncmp("-u", argv[1], sizeof("-u"))) {
		unsigned long offset = 0;
		unsigned char *vmlinux_file = NULL;
		unsigned char *hmac_file = NULL;

		if (argc != 5) {
			printf("\nUsage : \n");
			printf("fips_fmp_utils -u vmlinux_file hmac_file offset");
			printf("\n");

			return -1;
		}

		vmlinux_file = argv[2];
		hmac_file = argv[3];
		offset = atol(argv[4]);

		if (!vmlinux_file || !hmac_file || !offset) {
			printf ("./fips_fmp_utils -u vmlinux_file hmac_file offset");
			return -1;
		}

		return update_fmp_hmac(vmlinux_file, hmac_file, offset);
	} else if (!strncmp("-g", argv[1], sizeof("-g"))) {
		const char *in_file = NULL;
		const char *section_name = NULL;
		unsigned long offset = 0;
		unsigned long size = 0;
		const char *out_file = NULL;

		if (argc != 7) {
			printf("\nUsage : \n");
			printf("./fips_fmp_utils -g vmlinux_file section_name offset size out_file");
			printf("\n");

			return -1;
		}

		in_file = argv[2];
		section_name = argv[3];
		offset = atol(argv[4]);
		size = atol(argv[5]);
		out_file = argv[6];

		if (!in_file || !section_name || !offset || !size || !out_file) {
			printf("./fips_fmp_utils -g vmlinux_file section_name offset size out_file");
			return -1;
		}

		if (size > MAX_SIZE) {
			printf("Fail fips_fmp_utils due to invalid size: %ld\n", size);
			return -1;
		}

		return collect_fmp_bytes(in_file, section_name, offset, size, out_file);
	} else {
		printf("\nUsage : \n");
		printf("fips_fmp_utils -u vmlinux_file hmac_file offset");
		printf("fips_fmp_utils -g vmlinux_file section_name offset size out_file");
		printf("\n");
	}

	return -1;
}

/*
 * Given a vmlinux file, dumps "size" bytes from given "offset" to output file
 * in_file      : absolute path to vmlinux file
 * section_name : Used only for printing / debugging
 * offset       : offset in file from where to dump bytes
 * size         : how many bytes to dump
 * out_file     : Output file, where to dump bytes.
 *                Open in append mode, to keep previous bytes, if present
 *                Caller need to clean up before 1st call
 *
 * Returns 0, if success
 *        -1, if error
 */

int collect_fmp_bytes(const char *in_file, const char *section_name, unsigned long offset,
                      unsigned long size, const char *out_file)
{
	FILE *in_fp = NULL;
	FILE *out_fp = NULL;
	unsigned int i = 0;
	unsigned char data = 0;

	if (!in_file || !section_name || !offset || !size || !out_file) {
		printf ("collect_fmp_bytes : Invalid arguments");
		return -1;
	}

	printf("Section : %s\n", section_name);

	in_fp = fopen(in_file, "r");
	if (!in_fp) {
		printf("Unable to open file : %s", in_file);
		return -1;
	}

	if (fseek(in_fp, offset, SEEK_SET) != 0) {
		printf ("Unable to seek file : %s", in_file);
		fclose (in_fp);
		return -1;
	}

	out_fp = fopen(out_file, "ab");
	if (!out_fp) {
		printf ("Unable to open file : %s", out_file);
		fclose(in_fp);
		return -1;
	}

	if (size > MAX_SIZE) {
		printf("Fail fips_fmp_utils due to invalid size: %ld\n", size);
		return -1;
	}

	for (i = 1; i <= size; i++) {
		if ( 1 != fread (&data, sizeof(unsigned char), 1, in_fp)) {
			printf("Unable to read 1 byte from  file : %s", in_file);
			fclose(in_fp);
			fclose(out_fp);
			return -1;
		}

		printf ("%02x ", data);
		if (1 != fwrite (&data, 1, 1, out_fp)) {
			printf("Unable to write 1 byte to file : %s", out_file);
			fclose(in_fp);
			fclose(out_fp);
			return -1;
		}

		if (!(i % 16))
			printf("\n");
	}

	fclose(in_fp);
	fclose(out_fp);

	return 0;
}

#define SHA256_DIGEST_SIZE 32

/*
 * Given a vmlinux file, overwrites bytes at given offset with hmac bytes, available in
 * hmac file.
 * Return 0, if Success
 *       -1, if Error
 */
int update_fmp_hmac(const char *vmlinux_path, const char *hmac_path, unsigned long offset)
{
	FILE *vmlinux_fp = NULL;
	FILE *hmac_fp = NULL;
	int i = 0, j = 0;
	unsigned char hmac[SHA256_DIGEST_SIZE];

	if (!vmlinux_path || !hmac_path || !offset) {
		printf("FIPS update_fmp_hmac : Invalid Params");
		return -1;
	}

	vmlinux_fp = fopen(vmlinux_path, "r+b");
	if (!vmlinux_fp) {
		printf("Unable to open vmlinux file ");
		return -1;
	}

	hmac_fp = fopen(hmac_path, "rb");

	if (!hmac_fp) {
		printf("Unable to open hmac file ");
		fclose(vmlinux_fp);
		return -1;
	}

	if (SHA256_DIGEST_SIZE != fread(&hmac, sizeof(unsigned char), SHA256_DIGEST_SIZE, hmac_fp)) {
		printf("Unable to read %d bytes from hmac file", SHA256_DIGEST_SIZE);
		fclose(hmac_fp);
		fclose(vmlinux_fp);
		return -1;
	}

	if (fseek(vmlinux_fp, offset, SEEK_SET) != 0) {
		printf("Unable to seek into vmlinux file.");
		fclose(hmac_fp);
		fclose(vmlinux_fp);
		return -1;
	}

	if (SHA256_DIGEST_SIZE !=  fwrite (hmac, sizeof(unsigned char), SHA256_DIGEST_SIZE, vmlinux_fp)) {
		printf("Unable to write %d byte into vmlinux", SHA256_DIGEST_SIZE);
		fclose(hmac_fp);
		fclose(vmlinux_fp);
		return -1;
	}

	fclose(vmlinux_fp);
	fclose(hmac_fp);

	return 0;
}