// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (C) 2020 Andreas Boehler <dev@aboehler.at>
 *
 * This tool was based on:
 *      firmware-crc.pl by Atheros Communications
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 as published
 * by the Free Software Foundation.
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <getopt.h>     /* for getopt() */
#include <byteswap.h>

char *infile;
char *outfile;
char *progname;
enum {
	MODEL_3390,
	MODEL_X490
} model;

#define CHUNK_SIZE 256

uint32_t crc32_for_byte(uint32_t r)
{
	for (int j = 0; j < 8; ++j)
		r = (r & 1 ? 0 : (uint32_t)0xEDB88320L) ^ r >> 1;
	return r ^ (uint32_t)0xFF000000L;
}

void crc32(const void *data, size_t n_bytes, uint32_t *crc)
{
	static uint32_t table[0x100];

	if (!*table)
		for (size_t i = 0; i < 0x100; ++i)
			table[i] = crc32_for_byte(i);
	for (size_t i = 0; i < n_bytes; ++i)
		*crc = table[(uint8_t)*crc ^ ((uint8_t *)data)[i]] ^ *crc >> 8;
}

static void usage(int status)
{
	fprintf(stderr, "Usage: %s [OPTIONS...]\n", progname);
	fprintf(stderr,
		"\n"
		"Options:\n"
		"  -i              input file name\n"
		"  -o              output file name\n"
		"  -m              model (3390, x490 for 3490/5490/7490)\n"
		"  -h              show this screen\n"
	);

	exit(status);
}

int main(int argc, char *argv[])
{
	uint32_t crc = 0;
	FILE *in_fp;
	FILE *out_fp;
	uint32_t buf[CHUNK_SIZE];
	size_t read;

	progname = argv[0];

	while (1) {
		int c;

		c = getopt(argc, argv, "i:o:m:h");
		if (c == -1)
			break;

		switch (c) {
		case 'i':
			infile = optarg;
			break;
		case 'o':
			outfile = optarg;
			break;
		case 'm':
			if (strcmp(optarg, "3390") == 0)
				model = MODEL_3390;
			else if (strcmp(optarg, "x490") == 0)
				model = MODEL_X490;
			else
				usage(EXIT_FAILURE);
			break;
		case 'h':
			usage(EXIT_SUCCESS);
		default:
			usage(EXIT_FAILURE);
			break;
		}
	}

	if (!infile || !outfile)
		usage(EXIT_FAILURE);

	in_fp = fopen(infile, "r");
	if (!in_fp) {
		fprintf(stderr, "Error opening input file: %s\n", infile);
		return EXIT_FAILURE;
	}
	out_fp = fopen(outfile, "w");
	if (!out_fp) {
		fprintf(stderr, "Error opening output file: %s\n", outfile);
		fclose(in_fp);
		return EXIT_FAILURE;
	}

	while (!feof(in_fp)) {
		switch (model) {
		case MODEL_3390:
			read = fread(buf, sizeof(uint32_t), CHUNK_SIZE, in_fp);
			if (ferror(in_fp)) {
				fprintf(stderr, "Error reading input file: %s\n", infile);
				fclose(in_fp);
				fclose(out_fp);
				return EXIT_FAILURE;
			}
			for (int i = 0; i < read; i++)
				crc = crc ^ buf[i];
			fwrite(buf, sizeof(uint32_t), read, out_fp);
			if (ferror(out_fp)) {
				fprintf(stderr, "Error writing output file: %s\n", outfile);
				fclose(in_fp);
				fclose(out_fp);
				return EXIT_FAILURE;
			}
			break;
		case MODEL_X490:
			read = fread(buf, 1, sizeof(uint32_t) * CHUNK_SIZE, in_fp);
			if (ferror(in_fp)) {
				fprintf(stderr, "Error reading input file: %s\n", infile);
				fclose(in_fp);
				fclose(out_fp);
				return EXIT_FAILURE;
			}
			crc32(buf, read, &crc);
			fwrite(buf, 1, read, out_fp);
			if (ferror(out_fp)) {
				fprintf(stderr, "Error writing output file: %s\n", outfile);
				fclose(in_fp);
				fclose(out_fp);
				return EXIT_FAILURE;
			}
			break;
		}
	}
	if (model == MODEL_X490)
		crc = bswap_32(crc);
	fwrite(&crc, sizeof(uint32_t), 1, out_fp);
	if (ferror(out_fp)) {
		fprintf(stderr, "Error writing checksum to output file: %s\n", outfile);
		fclose(in_fp);
		fclose(out_fp);
		return EXIT_FAILURE;
	}
	fclose(in_fp);
	fclose(out_fp);
	printf("Done.\n");
	return EXIT_SUCCESS;
}
