#!/usr/bin/env python3

import argparse


#******************************************************************************
#
# CRC function that matches the CRC used by the Apollo bootloader.
#
#******************************************************************************
poly32 = 0x1EDC6F41
def crc32(L):
    rem = 0
    for b in L:
        rem = rem ^ (b << 24)
        for i in range(8):
            if rem & 0x80000000:
                rem = ((rem << 1) ^ poly32)
            else:
                rem = (rem << 1)

            rem = rem & 0xFFFFFFFF
    return rem

#******************************************************************************
#
# Read in the binary files and output the merged binary
#
#******************************************************************************
#def process(boot_loader_filename, app_filename,  output):
def process(load_address, app_filename, secinfo_file_name, app_ver, bin_type, str_type, output, align):

    # Open the file, and read it into an array of integers.
    with open(app_filename, mode = 'rb') as f_app:
        app_binarray = f_app.read()
        f_app.close()

    sec_binarray = bytearray([])
    seclen = 0
    # Open the file, and read it into an array of integers.
    if len(secinfo_file_name) > 0:
        with open(secinfo_file_name, mode='rb') as f_sec:
            sec_binarray= f_sec.read()
            seclen = len(sec_binarray)

    app_length  = len(app_binarray)

    hdr_length  = 48;   #fixed header length
    print("hdr_length ",hdr_length);

    #generate mutable byte array for the boot loader
    hdr_binarray = bytearray([0]*hdr_length);

    # Insert encryption flag, always 0
    hdr_binarray[0]  = 0x00;
    hdr_binarray[1]  = 0x00;
    hdr_binarray[2]  = 0x00;
    hdr_binarray[3]  = 0x00;
    
    # Insert the application binary load address.
    loadaddress = int(load_address,16)
    print("load_address ",hex(loadaddress), "(",load_address,")")
    hdr_binarray[4]  = (loadaddress >>  0) & 0x000000ff;
    hdr_binarray[5]  = (loadaddress >>  8) & 0x000000ff;
    hdr_binarray[6]  = (loadaddress >> 16) & 0x000000ff;
    hdr_binarray[7]  = (loadaddress >> 24) & 0x000000ff;


    app_length  = len(app_binarray)
    print("app_size ",hex(app_length), "(",app_length,")")
    app_crc = crc32(app_binarray)
    print("app_crc =  ",hex(app_crc));
    print("Security Info Length", hex(seclen))

    pad_binarray = bytearray([])
    pad_size = 0
    if (seclen > 0):
        if (seclen % int(align) != 0):
            # Add Padding
            pad_size = int(align) - (seclen % int(align))
            pad_binarray = bytearray([0]*pad_size);

    # put the application binary size
    # Total blob length
    hdr_binarray[8]   = ((app_length + pad_size + seclen) >>  0) & 0x000000ff
    hdr_binarray[9]   = ((app_length + pad_size + seclen) >>  8) & 0x000000ff
    hdr_binarray[10]  = ((app_length + pad_size + seclen) >> 16) & 0x000000ff
    hdr_binarray[11]  = ((app_length + pad_size + seclen) >> 24) & 0x000000ff
	
    # compute the CRC for the blob
    crc = crc32(sec_binarray + pad_binarray + app_binarray)
    print("blob crc =  ",hex(crc));
    hdr_binarray[12]  = (crc >>  0) & 0x000000ff
    hdr_binarray[13]  = (crc >>  8) & 0x000000ff
    hdr_binarray[14]  = (crc >> 16) & 0x000000ff
    hdr_binarray[15]  = (crc >> 24) & 0x000000ff

    hdr_binarray[16]  = ((seclen + pad_size) >>  0) & 0x000000ff
    hdr_binarray[17]  = ((seclen + pad_size) >>  8) & 0x000000ff
    hdr_binarray[18]  = ((seclen + pad_size) >> 16) & 0x000000ff
    hdr_binarray[19]  = ((seclen + pad_size) >> 24) & 0x000000ff

	# word RFU
    hdr_binarray[20] = 0xFF
    hdr_binarray[21] = 0xFF
    hdr_binarray[22] = 0xFF
    hdr_binarray[23] = 0xFF

	# word RFU
    hdr_binarray[24] = 0xFF
    hdr_binarray[25] = 0xFF
    hdr_binarray[26] = 0xFF
    hdr_binarray[27] = 0xFF

	# word RFU
    hdr_binarray[28] = 0xFF
    hdr_binarray[29] = 0xFF
    hdr_binarray[30] = 0xFF
    hdr_binarray[31] = 0xFF
	
	# application software version here
    print("app_ver",int(app_ver,16), "(",app_ver,")")
    appver = int(app_ver,16)
    hdr_binarray[32]  = (appver >>  0) & 0x000000ff;
    hdr_binarray[33]  = (appver >>  8) & 0x000000ff;
    hdr_binarray[34]  = (appver >> 16) & 0x000000ff;
    hdr_binarray[35]  = (appver >> 24) & 0x000000ff;
	
	# binary type here
    print("bin_type",int(bin_type,16), "(",bin_type,")")
    bintype = int(bin_type,16)
    hdr_binarray[36]  = (bintype >>  0) & 0x000000ff;
    hdr_binarray[37]  = (bintype >>  8) & 0x000000ff;
    hdr_binarray[38]  = (bintype >> 16) & 0x000000ff;
    hdr_binarray[39]  = (bintype >> 24) & 0x000000ff;
	
    # storage type here, 0 = internal, 1 = external
    print("str_type",int(str_type,16), "(",str_type,")")
    strtype = int(str_type,16)
    if strtype != 0:
        strtype = 1
    hdr_binarray[40] = (strtype >>  0) & 0x000000ff;
    hdr_binarray[41] = (strtype >>  8) & 0x000000ff;
    hdr_binarray[42] = (strtype >> 16) & 0x000000ff;
    hdr_binarray[43] = (strtype >> 24) & 0x000000ff;
	
	# image id
    hdr_binarray[44] = 0x01
    hdr_binarray[45] = 0x00
    hdr_binarray[46] = 0x00
    hdr_binarray[47] = 0x00


    # now output all three binary arrays in the proper order
    with open(output + '.bin', mode = 'wb') as out:
        out.write(hdr_binarray)
        if (seclen > 0):
            print('Adding Security Info of {} bytes...'.format(seclen), flush=True)
            out.write(sec_binarray)
            if (pad_size != 0):
                # Add Padding
                print('Adding padding of {} bytes...'.format(pad_size), flush=True)
                out.write(pad_binarray)
        out.write(app_binarray)

def parse_arguments():
    parser = argparse.ArgumentParser(description =
                     'Combine two binary files in to a single download.')

    parser.add_argument('--load-address', dest='loadaddress', default='0x4000',
                        help='Load address of the application.')

    parser.add_argument('--appbin', dest='appbin', default='../keil/bin/freertos_fit_amota.bin',
                        help='Application binary file (app.bin)')
                        
    parser.add_argument('--secbin', dest = 'secbin', default='',
                        help = 'Binary file for (optional) security information')

    # add arg version
    parser.add_argument('--version', dest='app_ver', default='0x0',
                        help = 'Software version of the OTA image.')
    
    # add arg binary type
    parser.add_argument('--binary-type', dest='bin_type', default='0x0',
                        help = 'Binary type (0 = Firmware, 1 = Data) to be tranferred OTA')
                                                    
    # add arg binary type
    parser.add_argument('--storage-type', dest='str_type', default='0x0',
                        help = 'Storage type to for the image OTA.')

    parser.add_argument('-o', dest = 'output', default = 'binary_array',
                        help = 'Output filename (without the extension)')

    parser.add_argument('-a', dest = 'alignment', default=4,
                        help = 'Desired alignment for appbin in image blob')


    args = parser.parse_args()

    return args

#******************************************************************************
#
# Main function.
#
#******************************************************************************
def main():
    # Read the arguments.
    args = parse_arguments()

    process(args.loadaddress, args.appbin, args.secbin, args.app_ver, args.bin_type, args.str_type, args.output, args.alignment)

if __name__ == '__main__':
    main()
