/* SPDX-License-Identifier: GPL-3.0-or-later */
/*
 *  Copyright (C) 2016-2021, Rasmus Althoff <info@ct800.net>
 *
 *  This file is part of CT800 (CRC firmware tool).
 *
 *  CT800 is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  CT800 is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with CT800. If not, see <http://www.gnu.org/licenses/>.
 *
*/

/* this tool calculates a CRC32 over the firmware file.

If you give the inputfile "example.bin",
the program will generate "example_crc.bin" and "example_crc.hex".

example usage:

./crctool ct800fw.bin 0x08000000
=> result: ct800fw_crc.bin (384k) and ct800fw_crc.hex (384k, starting at 0x08000000)

The start address can be given as 0x1234 or just 1234 (still in hexadecimal). Leading
zeros can be omitted.

The binary input file must not be bigger than 384k. It can be smaller, the rest
is automatically filled up with 0xff. The last four bytes contain the CRC32, most significant
byte first. The byte order is written byte by byte to avoid endianess issues.

In fact, there are enough make-crc-tools out there, but to dig into them to generate exactly
what's needed here would have taken more time than just code the already implemented CRC
running over a binary buffer and then spit out a hex file.

Despite, the whole project should be as self-contained as possible.
*/

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

/*BIN_FILE_SIZE must be a multiple of 64k*/
#define BIN_FILE_SIZE (384UL * 1024UL)

static uint8_t bin_file_buffer[BIN_FILE_SIZE];

/*CRC algorithm with 8 bits per iteration: invented by Dilip V. Sarwate in 1988.*/
static const uint32_t Crc32Table[256] = {
    0x00000000U, 0x77073096U, 0xEE0E612CU, 0x990951BAU, 0x076DC419U, 0x706AF48FU, 0xE963A535U, 0x9E6495A3U,
    0x0EDB8832U, 0x79DCB8A4U, 0xE0D5E91EU, 0x97D2D988U, 0x09B64C2BU, 0x7EB17CBDU, 0xE7B82D07U, 0x90BF1D91U,
    0x1DB71064U, 0x6AB020F2U, 0xF3B97148U, 0x84BE41DEU, 0x1ADAD47DU, 0x6DDDE4EBU, 0xF4D4B551U, 0x83D385C7U,
    0x136C9856U, 0x646BA8C0U, 0xFD62F97AU, 0x8A65C9ECU, 0x14015C4FU, 0x63066CD9U, 0xFA0F3D63U, 0x8D080DF5U,
    0x3B6E20C8U, 0x4C69105EU, 0xD56041E4U, 0xA2677172U, 0x3C03E4D1U, 0x4B04D447U, 0xD20D85FDU, 0xA50AB56BU,
    0x35B5A8FAU, 0x42B2986CU, 0xDBBBC9D6U, 0xACBCF940U, 0x32D86CE3U, 0x45DF5C75U, 0xDCD60DCFU, 0xABD13D59U,
    0x26D930ACU, 0x51DE003AU, 0xC8D75180U, 0xBFD06116U, 0x21B4F4B5U, 0x56B3C423U, 0xCFBA9599U, 0xB8BDA50FU,
    0x2802B89EU, 0x5F058808U, 0xC60CD9B2U, 0xB10BE924U, 0x2F6F7C87U, 0x58684C11U, 0xC1611DABU, 0xB6662D3DU,
    0x76DC4190U, 0x01DB7106U, 0x98D220BCU, 0xEFD5102AU, 0x71B18589U, 0x06B6B51FU, 0x9FBFE4A5U, 0xE8B8D433U,
    0x7807C9A2U, 0x0F00F934U, 0x9609A88EU, 0xE10E9818U, 0x7F6A0DBBU, 0x086D3D2DU, 0x91646C97U, 0xE6635C01U,
    0x6B6B51F4U, 0x1C6C6162U, 0x856530D8U, 0xF262004EU, 0x6C0695EDU, 0x1B01A57BU, 0x8208F4C1U, 0xF50FC457U,
    0x65B0D9C6U, 0x12B7E950U, 0x8BBEB8EAU, 0xFCB9887CU, 0x62DD1DDFU, 0x15DA2D49U, 0x8CD37CF3U, 0xFBD44C65U,
    0x4DB26158U, 0x3AB551CEU, 0xA3BC0074U, 0xD4BB30E2U, 0x4ADFA541U, 0x3DD895D7U, 0xA4D1C46DU, 0xD3D6F4FBU,
    0x4369E96AU, 0x346ED9FCU, 0xAD678846U, 0xDA60B8D0U, 0x44042D73U, 0x33031DE5U, 0xAA0A4C5FU, 0xDD0D7CC9U,
    0x5005713CU, 0x270241AAU, 0xBE0B1010U, 0xC90C2086U, 0x5768B525U, 0x206F85B3U, 0xB966D409U, 0xCE61E49FU,
    0x5EDEF90EU, 0x29D9C998U, 0xB0D09822U, 0xC7D7A8B4U, 0x59B33D17U, 0x2EB40D81U, 0xB7BD5C3BU, 0xC0BA6CADU,
    0xEDB88320U, 0x9ABFB3B6U, 0x03B6E20CU, 0x74B1D29AU, 0xEAD54739U, 0x9DD277AFU, 0x04DB2615U, 0x73DC1683U,
    0xE3630B12U, 0x94643B84U, 0x0D6D6A3EU, 0x7A6A5AA8U, 0xE40ECF0BU, 0x9309FF9DU, 0x0A00AE27U, 0x7D079EB1U,
    0xF00F9344U, 0x8708A3D2U, 0x1E01F268U, 0x6906C2FEU, 0xF762575DU, 0x806567CBU, 0x196C3671U, 0x6E6B06E7U,
    0xFED41B76U, 0x89D32BE0U, 0x10DA7A5AU, 0x67DD4ACCU, 0xF9B9DF6FU, 0x8EBEEFF9U, 0x17B7BE43U, 0x60B08ED5U,
    0xD6D6A3E8U, 0xA1D1937EU, 0x38D8C2C4U, 0x4FDFF252U, 0xD1BB67F1U, 0xA6BC5767U, 0x3FB506DDU, 0x48B2364BU,
    0xD80D2BDAU, 0xAF0A1B4CU, 0x36034AF6U, 0x41047A60U, 0xDF60EFC3U, 0xA867DF55U, 0x316E8EEFU, 0x4669BE79U,
    0xCB61B38CU, 0xBC66831AU, 0x256FD2A0U, 0x5268E236U, 0xCC0C7795U, 0xBB0B4703U, 0x220216B9U, 0x5505262FU,
    0xC5BA3BBEU, 0xB2BD0B28U, 0x2BB45A92U, 0x5CB36A04U, 0xC2D7FFA7U, 0xB5D0CF31U, 0x2CD99E8BU, 0x5BDEAE1DU,
    0x9B64C2B0U, 0xEC63F226U, 0x756AA39CU, 0x026D930AU, 0x9C0906A9U, 0xEB0E363FU, 0x72076785U, 0x05005713U,
    0x95BF4A82U, 0xE2B87A14U, 0x7BB12BAEU, 0x0CB61B38U, 0x92D28E9BU, 0xE5D5BE0DU, 0x7CDCEFB7U, 0x0BDBDF21U,
    0x86D3D2D4U, 0xF1D4E242U, 0x68DDB3F8U, 0x1FDA836EU, 0x81BE16CDU, 0xF6B9265BU, 0x6FB077E1U, 0x18B74777U,
    0x88085AE6U, 0xFF0F6A70U, 0x66063BCAU, 0x11010B5CU, 0x8F659EFFU, 0xF862AE69U, 0x616BFFD3U, 0x166CCF45U,
    0xA00AE278U, 0xD70DD2EEU, 0x4E048354U, 0x3903B3C2U, 0xA7672661U, 0xD06016F7U, 0x4969474DU, 0x3E6E77DBU,
    0xAED16A4AU, 0xD9D65ADCU, 0x40DF0B66U, 0x37D83BF0U, 0xA9BCAE53U, 0xDEBB9EC5U, 0x47B2CF7FU, 0x30B5FFE9U,
    0xBDBDF21CU, 0xCABAC28AU, 0x53B39330U, 0x24B4A3A6U, 0xBAD03605U, 0xCDD70693U, 0x54DE5729U, 0x23D967BFU,
    0xB3667A2EU, 0xC4614AB8U, 0x5D681B02U, 0x2A6F2B94U, 0xB40BBE37U, 0xC30C8EA1U, 0x5A05DF1BU, 0x2D02EF8DU

};

/*CRC table generator, just for info:
#define POLYNOMIAL  0xEDB88320U
uint32_t crc, i, j;
for (i = 0; i <= 0xFFU; i++)
{
    crc = i;
    for (j = 0; j < 8; j++)
        crc = (crc >> 1) ^ (-int32_t(crc & 1) & POLYNOMIAL);
    Crc32Table[i] = crc;
}
*/

/*CRC algorithm with 8 bits per iteration: invented by Dilip V. Sarwate in 1988.*/
static uint32_t Ct_Crc32(const void *buffer, size_t len)
{
    uint32_t crc = 0xFFFFFFFFU;
    const uint8_t* databyte = (const uint8_t*) buffer;

    while (len--)
    {
        uint32_t lookup = crc;
        lookup &= 0xFFU;
        lookup ^= *databyte++;
        crc >>= 8;
        crc ^= Crc32Table[lookup];
    }

    return(~crc);
}

/*each 64k block starts with an extended address.*/
static void Get_Extended_Address(char *address_record, uint32_t address)
{
    char buffer[12];
    uint8_t byte;
    uint8_t chksum = 6U;
    strcpy(address_record, ":02000004"); /*record type*/

    byte = (address >> 24 ) & 0xFFU;
    chksum += byte;
    sprintf(buffer, "%02X", byte);
    strcat(address_record, buffer);

    byte = (address >> 16 ) & 0xFFU;
    chksum += byte;
    sprintf(buffer, "%02X", byte);
    strcat(address_record, buffer);

    chksum = ~chksum;
    chksum++;

    sprintf(buffer, "%02X\r\n", chksum);
    strcat(address_record, buffer);
}

/*end of file.*/
static void Get_End_Record(char *end_record)
{
    strcpy(end_record, ":00000001FF\r\n");
}

/*one line of regular data, 16 bytes per line.*/
static void Get_Data_Record_16(char *data_record, const uint8_t *inputbuffer, uint32_t address)
{
    char buffer[12];
    uint8_t byte, i;
    uint8_t chksum = 16U;
    strcpy(data_record, ":10"); /*record length*/

    /*16 bit address within a 64 block*/
    byte = (address >> 8) & 0xFFU;
    chksum += byte;
    sprintf(buffer, "%02X", byte);
    buffer[2] = '\0';
    strcat(data_record, buffer);
    byte = (address     ) & 0xFFU;
    chksum += byte;
    sprintf(buffer, "%02X", byte);
    buffer[2] = '\0';
    strcat(data_record, buffer);

    strcat(data_record, "00"); /*record type*/

    for (i = 0; i < 16; i++)
    {
        byte = inputbuffer[i];
        chksum += byte;
        sprintf(buffer, "%02X", byte);
        buffer[2] = '\0';
        strcat(data_record, buffer);
    }

    chksum = ~chksum;
    chksum++;

    sprintf(buffer, "%02X\r\n", chksum);
    strcat(data_record, buffer);
}


int main(int argc, char* argv[])
{
    FILE *bin_file;
    size_t bytes_read, bytes_written;
    uint32_t crc32, addr, segment, blocks, start_addr_param, i;
    int ret; /*for error checking with file operations*/
    uint8_t crc_byte;
    char line[64];
    char filename[524];
    char ch;

    fprintf(stdout, "\r\nCT800 CRC tool V1.10\r\n\r\n");

    if (argc < 3)
    {
        fprintf(stderr, "ERROR. usage: crctool binfile hex-start-address\r\n");
        fprintf(stderr, "example: crctool my_bin_file.bin 0x08000000\r\n");
        return(-1);
    }

    strncpy(filename, argv[1], 510);
    filename[510] = '\0';
    i = strlen(filename);
    if (i > 0)
    {
        /*cut off a potential CR/LF at the end
        - that can happen if called from within a shell script.*/
        if ((filename[i-1] == '\r') || (filename[i-1] == '\n'))
        {
            filename[i-1] = '\0';
            i--;
            if (i > 0)
                if ((filename[i-1] == '\r') || (filename[i-1] == '\n'))
                    filename[i-1] = '\0';
        }
    }

    strncpy(line, argv[2], 20);
    line[20] = '\0';
    i = 0;

    /*if the hexadecimal start address is given as 0x12345678,
    ignore the 0x part.	but it might also be given as 56789ABC.
    or with less than eight characters, omitting leading zeros.*/
    if ((line[1]=='x') || (line[1]=='X'))
        i += 2;
    start_addr_param = 0;
    ch = line[i];
    if ((ch >= 'a') && (ch <= 'f'))
        ch += 'A' - 'a';
    while (((ch >= '0') && (ch <= '9')) || ((ch >= 'A') && (ch <= 'F')))
    {
        start_addr_param <<= 4;
        if ((ch >= '0') && (ch <= '9'))
            start_addr_param += ch - '0';
        else
            start_addr_param += ch - 'A';
        i++;
        ch = line[i];
        if ((ch >= 'a') && (ch <= 'f'))
            ch += 'A' - 'a';
    }

    filename[510] = '\0';
    bin_file = fopen(filename, "rb");
    if (bin_file == NULL)
    {
        fprintf(stderr, "ERROR: file %s not found.\r\n", filename);
        return(-2);
    }

    bytes_read = fread(bin_file_buffer, sizeof(uint8_t), BIN_FILE_SIZE, bin_file);
    /*close the input file*/
    fclose(bin_file);

    if (bytes_read < BIN_FILE_SIZE)
    {
        for (addr = bytes_read; addr < BIN_FILE_SIZE; addr++)
            bin_file_buffer[addr] = 0xFFU;
    }

    fprintf(stdout, "INFO: %"PRIu32"k read.\r\n", (uint32_t) (bytes_read/1024U));
    /*be clear to the user what the actual start address is.*/
    fprintf(stdout, "INFO: start address: 0x%08X\r\n", start_addr_param);

    /*last four bytes are for storing the CRC*/
    crc32 = Ct_Crc32(bin_file_buffer, BIN_FILE_SIZE - sizeof(uint32_t));

    fprintf(stdout, "INFO: CRC is (hex): ");

    crc_byte = (crc32 & 0xFF000000U) >> 24;
    fprintf(stdout, "%02X ", crc_byte);
    bin_file_buffer[BIN_FILE_SIZE - 4] = crc_byte;

    crc_byte = (crc32 & 0x00FF0000U) >> 16;
    fprintf(stdout, "%02X ", crc_byte);
    bin_file_buffer[BIN_FILE_SIZE - 3] = crc_byte;

    crc_byte = (crc32 & 0x0000FF00U) >> 8;
    fprintf(stdout, "%02X ", crc_byte);
    bin_file_buffer[BIN_FILE_SIZE - 2] = crc_byte;

    crc_byte = (crc32 & 0x000000FFU);
    fprintf(stdout, "%02X\r\n", crc_byte);
    bin_file_buffer[BIN_FILE_SIZE - 1] = crc_byte;


    /*generate a filename for the output file.
    if "example.bin" has been the input file name , then "example_crc.bin" will be
    the binary output file name.*/
    i = strlen(filename);
    if (i > 4)
        if (filename[i-4] == '.')
            filename[i-4] = '\0';
    strcat(filename, "_crc.bin");

    /*that's the binary output file*/
    bin_file = fopen(filename, "wb");
    if (bin_file == NULL)
    {
        fprintf(stderr, "ERROR: file %s not writable.\r\n", filename);
        return(-3);
    }

    /*write the binary output file to disk*/
    bytes_written = fwrite(bin_file_buffer, sizeof(uint8_t), BIN_FILE_SIZE, bin_file);
    fclose(bin_file);

    if (bytes_written != BIN_FILE_SIZE)
    {
        fprintf(stderr, "ERROR: file %s not writable.\r\n", filename);
        /*try to delete it, it is corrupted anyway.*/
        if (remove(filename) == 0)
            return(-4);
        else
            return(-5);
    }

    /*generate a filename for the hex format output file.
    if "example.bin" has been the input file name , then "example_crc.hex" will be
    the hex format output file name.*/
    i = strlen(filename);
    if (i > 4)
        if (filename[i-4] == '.')
            filename[i-4] = '\0';

    /*put out the hex format*/
    strcat(filename, ".hex");

    /*that's the hex output file*/
    bin_file = fopen(filename, "wb");
    if (bin_file == NULL)
    {
        fprintf(stderr, "ERROR: file %s not writable.\r\n", filename);
        return(-6);
    }

    addr = 0; /*reading the binary buffer*/
    segment = start_addr_param; /*the offset of the binary file*/

    for (blocks = 0; blocks < (BIN_FILE_SIZE/0x10000U); blocks++, segment += 0x10000U) /*64k blocks*/
    {
        Get_Extended_Address(line, segment);
        ret = fputs(line, bin_file);
        if (ret < 0) goto hex_file_end;
        /*there are 4096 lines of 16 bytes within a 64k block.*/
        for (i = 0; i < 4096U; i++, addr += 16U)
        {
            Get_Data_Record_16(line, &bin_file_buffer[addr], addr);
            ret = fputs(line, bin_file);
            if (ret < 0) goto hex_file_end; /*error handling if write fails*/
        }
    }

    /*end record*/
    Get_End_Record(line);
    ret = fputs(line, bin_file);

hex_file_end:
    fclose(bin_file);
    if (ret < 0)
    {
        /*writing the hex file failed.*/
        fprintf(stderr, "ERROR: file %s not writable.\r\n", filename);

        /*try to delete it, it is corrupted anyway.*/
        if (remove(filename) == 0)
            return(-7);
        else
            return(-8);
    }

    fprintf(stdout, "SUCCESS: %"PRIu32"k written.\r\n", (uint32_t)(BIN_FILE_SIZE/1024U));
    return(0);
}
