#!/usr/bin/env python
#
# Copyright (c) 2020 P&E Microcomputer Systems, Inc
# All rights reserved.
# Visit us at www.pemicro.com
#
# SPDX-License-Identifier:
# BSD-3-Clause
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# o Redistributions of source code must retain the above copyright notice, this list
#   of conditions and the following disclaimer.
#
# o Redistributions in binary form must reproduce the above copyright notice, this
#   list of conditions and the following disclaimer in the documentation and/or
#   other materials provided with the distribution.
#
# o Neither the names of the copyright holders nor the names of the
#   contributors may be used to endorse or promote products derived from this
#   software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import time
import platform
import ctypes
import sys
import os.path
from ctypes import *

# These password masks should be a power of 2. 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, etc so that
# Multiple password types can be requested at once
PASSWORD_TYPE_NONE = 0
PASSWORD_TYPE_IMXRT_RAW_64BIT = 0x00000001
PASSWORD_TYPE_IMXRT_CHALLENGE_RESPONSE_64BIT = 0x00000002
PASSWORD_TYPE_ILLEGAL = 0x80000000
PASSWORD_TYPE_ALL = 0xFFFFFFFF

CIPHER_TYPE_AES128_CBC = 0
CIPHER_TYPE_AES128_CTR = 1
CIPHER_TYPE_AES128_ECB = 2
CIPHER_TYPE_AES256_CBC = 3
CIPHER_TYPE_AES_RFC3394 = 4

PortType_Autodetect = 99
PortType_ParallelPortCable = 1
PortType_PCIBDMLightning = 2
PortType_USBMultilink = 3
PortType_CycloneProMaxSerial = 4
PortType_CycloneProMaxUSB = 5
PortType_CycloneProMaxEthernet = 6
PortType_OpenSDAUSB = 9

# Special Features for Power Management
pwr_set_power_options = 0x38000001      
pwr_turn_power_on = 0x38000011
pwr_turn_power_off = 0x38000012

# Special Features for debug communications mode
pe_arm_set_communications_mode = 0x44000001
pe_arm_set_debug_comm_swd = 0x00000000
pe_arm_set_debug_comm_jtag = 0x00000001

pe_arm_enable_debug_module = 0x44000002
pe_arm_write_ap_register = 0x44000003
pe_arm_read_ap_register = 0x44000004
pe_arm_write_dp_register = 0x44000007
pe_arm_read_dp_register = 0x44000008
pe_arm_flush_any_queued_data = 0x44000005

pe_arm_get_last_swd_status = 0x44000006
pe_arm_swd_status_ack = 0x04;
pe_arm_swd_status_wait = 0x02;
pe_arm_swd_status_fault = 0x01;

  
# Special Features for Setting current device and core
pe_generic_get_device_list = 0x58004000
pe_generic_select_device = 0x58004001
pe_generic_get_core_list = 0x58004002
pe_generic_select_core = 0x58004003
pe_set_default_application_files_directory = 0x58006000

class pemicroUnitAcmp(object):	
	
    def getUserFriendlySystemName(self):
        if platform.system() == "Windows":
            return "Windows"
        else:
            if platform.system() == "Linux":
                return "Linux"
            else:
                if platform.system() == "Darwin":
                    return "MacOS"
                else:
                    return "Unknown"
    def __init__(self):
        b_is_python_64bit = (ctypes.sizeof(ctypes.c_voidp) == 8)
        osutilitypath = os.path.join("libraries",self.getUserFriendlySystemName(),"PEMicro")
        if self.getUserFriendlySystemName() == "Windows":
            if b_is_python_64bit:
                self.name = "unitacmp-64.dll"
            else:
                self.name = "unitacmp-32.dll"
        else:
            if self.getUserFriendlySystemName() == "Linux":
                self.name = "unitacmp-64.so"
            else:
                self.name = "unitacmp-64.dylib"

        # load the library
        self.libraryLoaded = False
        self.connectedToDebugCable = False
        try:
            # Look in System Folders
            self.libraryPath = ''
            self.lib = cdll.LoadLibrary(self.name)
        except:
            try:
                # Look in the folder with .py file
                self.libraryPath = os.path.dirname(__file__)
                self.lib = cdll.LoadLibrary(os.path.join(self.libraryPath,self.name))
            except:
                try:
                    # Look in a structured subfolder
                    self.libraryPath = os.path.join(os.path.dirname(__file__),osutilitypath)
                    self.lib = cdll.LoadLibrary(os.path.join(self.libraryPath,self.name))
                except:
                    # Look up one directory
                    self.libraryPath = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'Deploy')
                    self.lib = cdll.LoadLibrary(os.path.join(self.libraryPath,self.name))
        if self.lib:
            self.libraryLoaded = True
			
			### DLL INITIALIZATION CALLS
            self.lib.version.restype = c_void_p
			
            self.lib.get_dll_version.restype = c_ushort

            self.lib.open_debug_file.argtypes = [c_void_p]
			
            self.lib.set_local_machine_ip_number.argtypes = [c_void_p]
			
			### CONNECTION INITIALIZATION CALLS 
            self.lib.reenumerate_all_port_types.restype = c_bool
			
            self.lib.get_enumerated_number_of_ports.argtypes = [c_ulong]
            self.lib.get_enumerated_number_of_ports.restype = c_ulong	

            self.lib.get_port_descriptor_short.argtypes = [c_ulong,c_ulong]
            self.lib.get_port_descriptor_short.restype = c_char_p

            self.lib.get_port_descriptor.argtypes = [c_ulong,c_ulong]
            self.lib.get_port_descriptor.restype = c_char_p	

            self.lib.open_port.argtypes = [c_ulong,c_ulong]
            self.lib.open_port.restype = c_bool

            self.lib.open_port_by_identifier.argtypes = [c_char_p]
            self.lib.open_port_by_identifier.restype = c_bool

            ### HARDWARE CONFIGURATIONS CALLS
            self.lib.check_critical_error.restype = c_byte	

            self.lib.set_debug_shift_frequency.argtypes = [c_ulong]

            self.lib.set_reset_delay_in_ms.argtypes = [c_ulong]	

            self.lib.get_cable_version.restype = c_ushort	

            ### EXECUTION CALLS
            self.lib.target_reset.restype = c_bool

            self.lib.target_halt.restype = c_bool

            self.lib.target_check_if_halted.restype = c_bool

            self.lib.target_resume.restype = c_bool

            self.lib.target_step.restype = c_bool
			
            ### MEMORY CALLS 
            self.lib.get_block.argtypes = [c_ulong,c_ulong,c_ulong,c_ulong,POINTER(c_ubyte),c_void_p]
            self.lib.get_block.restype = c_bool
			
            self.lib.put_block.argtypes = [c_ulong,c_ulong,c_ulong,c_ulong,c_void_p,c_void_p]
            self.lib.put_block.restype = c_bool

            self.lib.read_8bit_value.argtypes = [c_ulong,c_ulong,c_void_p]  
            self.lib.read_8bit_value.restype = c_byte

            self.lib.read_16bit_value.argtypes = [c_ulong,c_ulong,c_void_p]
            self.lib.read_16bit_value.restype = c_ushort

            self.lib.read_32bit_value.argtypes = [c_ulong,c_ulong,c_void_p]
            self.lib.read_32bit_value.restype = c_ulong
	
            self.lib.read_64bit_value.argtypes = [c_ulong,c_ulong,c_void_p]
            self.lib.read_64bit_value.restype = c_ulonglong

            self.lib.write_8bit_value.argtypes = [c_ulong,c_ulong,c_ulong,c_void_p]

            self.lib.write_16bit_value.argtypes = [c_ulong,c_ulong,c_ulong,c_void_p]

            self.lib.write_32bit_value.argtypes = [c_ulong,c_ulong,c_ulong,c_void_p]
			
            self.lib.write_64bit_value.argtypes = [c_ulong,c_ulong,c_ulonglong,c_void_p]

            self.lib.load_bin_file.argtypes = [c_void_p,c_long]
            self.lib.load_bin_file.restype = c_bool

            self.lib.load_srec_file.argtypes = [c_void_p,c_long]
            self.lib.load_srec_file = c_bool

            self.lib.get_mcu_register.argtypes = [c_ulong,c_ulong,POINTER(c_ulong)]
            self.lib.get_mcu_register.restype = c_bool

            self.lib.set_mcu_register.argtypes = [c_ulong,c_ulong,c_ulong]
            self.lib.set_mcu_register.restype = c_bool			
			
            self.lib.pe_special_features.argtypes = [c_ulong,c_bool,c_ulong,c_ulong,c_ulong,c_void_p,c_void_p]
            self.lib.pe_special_features.restype = c_bool

            self.lib.aes_encrypt_generic_raw.argtypes = [c_void_p,c_ulong,c_void_p,c_ulong,c_void_p,c_ulong, c_ulong, POINTER(c_ulong)]
            self.lib.aes_encrypt_generic_raw.restype = POINTER(c_ubyte)

            self.lib.aes_decrypt_generic_raw.argtypes = [c_void_p,c_ulong,c_void_p,c_ulong,c_void_p,c_ulong, c_ulong, POINTER(c_ulong)]
            self.lib.aes_decrypt_generic_raw.restype = POINTER(c_ubyte)

            self.lib.aes128_generate_random_key_or_iv.restype = POINTER(c_ubyte)

            self.lib.calculate_crc32.argtypes = [c_void_p,c_ulong,c_ulong,c_ulong]
            self.lib.calculate_crc32.restype = c_ulong

            self.lib.calculate_crc.argtypes = [c_void_p,c_ulong,c_ulong,c_ulong,c_ulong,c_ulong,c_bool,c_bool]
            self.lib.calculate_crc.restype = c_ulong
			
            self.lib.external_pe_create_PJOFF_object.argtypes = [c_char_p, c_char_p]
            self.lib.external_pe_create_PJOFF_object.restype = c_void_p

            self.lib.external_pe_load_PJOFF_from_string.argtypes = [c_char_p]
            self.lib.external_pe_load_PJOFF_from_string.restype = c_void_p
			
            self.lib.external_pe_load_PJOFF_from_file.argtypes = [c_char_p]
            self.lib.external_pe_load_PJOFF_from_file.restype = c_void_p
			
            self.lib.external_pe_save_PJOFF_to_string.argtypes = [c_void_p]
            self.lib.external_pe_save_PJOFF_to_string.restype = c_char_p
			
            self.lib.external_pe_save_PJOFF_to_file.argtypes = [c_void_p, c_char_p]
            self.lib.external_pe_save_PJOFF_to_file.restype =  c_long
			
            self.lib.external_pe_generate_PJOFF_unique_id_from_filetype.argtypes = [c_char_p]
            self.lib.external_pe_generate_PJOFF_unique_id_from_filetype.restype = c_char_p
			
            self.lib.external_pe_json_free_json_file.argtypes = [c_void_p]
			
            self.lib.external_pe_json_get_hexadecimal.argtypes = [c_void_p, c_char_p, c_char_p, c_ulong]
            self.lib.external_pe_json_get_hexadecimal.restype = c_ulong
			
            self.lib.external_pe_json_get_string.argtypes = [c_void_p, c_char_p, c_char_p, c_char_p]
            self.lib.external_pe_json_get_string.restype = c_char_p
			
            self.lib.external_pe_json_get_integer.argtypes = [c_void_p,c_char_p,c_char_p,c_ulong]
            self.lib.external_pe_json_get_integer.restype = c_ulong 
			
            self.lib.external_pe_json_put_string.argtypes = [c_void_p, c_char_p, c_char_p, c_char_p]
			
            self.lib.external_pe_json_put_integer.argtypes = [c_void_p, c_char_p, c_char_p, c_ulong]
			
            self.lib.external_validate_PJOFF_object.argtypes = [c_void_p]
            self.lib.external_validate_PJOFF_object.restype = c_bool
            
            self.lib.pe_import_pki_tree.argtypes = [c_void_p, c_char_p, c_char_p]
            self.lib.pe_import_pki_tree.restype = c_bool
            
            self.lib.pe_export_pki_tree.argtypes = [c_void_p, c_char_p, c_char_p, c_char_p, c_bool]
            self.lib.pe_export_pki_tree.restype = c_bool

            self.lib.external_pe_json_delete.argtypes = [c_void_p, c_char_p, c_char_p]
            self.lib.external_pe_json_delete.restype = c_bool

            self.lib.external_pe_json_get_rawbuffer.argtypes = [c_void_p, c_char_p, c_char_p, POINTER(c_ulong)]
            self.lib.external_pe_json_get_rawbuffer.restype = POINTER(c_ubyte)

            self.lib.external_pe_json_put_rawbuffer.argtypes = [c_void_p, c_char_p, c_char_p, c_void_p]
			
            self.lib.external_pe_json_get_subsection_descriptions_from_list.argtypes = [c_void_p, c_char_p]
            self.lib.external_pe_json_get_subsection_descriptions_from_list.restype = c_char_p
		
            self.lib.external_pe_json_get_subsection_name_from_description.argtypes = [c_void_p, c_char_p, c_char_p]
            self.lib.external_pe_json_get_subsection_name_from_description.restype = c_char_p
			
            self.lib.external_pe_json_add_subsection_description_to_list.argtypes = [c_void_p, c_char_p, c_char_p]

            self.lib.external_pe_json_remove_subsection_from_list.argtypes = [c_void_p, c_char_p, c_char_p]

            self.lib.external_object_storage_create_storage_object.argtypes = []
            self.lib.external_object_storage_create_storage_object.restype = c_void_p

            self.lib.external_object_storage_load_data_from_object_file.argtypes = [c_void_p, c_char_p]
            self.lib.external_object_storage_load_data_from_object_file.restype = c_bool

            self.lib.external_object_storage_put_block.argtypes = [c_void_p,c_ulong,c_ulong,POINTER(c_ubyte)]
            self.lib.external_object_storage_put_block.restype = c_bool

            self.lib.external_object_storage_get_block.argtypes = [c_void_p,c_ulong,c_ulong,c_ubyte]
            self.lib.external_object_storage_get_block.restype = POINTER(c_ubyte)

            self.lib.external_object_storage_get_number_of_ranges.argtypes = [c_void_p]
            self.lib.external_object_storage_get_number_of_ranges.restype = c_long

            self.lib.external_object_storage_get_range.argtypes = [c_void_p,c_ulong,POINTER(c_ulong),POINTER(c_ulong)]
            self.lib.external_object_storage_get_range.restype = c_bool

            self.lib.external_object_storage_get_entire_range_including_gaps.argtypes = [c_void_p,POINTER(c_ulong),POINTER(c_ulong)]
            self.lib.external_object_storage_get_entire_range_including_gaps.restype = c_bool

            self.lib.external_object_storage_delete_block.argtypes = [c_void_p,c_ulong,c_ulong]
            self.lib.external_object_storage_delete_block.restype = c_bool

            self.lib.external_object_storage_crop_storage.argtypes = [c_void_p,c_ulong,c_ulong]
            self.lib.external_object_storage_crop_storage.restype = c_bool

            self.lib.external_object_storage_get_start_address.argtypes = [c_void_p,POINTER(c_ulong)]
            self.lib.external_object_storage_get_start_address.restype = c_bool

            self.lib.external_object_storage_set_start_address.argtypes = [c_void_p,c_ulong]
            self.lib.external_object_storage_set_start_address.restype = c_bool

            self.lib.external_object_storage_relocate.argtypes = [c_void_p,c_ulong]
            self.lib.external_object_storage_relocate.restype = c_bool

            self.lib.external_object_storage_save_object_to_srec_file.argtypes = [c_void_p, c_char_p]
            self.lib.external_object_storage_save_object_to_srec_file.restype = c_bool

            self.lib.external_object_storage_destroy_storage_object.argtypes = [c_void_p]
            self.lib.external_object_storage_destroy_storage_object.restype = c_bool

            self.lib.external_list_passwords.argtypes = [c_void_p, c_ulong]
            self.lib.external_list_passwords.restype = c_char_p

            self.lib.external_create_random_password.argtypes = [c_void_p, c_ulong, c_char_p]
            self.lib.external_create_random_password.restype = c_bool

            self.lib.external_delete_password.argtypes = [c_void_p, c_char_p]

            self.lib.external_get_password_definition.argtypes = [c_void_p, c_char_p]
            self.lib.external_get_password_definition.restype = c_char_p

            self.lib.external_calculate_imx_64bit_passcode_based_on_key_and_challenge.argtypes = [c_void_p, c_ulong, POINTER(c_ubyte),POINTER(c_ulong)]
            self.lib.external_calculate_imx_64bit_passcode_based_on_key_and_challenge.restype = POINTER(c_ubyte)


# New library functions should be added with this syntax:
#            self.lib.xxx.argtypes = []
#            self.lib.xxx.restype = 

            self.lib.pe_special_features(pe_set_default_application_files_directory,True,0,0,0,c_char_p(self.libraryPath.encode('utf-8')),0)
			
    def versionType(self):
        if not self.libraryLoaded:
            return ''
        return self.lib.version().decode()
		
    def getDllVersion(self):
        if not self.libraryLoaded:
            return 0
        return self.lib.get_dll_version()

    def openDebugFile(self,fileName):
        self.lib.open_debug_file(fileName.encode("utf-8"))

    def closeDebugFile(self):
        self.lib.close_debug_file()
		
    def setLocalIpMachineNumber(self,ipNumber):
        self.lib.set_local_machine_ip_number(ipNumber.encode("utf-8"))

    def reenumerateAllPortTypes(self):
        self.lib.reenumerate_all_port_types()
	
    def getEnumeratedNumberOfPorts(self,portType):
        if not self.libraryLoaded:
            return 0
        return self.lib.get_enumerated_number_of_ports(portType)
	
    def getPortDescriptor(self,portType,portNum):
        if not self.libraryLoaded:
            return ''
        return self.lib.get_port_descriptor(portType,portNum).decode()
		
    def getPortDescriptorShort(self,portType,portNum):
        if not self.libraryLoaded:
            return ''
        return self.lib.get_port_descriptor_short(portType,portNum).decode()
		
    def resetHardwareInterface(self):
        self.lib.reset_hardware_interface()
		
    def setDebugShiftFrequency(self, shiftSpeedInHz):
        self.lib.set_debug_shift_frequency(shiftSpeedInHz)
	
    def setResetDelayInMs(self, delayLength):
        self.lib.set_reset_delay_in_ms(delayLength)
		
    def checkCriticalError(self):
        if not self.libraryLoaded:
            return 0
        return self.lib.check_critical_error()
		
    def getCableVersion(self):
        if not self.libraryLoaded:
            return 0
        return self.lib.get_cable_version()		

    def targetReset(self):
        if not self.libraryLoaded:
            return False
        return self.lib.target_reset()
		
    def targetCheckIfHalted(self):
        if not self.libraryLoaded:
            return False
        return self.lib.target_check_if_halted()

    def targetResume(self):
        if not self.libraryLoaded:
            return False
        return self.lib.target_resume()

    def targetHalt(self):
        if not self.libraryLoaded:
            return False
        return self.lib.target_halt()

    def targetStep(self):
        if not self.libraryLoaded:
            return False
        return self.lib.target_step()

    def putBlock(self,memAccessTag,address,accessSizing,buffer):
        if not self.libraryLoaded:
            return False
        return self.lib.put_block(memAccessTag,address,len(buffer),accessSizing,(c_ubyte*len(buffer)).from_buffer(buffer),None)

    def getBlock(self,memAccessTag,address,accessSizing,buffer):
        if not self.libraryLoaded:
            return bytearray()
        returnBool = self.lib.get_block(memAccessTag,address,len(buffer),accessSizing,(c_ubyte*len(buffer)).from_buffer(buffer),None)
        if (not returnBool):
            return bytearray()
        return buffer

    def write8BitValue(self, memAccessTag, address,datum):
        self.lib.write_8bit_value(memAccessTag, address, datum, None)

    def read8BitValue(self, memAccessTag, address):
        if not self.libraryLoaded:
            return 0
        return self.lib.read_8bit_value(memAccessTag,address, None)		

    def write16BitValue(self, memAccessTag, address,datum):
        self.lib.write_16bit_value(memAccessTag, address, datum, None)	

    def read16BitValue(self, memAccessTag, address):
        if not self.libraryLoaded:
            return 0
        return self.lib.read_16bit_value(memAccessTag,address, None)

    def write32BitValue(self, memAccessTag, address,datum):
        self.lib.write_32bit_value(memAccessTag, address, datum, None)

    def read32BitValue(self, memAccessTag, address):
        if not self.libraryLoaded:
            return 0
        return self.lib.read_32bit_value(memAccessTag,address, None)
		
    def write64BitValue(self, memAccessTag, address,datum):
        self.lib.write_64bit_value(memAccessTag, address, datum, None)

    def read64BitValue(self, memAccessTag, address):
        if not self.libraryLoaded:
            return 0
        return self.lib.read_64bit_value(memAccessTag,address, None)
		
    def loadBinaryFile(self, fileName, startAddress):
        if not self.libraryLoaded:
            return False
        return self.lib.load_bin_file(fileName.encode("utf-8"), startAddress)
		
    def loadSrecFile(self, fileName, offset):
        if not self.libraryLoaded:
            return False
        return self.lib.load_srec_file(fileName.encode("utf-8"), offset)

    def getMcuRegister(self, regAccessTag, numReg):
        if not self.libraryLoaded:
            return 0
        returnLength = c_ulong()
        if (not self.lib.get_mcu_register(regAccessTag, numReg, byref(returnLength))):
            return 0
        return (returnLength.value)
		
    def setMcuRegister(self,regAccessTag,numReg,regValue):
        if not self.libraryLoaded:
            return False
        return self.lib.set_mcu_register(regAccessTag,numReg,regValue)
		
    def peSpecialFeatures(self,featureNum,setFeature,paramValue1,paramValue2,paramValue3, paramRef1,paramRef2):
        if not self.libraryLoaded:
            return False
        return self.lib.pe_special_features(featureNum,setFeature,paramValue1,paramValue2,paramValue3,paramRef1,paramRef2)
			
    def close(self):
        if not self.connectedToDebugCable:
            return
        # Close any open connections to hardware
        self.peSpecialFeatures(pe_arm_flush_any_queued_data,True,0,0,0,0,0)
        self.lib.close_port()
        self.connectedToDebugCable = False
        return

    def __del__(self):
        self.close()
        return

    def listPortsDescription(self):
        if not self.libraryLoaded:
            return
        numports = self.lib.get_enumerated_number_of_ports(PortType_Autodetect)
        if numports == 0:
            print('No hardware detected locally.')
        for ii in range(numports):
            print(self.lib.get_port_descriptor_short(PortType_Autodetect,ii+1).decode() + " : " + self.lib.get_port_descriptor(PortType_Autodetect,ii+1).decode())
        return
    def listPortsName(self):
        if not self.libraryLoaded:
            return
        numports = self.lib.get_enumerated_number_of_ports(PortType_Autodetect)
        if numports == 0:
            print('No hardware detected locally.')
        for ii in range(numports):
            print(self.lib.get_port_descriptor_short(PortType_Autodetect,ii+1).decode().encode("utf-8"))
        return

    def connectToDebugCable(self,debugHardwareNameIpOrSerialnum=None):
        if not self.libraryLoaded:
            return False
            
        self.connectedToDebugCable = False
        if debugHardwareNameIpOrSerialnum==None:
            # USB1 is a generic identifier which will select the first autodetected USB pemicro device
            portName = c_char_p('USB1'.encode('utf-8'))
        else:
            # This identifier can be the debug hardware's IP address, assigned name, serial number, or generic identifier (USB1, ETHERNET1)
            portName = c_char_p(debugHardwareNameIpOrSerialnum.encode('utf-8'))
        if not self.lib.open_port_by_identifier(portName):
            return False
        self.connectedToDebugCable = True
        return True

    def establishCommunicationsWithTarget(self, swdMode=True, shiftSpeed=1000000):
        # connectToDebugCable must be used first to connect to the debug hardware
        if not self.libraryLoaded:
            return False
        if not self.connectedToDebugCable:
            return False
        if swdMode:
            self.peSpecialFeatures(pe_arm_set_communications_mode,True,pe_arm_set_debug_comm_swd,0,0,0,0)
        else:       
            self.peSpecialFeatures(pe_arm_set_communications_mode,True,pe_arm_set_debug_comm_jtag,0,0,0,0)
        # Set 1Mhz Shift Rate
        self.lib.set_debug_shift_frequency(shiftSpeed)
        # Communicate to the target, power up debug module, check  (powering it up). Looks for arm IDCODE to verify connection.
        return self.peSpecialFeatures(pe_arm_enable_debug_module,True,0,0,0,0,0)

    def writeApRegister(self, apselect, addr, value, now=False):
        if not self.connectedToDebugCable:
            return
        self.peSpecialFeatures(pe_arm_write_ap_register, now, apselect,addr,value,0,0)
        return

    def readApRegister(self, apselect, addr, now=True, requiresDelay=False):
        if not self.connectedToDebugCable:
            return 0
        retVal = c_ulong()
        self.peSpecialFeatures(pe_arm_read_ap_register,True,apselect,addr,0,byref(retVal),0)
        return retVal.value
    
    def writeDpRegister(self, addr, value, now=False):
        if not self.connectedToDebugCable:
            return
        self.peSpecialFeatures(pe_arm_write_dp_register, now, addr,value,0,0,0)
        return

    def readDpRegister(self, addr, now=True, requiresDelay=False):
        if not self.connectedToDebugCable:
            return 0
        retVal = c_ulong()
        self.peSpecialFeatures(pe_arm_read_dp_register,True,addr,0,0,byref(retVal),0)
        return retVal.value
    
    def lastSwdStatus(self):
        if not self.libraryLoaded:
            return 0
        retVal = c_ulong()
        self.peSpecialFeatures(pe_arm_get_last_swd_status,True,0,0,0,byref(retVal),0)
        return retVal.value

# *******************************************************************************************************************
# CRC/CRYPTO FUNCTIONS *******************************************************************************
# *******************************************************************************************************************

    def aesEncryptGeneric(self,plainTextArray,aesKey,aesIv,aesCipherType):
        if not self.libraryLoaded:
            return bytearray()
        returnLength = c_ulong()
        returnPtr = self.lib.aes_encrypt_generic_raw((c_ubyte*len(plainTextArray)).from_buffer(plainTextArray),len(plainTextArray),
                                                     (c_ubyte*len(aesKey)).from_buffer(aesKey),len(aesKey),
                                                     (c_ubyte*len(aesIv)).from_buffer(aesIv),len(aesIv),
                                                     aesCipherType,byref(returnLength))
        if (not returnPtr):
            return bytearray()        
        return bytearray((ctypes.c_ubyte * returnLength.value).from_address(addressof(returnPtr.contents)))

    def calcCrc32(self,plainTextArray,startingCrcValue,xorOutValue):
        if not self.libraryLoaded:
            return 0
        return self.lib.calculate_crc32((c_ubyte*len(plainTextArray)).from_buffer(plainTextArray),len(plainTextArray),startingCrcValue,xorOutValue)

    def calcCrc(self,plainTextArray,startingCrcValue,xorOutValue,crcOrder,crcPolynomial,crcReflectIn,crcReflectOut):
        if not self.libraryLoaded:
            return 0
        return self.lib.calculate_crc((c_ubyte*len(plainTextArray)).from_buffer(plainTextArray),len(plainTextArray),startingCrcValue,xorOutValue,crcOrder,crcPolynomial,crcReflectIn,crcReflectOut)

    def aesDecryptGeneric(self,cipherTextArray,aesKey,aesIv,aesCipherType):
        if not self.libraryLoaded:
            return bytearray()
        returnLength = c_ulong()
        returnPtr = self.lib.aes_encrypt_generic_raw((c_ubyte*len(cipherTextArray)).from_buffer(cipherTextArray),len(cipherTextArray),
                                                     (c_ubyte*len(aesKey)).from_buffer(aesKey),len(aesKey),
                                                     (c_ubyte*len(aesIv)).from_buffer(aesIv),len(aesIv),
                                                     aesCipherType,byref(returnLength))
        if (not returnPtr):
            return bytearray()        
        return bytearray((ctypes.c_ubyte * returnLength.value).from_address(addressof(returnPtr.contents)))

    def aes128GenerateRandomKeyOrIv(self):
        if not self.libraryLoaded:
            return bytearray()
        returnPtr = self.lib.aes128_generate_random_key_or_iv()
        if (not returnPtr):
            return bytearray()        
        return bytearray((ctypes.c_ubyte * 16).from_address(addressof(returnPtr.contents)))
		
# *******************************************************************************************************************
# PJOFF FUNCTIONS *******************************************************************************
# *******************************************************************************************************************

    def peCreatePjoffObject(self,filetype,objectName):
        if not self.libraryLoaded:
            return None
        return self.lib.external_pe_create_PJOFF_object(filetype.encode('utf-8'),objectName.encode('utf-8'))		

    def peLoadPjoffFromString(self,pjoffString):
        if not self.libraryLoaded:
            return None
        return self.lib.external_pe_load_PJOFF_from_string(pjoffString.encode('utf-8'))
		
    def peLoadPjoffFromFile(self,pjoffFile):
        if not self.libraryLoaded:
            return None
        return self.lib.external_pe_load_PJOFF_from_file(pjoffFile.encode('utf-8'))
		
    def peSavePjoffToString(self,pjoffObject):
        if not self.libraryLoaded:
            return None
        return self.lib.external_pe_save_PJOFF_to_string(pjoffObject).decode()	

    def peSavePjoffToFile(self,pjoffObject,pjoffFilename):
        if not self.libraryLoaded:
            return None
        return self.lib.external_pe_save_PJOFF_to_file(pjoffObject,pjoffFilename.encode('utf-8'))		

    def peGeneratePjoffUniqueIdFromFiletype(self,filetype):
        if not self.libraryLoaded:
            return None
        return self.lib.external_pe_generate_PJOFF_unique_id_from_filetype(filetype.encode('utf-8'))

    def peJsonFreeJsonFile(self,jsonFile):
        if not self.libraryLoaded:
            return 
        self.lib.external_pe_json_free_json_file(jsonFile)	

    def peJsonGetHexadecimal(self,jsonFile,jsonSection,jsonKey,jsonDefaultValue):
        if not self.libraryLoaded:
            return 0
        return self.lib.external_pe_json_get_hexadecimal(jsonFile,jsonSection.encode('utf-8'),jsonKey.encode('utf-8'),jsonDefaultValue)		

    def peJsonGetInteger(self,jsonFile,jsonSection,jsonKey,jsonDefaultValue):
        if not self.libraryLoaded:
            return 0
        return self.lib.external_pe_json_get_integer(jsonFile,jsonSection.encode('utf-8'),jsonKey.encode('utf-8'),jsonDefaultValue)
		
    def peJsonGetString(self,jsonFile,jsonSection,jsonKey,jsonDefaultValue):
        if not self.libraryLoaded:
            return None
        return self.lib.external_pe_json_get_string(jsonFile,jsonSection.encode('utf-8'),jsonKey.encode('utf-8'),jsonDefaultValue.encode('utf-8')).decode()

    def peJsonGetByteArray(self,jsonFile,jsonSection,jsonKey):
        if not self.libraryLoaded:
            return bytearray()
        returnLength = c_ulong()
        returnPtr = self.lib.external_pe_json_get_rawbuffer(jsonFile,jsonSection.encode('utf-8'),jsonKey.encode('utf-8'),byref(returnLength))
        if (not returnPtr):
            return bytearray()        
        return bytearray((ctypes.c_ubyte * returnLength.value).from_address(addressof(returnPtr.contents)))

    def peJsonPutByteArray(self,jsonFile,jsonSection,jsonKey,outputbytearray):
        if not self.libraryLoaded:
            return
        self.lib.external_pe_json_put_rawbuffer(jsonFile,jsonSection.encode('utf-8'),jsonKey.encode('utf-8'),(c_ubyte*len(outputbytearray)).from_buffer(outputbytearray),len(outputbytearray))
        return 

    def peJsonPutString(self,jsonFile,jsonSection,jsonKey,jsonValue):
        if not self.libraryLoaded:
            return
        return self.lib.external_pe_json_put_string(jsonFile,jsonSection.encode('utf-8'),jsonKey.encode('utf-8'),jsonValue.encode('utf-8'))

    def peJsonDelete(self,jsonFile,jsonSection,jsonKey):
        if not self.libraryLoaded:
            return False
        return self.lib.external_pe_json_delete(jsonFile,jsonSection.encode('utf-8'),jsonKey.encode('utf-8'))

    def peJsonPutInteger(self,jsonFile,jsonSection,jsonKey,jsonValue):
        if not self.libraryLoaded:
            return
        return self.lib.external_pe_json_put_integer(jsonFile,jsonSection.encode('utf-8'),jsonKey.encode('utf-8'),jsonValue)

    def validatePjoffObject(self,pjoffObject):
        if not self.libraryLoaded:
            return False
        return self.lib.external_validate_PJOFF_object(pjoffObject)	
		
    def peJsonGetSubsectionDescriptionsFromList(self,pjoffObject,jsonSection):
        if not self.libraryLoaded:
            return []
        myList = list(self.lib.external_pe_json_get_subsection_descriptions_from_list(pjoffObject,jsonSection.encode('utf-8')).decode().split('\x08'))
        if (myList == ['']):
            return []
        return myList
		
    def peJsonGetSubsectionNameFromDescription(self,pjoffObject,jsonSection,jsonSubsectionDescription):
        if not self.libraryLoaded:
            return None
        return self.lib.external_pe_json_get_subsection_name_from_description(pjoffObject,jsonSection.encode('utf-8'),jsonSubsectionDescription.encode('utf-8')).decode()

    def peJsonAddSubsectionDescriptionToList(self,pjoffObject,jsonSection,jsonSubsectionDescription):
        if not self.libraryLoaded:
           return False
        return self.lib.external_pe_json_add_subsection_description_to_list(pjoffObject,jsonSection.encode('utf-8'),jsonSubsectionDescription.encode('utf-8'))

    def peJsonRemoveSubsectionFromList(self,pjoffObject,jsonSection,jsonSubsectionDescription):
        if not self.libraryLoaded:
           return False
        return self.lib.external_pe_json_remove_subsection_from_list(pjoffObject,jsonSection.encode('utf-8'),jsonSubsectionDescription.encode('utf-8'))


# *******************************************************************************************************************
# OBJECT FILE FUNCTIONS (S-record, Elf/Dwarf) ***********************************************************************
# *******************************************************************************************************************

    def peObjectLoaderCreateStorageObject(self):
        if not self.libraryLoaded:
            return None
        return self.lib.external_object_storage_create_storage_object()        

    def peObjectLoaderLoadDataFromObjectFile(self,storageObject, objectFilename):
        if not self.libraryLoaded:
            return False
        return self.lib.external_object_storage_load_data_from_object_file(storageObject, objectFilename.encode('utf-8'))

    def peObjectLoaderGetMemoryRanges(self,storageObject):
        if not self.libraryLoaded:
            return [(0,0)]
        numRanges = self.lib.external_object_storage_get_number_of_ranges(storageObject)
        myList = []
        if (numRanges>0):
            for rangeLoop in range(numRanges):
                rangeAddress = c_ulong()
                rangeLength = c_ulong()
                self.lib.external_object_storage_get_range(storageObject, rangeLoop+1, byref(rangeAddress), byref(rangeLength))
                myList.append(tuple((rangeAddress.value,rangeLength.value)))
            return myList
        return [(0,0)]

    def peObjectLoaderPutBlock(self,storageObject,memoryAddress,dataByteArray):
        if not self.libraryLoaded:
            return False
        return self.lib.external_object_storage_put_block(storageObject, memoryAddress, len(dataByteArray),(c_ubyte*len(dataByteArray)).from_buffer(dataByteArray))

    def peObjectLoaderGetBlock(self,storageObject,memoryAddress,memoryLength,fillValue=0xff):
        if not self.libraryLoaded:
            return bytearray()
        returnPtr = self.lib.external_object_storage_get_block(storageObject, memoryAddress, memoryLength, fillValue);
        if (not returnPtr):
            return bytearray()        
        return bytearray((ctypes.c_ubyte * memoryLength).from_address(addressof(returnPtr.contents)))

    def peObjectLoaderDeleteBlock(self,storageObject,memoryAddress,memoryLength):
        if not self.libraryLoaded:
            return bytearray()
        return self.lib.external_object_storage_delete_block(storageObject, memoryAddress, memoryLength)

    def peObjectLoaderCropToBlock(self,storageObject,memoryAddress,memoryLength):
        if not self.libraryLoaded:
            return bytearray()
        return self.lib.external_object_storage_crop_storage(storageObject, memoryAddress, memoryLength)

    def peObjectLoaderGetStartRecordAddress(self,storageObject):
        if not self.libraryLoaded:
            return 0
        startAddress = c_ulong()
        returnBool = self.lib.external_object_storage_get_start_address(storageObject, byref(startAddress))
        if not returnBool:
            return 0
        return startAddress.value

    def peObjectLoaderSetStartRecordAddress(self,storageObject,startAddress):
        if not self.libraryLoaded:
            return False
        return self.lib.external_object_storage_set_start_address(storageObject, startAddress)

    def peObjectLoaderRelocate(self,storageObject,startAddress):
        # Relocates first address found to new address and offsets all other data appropriately
        if not self.libraryLoaded:
            return False
        return self.lib.external_object_storage_relocate(storageObject, startAddress)

    def peObjectLoaderSaveDataToObjectFile(self,storageObject, objectFilename):
        if not self.libraryLoaded:
            return False
        return self.lib.external_object_storage_save_object_to_srec_file(storageObject, objectFilename.encode('utf-8'))


    def peObjectLoaderGetEntireRangeIncludingGaps(self,storageObject):
        if not self.libraryLoaded:
            return (0,0)
        numRanges = self.lib.external_object_storage_get_number_of_ranges(storageObject)
        rangeAddress0 = c_ulong()
        rangeLength0 = c_ulong()
        rangeAddressMax = c_ulong()
        rangeLengthMax = c_ulong()
        self.lib.external_object_storage_get_range(storageObject, 1, byref(rangeAddress0), byref(rangeLength0))
        self.lib.external_object_storage_get_range(storageObject, numRanges, byref(rangeAddressMax), byref(rangeLengthMax))
        return tuple((rangeAddress0.value,rangeAddressMax.value+rangeLengthMax.value-rangeAddress0.value))

    def peObjectLoaderFreeStorageObject(self,storageObject):
        if not self.libraryLoaded:
            return False
        return self.lib.external_object_storage_destroy_storage_object(storageObject)

 
# *******************************************************************************************************************
# AES KEY GENERATION / LIST FUNCTIONS *******************************************************************************
# *******************************************************************************************************************
    def createRandomAesKey(self,pjoffObject,keyNameDescription, ivBytes=None, keyBytes=None):
        if not self.libraryLoaded:
            return False
        self.peJsonAddSubsectionDescriptionToList(pjoffObject,'aeskeys',keyNameDescription)
        sectionName = self.peJsonGetSubsectionNameFromDescription(pjoffObject,'aeskeys',keyNameDescription)

        if ivBytes == None:
            self.peJsonPutByteArray(pjoffObject,sectionName,'iv',self.aes128GenerateRandomKeyOrIv())
        else:
            self.peJsonPutByteArray(pjoffObject,sectionName,'iv',ivBytes)

        if keyBytes == None:
            self.peJsonPutByteArray(pjoffObject,sectionName,'key',self.aes128GenerateRandomKeyOrIv())
        else:
            self.peJsonPutByteArray(pjoffObject,sectionName,'key',keyBytes)

        return True

    def deleteRandomAesKey(self,pjoffObject,keyNameDescription):
        if not self.libraryLoaded:
            return
        self.peJsonRemoveSubsectionFromList(pjoffObject,'aeskeys',keyNameDescription)
        return

    def getAesKey(self,pjoffObject,keyNameDescription, iv,key):
        if not self.libraryLoaded:
            return False
        iv[:] = self.peJsonGetByteArray(pjoffObject,self.peJsonGetSubsectionNameFromDescription(pjoffObject, 'aeskeys', keyNameDescription), 'iv')
        key[:] = self.peJsonGetByteArray(pjoffObject,self.peJsonGetSubsectionNameFromDescription(pjoffObject, 'aeskeys', keyNameDescription), 'key')
        if (iv != []) and (key != []):
            return True
        else:
            return False

    def listAesKeys(self,pjoffObject):
        if not self.libraryLoaded:
            return []
        return self.peJsonGetSubsectionDescriptionsFromList(pjoffObject,'aeskeys')

# *******************************************************************************************************************
# RANDOM PASSWORD GENERATION / LIST FUNCTIONS ****************************************************************
# *******************************************************************************************************************

    def listPasswords(self,keyfilePjoffObject, passwordTypeMask):
        if not self.libraryLoaded:
            return []
        myList = list(self.lib.external_list_passwords(keyfilePjoffObject,passwordTypeMask).decode().split('\x08'))
        if (myList == ['']):
            return []
        return myList

    def createRandomPassword(self,keyfilePjoffObject,passwordType,passwordDescription):
        if not self.libraryLoaded:
            return False
        return self.lib.external_create_random_password(keyfilePjoffObject, passwordType, passwordDescription.encode('utf-8'))
        
    def deletePassword(self,keyfilePjoffObject,passwordDescription):
        if not self.libraryLoaded:
            return
        self.lib.external_delete_password(keyfilePjoffObject, passwordDescription.encode('utf-8'))
        return

    def getPasswordDefinition(self,passwordPjoffObject,passwordDescription):
        # When given a keyfile and password name, returns a string which is a PJOFF Structure defining this password. This
        # return string can be stored to a file or loaded and used by the calculateImx64bitPasswordResponseFromKeyAndChallenge() routine
        if not self.libraryLoaded:
            return ''
        return self.lib.external_get_password_definition(passwordPjoffObject,passwordDescription.encode('utf-8')).decode()

    def calculateImx64bitPasswordResponseFromKeyAndChallenge(self,passwordPjoffObject, challengeByteArray):
        # When passed a loaded PJOFF Password object (Not a Keyfile PJOFF object), calculates answer to challenge
        if not self.libraryLoaded:
            return bytearray()
        returnLength = c_ulong()
        returnPtr = self.lib.external_calculate_imx_64bit_passcode_based_on_key_and_challenge(passwordPjoffObject,len(challengeByteArray),(c_ubyte*len(challengeByteArray)).from_buffer(challengeByteArray),byref(returnLength))
        if (not returnPtr):
            return bytearray()        
        return bytearray((ctypes.c_ubyte * returnLength.value).from_address(addressof(returnPtr.contents)))

        
# *******************************************************************************************************************
# SIGNING KEY IMPORT / LIST FUNCTIONS *******************************************************************************
# *******************************************************************************************************************

    def importPkiTree (self, pjoffObject, keyFolder, crtFolder):
        if not self.libraryLoaded:
            return False
        return self.lib.pe_import_pki_tree(pjoffObject,keyFolder.encode('utf-8'),crtFolder.encode('utf-8'))

    def deleteImageSigningConfiguration(self,pjoffObject,keyNameDescription):
        if not self.libraryLoaded:
            return
        self.peJsonRemoveSubsectionFromList(pjoffObject,'imagesigningconfigurations',keyNameDescription)
        return

    def listImageSigningConfigurations(self,pjoffObject):
        if not self.libraryLoaded:
            return []
        return self.peJsonGetSubsectionDescriptionsFromList(pjoffObject,'imagesigningconfigurations')

    def exportPkiConfiguration (self, pjoffObject, keyFolder, crtFolder, imageSigningConfigurationDescription, eraseFolderContentsFirst):
        # even if eraseFolderContentsFirst is False, existing files of the same name as exports will be overwritten properly (deleted first)
        if not self.libraryLoaded:
            return False
        return self.lib.pe_export_pki_tree(pjoffObject,keyFolder.encode('utf-8'),crtFolder.encode('utf-8'),imageSigningConfigurationDescription.encode('utf-8'), eraseFolderContentsFirst)

    def getImageSigningConfigurationFilenames(self,pjoffObject,imageSigningConfigurationDescription):
        imageKey = ''
        imageCertificate = ''
        csfKey = ''
        csfCertificate = ''
        if not self.libraryLoaded:
            return imageKey, imageCertificate, csrKey, csrCertificate

        imageKey = self.peJsonGetString(pjoffObject,self.peJsonGetSubsectionNameFromDescription(pjoffObject, 'imagesigningconfigurations', imageSigningConfigurationDescription), 'imgkey','')
        imageCertificate = self.peJsonGetString(pjoffObject,self.peJsonGetSubsectionNameFromDescription(pjoffObject, 'imagesigningconfigurations', imageSigningConfigurationDescription), 'imgcrt','')
        csfKey = self.peJsonGetString(pjoffObject,self.peJsonGetSubsectionNameFromDescription(pjoffObject, 'imagesigningconfigurations', imageSigningConfigurationDescription), 'csfkey','')
        csfCertificate = self.peJsonGetString(pjoffObject,self.peJsonGetSubsectionNameFromDescription(pjoffObject, 'imagesigningconfigurations', imageSigningConfigurationDescription), 'csfcrt','')

        return imageKey, imageCertificate, csfKey, csfCertificate

# *******************************************************************************************************************
# TEST FUNCTIONS *********************************************************************************************
# *******************************************************************************************************************
    def checkKeyandIvValues(self, keyOrIv1, keyOrIv2):
        if (keyOrIv1 != keyOrIv2) and (len(keyOrIv1) == 16) and (len(keyOrIv2) == 16):
            return True
        else:
            return False
			
    def checkPasswordValues(self, password1, password2):
        if (password1 != password2) and (len(password1) == 8) and (len(password2) == 8):
            return True
        else:
            return False		


    #Legacy - This call will be removed
    def createRandom64BitPassword(self,pjoffObject,keyNameDescription):
        return self.createRandomPassword(pjoffObject,PASSWORD_TYPE_IMXRT_RAW_64BIT,keyNameDescription)
        
    #Legacy - This call will be removed
    def deleteRandom64BitPassword(self,pjoffObject,keyNameDescription):
        self.deletePassword(pjoffObject,keyNameDescription)
        return

    #Legacy - This call will be removed
    def getRandom64BitPassword(self,pjoffObject,keyNameDescription, password):
        if not self.libraryLoaded:
            return False
        pjoffpassword = self.peLoadPjoffFromString(self.getPasswordDefinition(pjoffObject,keyNameDescription))
        password[:] = self.peJsonGetByteArray(pjoffpassword,'mcupasswords','rawpassword')
        self.peObjectLoaderFreeStorageObject(pjoffpassword)
        if password != []:
            return True
        else:
            return False
        
    #Legacy - This call will be removed
    def listRandom64BitPasswords(self,pjoffObject):
        if not self.libraryLoaded:
            return []
        return self.listPasswords(pjoffObject,PASSWORD_TYPE_IMXRT_RAW_64BIT)

    def displayCoreRegisters(self):
        print('Displaying Core Registers')
        for ii in range(0,16):
            i = hex(self.getMcuRegister(0,ii))
            print('r' + str(ii) + '=' + str(i))
