#!/usr/bin/env python3
#===============================================================================
#
# MBN TOOLS
#
# GENERAL DESCRIPTION
#    Contains all MBN Utilities for image generation
#
# SPDX-License-Identifier: BSD-3-Clause

#-------------------------------------------------------------------------------
#                      EDIT HISTORY FOR FILE
#
#  This section contains comments describing changes made to the module.
#  Notice that changes are listed in reverse chronological order.
#
# when       who     what, where, why
# --------   ---     ---------------------------------------------------------
# 05/21/18   rissha  Added support for extended MBNV6 and Add support for hashing elf segments with SHA384
# 03/22/18   thiru   Added support for extended MBNV5.
# 06/06/13   yliong  CR 497042: Signed and encrypted image is corrupted. MRC features.
# 03/18/13   dhaval  Add support for hashing elf segments with SHA256 and
#                    sync up to mpss, adsp mbn-tools
# 01/14/13   kedara  Remove dependency on .builds, cust<bid>.h, targ<bid>.h files
# 08/30/12   kedara  Add virtual block suppport
# 02/24/12   dh      Add ssd side effect file names
# 07/08/11   aus     Added support for image_id in SBL image header as required by PBL
#                    Sahara mode
# 10/20/11   dxiang  Clean up
#===============================================================================

import stat
import csv
import itertools
import struct
import os
import shutil
import hashlib

#----------------------------------------------------------------------------
# GLOBAL VARIABLES BEGIN
#----------------------------------------------------------------------------
PAD_BYTE_1                  = 255           # Padding byte 1s
PAD_BYTE_0                  = 0             # Padding byte 0s
SHA256_SIGNATURE_SIZE       = 256           # Support SHA256
MAX_NUM_ROOT_CERTS          = 4             # Maximum number of OEM root certificates
MI_BOOT_SBL_HDR_SIZE        = 80            # sizeof(sbl_header)
BOOT_HEADER_LENGTH          = 20            # Boot Header Number of Elements
SBL_HEADER_LENGTH           = 20            # SBL Header Number of Elements
MAX_PHDR_COUNT              = 100           # Maximum allowable program headers
CERT_CHAIN_ONEROOT_MAXSIZE  = 6*1024        # Default Cert Chain Max Size for one root
VIRTUAL_BLOCK_SIZE          = 131072        # Virtual block size for MCs insertion in SBL1 if ENABLE_VIRTUAL_BLK ON
MAGIC_COOKIE_LENGTH         = 12            # Length of magic Cookie inserted per VIRTUAL_BLOCK_SIZE
MIN_IMAGE_SIZE_WITH_PAD     = 256*1024      # Minimum image size for sbl1 Nand based OTA feature

SBL_AARCH64                 = 0xF           # Indicate that SBL is a Aarch64 image
SBL_AARCH32                 = 0x0           # Indicate that SBL is a Aarch32 image

# Magic numbers filled in for boot headers
FLASH_CODE_WORD                       = 0x844BDCD1
UNIFIED_BOOT_COOKIE_MAGIC_NUMBER      = 0x33836685
MAGIC_NUM                             = 0x73D71034
AUTODETECT_PAGE_SIZE_MAGIC_NUM        = 0x7D0B435A
AUTODETECT_PAGE_SIZE_MAGIC_NUM64      = 0x7D0B5436
AUTODETECT_PAGE_SIZE_MAGIC_NUM128     = 0x7D0B6577
SBL_VIRTUAL_BLOCK_MAGIC_NUM           = 0xD48B54C6

# ELF Definitions
ELF_HDR_COMMON_SIZE       = 24
ELF32_HDR_SIZE            = 52
ELF32_PHDR_SIZE           = 32
ELF64_HDR_SIZE            = 64
ELF64_PHDR_SIZE           = 56
ELFINFO_MAG0_INDEX        = 0
ELFINFO_MAG1_INDEX        = 1
ELFINFO_MAG2_INDEX        = 2
ELFINFO_MAG3_INDEX        = 3
ELFINFO_MAG0              = 127     # 0x7F
ELFINFO_MAG1              = 69      # E
ELFINFO_MAG2              = 76      # L
ELFINFO_MAG3              = 70      # F
ELFINFO_CLASS_INDEX       = 4
ELFINFO_CLASS_32          = 1
ELFINFO_CLASS_64          = 2
ELFINFO_VERSION_INDEX     = 6
ELFINFO_VERSION_CURRENT   = 1
ELF_BLOCK_ALIGN           = 0x1000
ALIGNVALUE_1MB             = 0x100000
ALIGNVALUE_4MB            = 0x400000
ELFINFO_DATA2LSB          = b'\x01'
ELFINFO_EXEC_ETYPE        = b'\x02\x00'
ELFINFO_ARM_MACHINETYPE   = b'\x28\x00'
ELFINFO_VERSION_EV_CURRENT = b'\x01\x00\x00\x00'
ELFINFO_SHOFF             = 0x00
ELFINFO_PHNUM             = b'\x01\x00'
ELFINFO_RESERVED          = 0x00

# ELF Program Header Types
NULL_TYPE                 = 0x0
LOAD_TYPE                 = 0x1
DYNAMIC_TYPE              = 0x2
INTERP_TYPE               = 0x3
NOTE_TYPE                 = 0x4
SHLIB_TYPE                = 0x5
PHDR_TYPE                 = 0x6
TLS_TYPE                  = 0x7

"""
The eight bits between 20 and 27 in the p_flags field in ELF program headers
is not used by the standard ELF format. We use this byte to hold OS and processor
specific fields as recommended by ARM.

The bits in this byte are defined as follows:

                   Pool Indx    Segment type     Access type    Page/non page
  bits in p_flags /-----27-----/----26-24-------/---- 23-21----/------20-------/

After parsing segment description strings in the SCL file, the appropriate segment
flag values are chosen from the follow definitions. The mask defined below is then
used to update the existing p_flags field in the program headers with the updated
values.
"""
# Mask for bits 20-27 to parse program header p_flags
MI_PBT_FLAGS_MASK = 0x0FF00000

# Helper defines to help parse ELF program headers
MI_PBT_FLAG_SEGMENT_TYPE_MASK         = 0x07000000
MI_PBT_FLAG_SEGMENT_TYPE_SHIFT        = 0x18
MI_PBT_FLAG_PAGE_MODE_MASK            = 0x00100000
MI_PBT_FLAG_PAGE_MODE_SHIFT           = 0x14
MI_PBT_FLAG_ACCESS_TYPE_MASK          = 0x00E00000
MI_PBT_FLAG_ACCESS_TYPE_SHIFT         = 0x15
MI_PBT_FLAG_POOL_INDEX_MASK           = 0x08000000
MI_PBT_FLAG_POOL_INDEX_SHIFT          = 0x1B

# Segment Type
MI_PBT_L4_SEGMENT                     = 0x0
MI_PBT_AMSS_SEGMENT                   = 0x1
MI_PBT_HASH_SEGMENT                   = 0x2
MI_PBT_BOOT_SEGMENT                   = 0x3
MI_PBT_L4BSP_SEGMENT                  = 0x4
MI_PBT_SWAPPED_SEGMENT                = 0x5
MI_PBT_XBL_SEC_SEGMENT                = 0x5
MI_PBT_SWAP_POOL_SEGMENT              = 0x6
MI_PBT_PHDR_SEGMENT                   = 0x7

# Page/Non-Page Type
MI_PBT_NON_PAGED_SEGMENT              = 0x0
MI_PBT_PAGED_SEGMENT                  = 0x1

# Access Type
MI_PBT_RW_SEGMENT                     = 0x0
MI_PBT_RO_SEGMENT                     = 0x1
MI_PBT_ZI_SEGMENT                     = 0x2
MI_PBT_NOTUSED_SEGMENT                = 0x3
MI_PBT_SHARED_SEGMENT                 = 0x4
MI_PBT_RWE_SEGMENT                    = 0x7

# ELF Segment Flag Definitions
MI_PBT_ELF_AMSS_NON_PAGED_RO_SEGMENT  = 0x01200000
MI_PBT_ELF_AMSS_PAGED_RO_SEGMENT  = 0x01300000
MI_PBT_ELF_SWAP_POOL_NON_PAGED_ZI_SEGMENT_INDEX0 = 0x06400000
MI_PBT_ELF_SWAPPED_PAGED_RO_SEGMENT_INDEX0 = 0x05300000
MI_PBT_ELF_SWAP_POOL_NON_PAGED_ZI_SEGMENT_INDEX1 = 0x0E400000
MI_PBT_ELF_SWAPPED_PAGED_RO_SEGMENT_INDEX1 = 0x0D300000
MI_PBT_ELF_AMSS_NON_PAGED_ZI_SEGMENT = 0x01400000
MI_PBT_ELF_AMSS_PAGED_ZI_SEGMENT = 0x01500000
MI_PBT_ELF_AMSS_NON_PAGED_RW_SEGMENT = 0x01000000
MI_PBT_ELF_AMSS_PAGED_RW_SEGMENT = 0x01100000
MI_PBT_ELF_AMSS_NON_PAGED_NOTUSED_SEGMENT = 0x01600000
MI_PBT_ELF_AMSS_PAGED_NOTUSED_SEGMENT = 0x01700000
MI_PBT_ELF_AMSS_NON_PAGED_SHARED_SEGMENT = 0x01800000
MI_PBT_ELF_AMSS_PAGED_SHARED_SEGMENT = 0x01900000
MI_PBT_ELF_HASH_SEGMENT = 0x02200000
MI_PBT_ELF_BOOT_SEGMENT = 0x03200000
MI_PBT_ELF_PHDR_SEGMENT = 0x07000000
MI_PBT_ELF_NON_PAGED_L4BSP_SEGMENT = 0x04000000
MI_PBT_ELF_PAGED_L4BSP_SEGMENT = 0x04100000
MI_PBT_ELF_AMSS_RELOCATABLE_IMAGE = 0x8000000

# New definitions for EOS demap paging requirement
# Bit 20 (0b) Bit 24-26(000): Non Paged = 0x0000_0000
# Bit 20 (1b) Bit 24-26(000): Locked Paged = 0x0010_0000
# Bit 20 (1b) Bit 24-26(001): Unlocked Paged = 0x0110_0000
# Bit 20 (0b) Bit 24-26(011): non secure = 0x0310_0000
MI_PBT_ELF_RESIDENT_SEGMENT = 0x00000000
MI_PBT_ELF_PAGED_LOCKED_SEGMENT = 0x00100000
MI_PBT_ELF_PAGED_UNLOCKED_SEGMENT = 0x01100000
MI_PBT_ELF_UNSECURE_SEGMENT = 0x03100000
#----------------------------------------------------------------------------
# GLOBAL VARIABLES END
#----------------------------------------------------------------------------

#----------------------------------------------------------------------------
# CLASS DEFINITIONS BEGIN
#----------------------------------------------------------------------------
#----------------------------------------------------------------------------
# OS Type ID Class
#----------------------------------------------------------------------------
class OSType:
    BMP_BOOT_OS        = 0
    WM_BOOT_OS         = 1
    ANDROID_BOOT_OS    = 2
    CHROME_BOOT_OS     = 3
    SYMBIAN_BOOT_OS    = 4
    LINUX_BOOT_OS      = 5

#----------------------------------------------------------------------------
# Image Type ID Class - These values must be kept consistent with mibib.h
#----------------------------------------------------------------------------
class ImageType:
   NONE_IMG           = 0
   OEM_SBL_IMG        = 1
   AMSS_IMG           = 2
   QCSBL_IMG          = 3
   HASH_IMG           = 4
   APPSBL_IMG         = 5
   APPS_IMG           = 6
   HOSTDL_IMG         = 7
   DSP1_IMG           = 8
   FSBL_IMG           = 9
   DBL_IMG            = 10
   OSBL_IMG           = 11
   DSP2_IMG           = 12
   EHOSTDL_IMG        = 13
   NANDPRG_IMG        = 14
   NORPRG_IMG         = 15
   RAMFS1_IMG         = 16
   RAMFS2_IMG         = 17
   ADSP_Q5_IMG        = 18
   APPS_KERNEL_IMG    = 19
   BACKUP_RAMFS_IMG   = 20
   SBL1_IMG           = 21
   SBL2_IMG           = 22
   RPM_IMG            = 23
   SBL3_IMG           = 24
   TZ_IMG             = 25
   PSI_IMG            = 32

#----------------------------------------------------------------------------
# Global Image Type Table
# Format of the look-up table:
# KEY -  IMAGE_TYPE string as passed into mbn_builder.py
# VALUE - [Specific ImageType ID enum, Template key string, MBN Type]
#----------------------------------------------------------------------------
image_id_table = {
   'appsbl': [ImageType.APPSBL_IMG, 'APPSBL_IMG', 'bin'],
   'dbl': [ImageType.DBL_IMG, 'DBL_IMG', 'bin'],
   'osbl': [ImageType.OSBL_IMG, 'OSBL_IMG', 'bin'],
   'amss': [ImageType.AMSS_IMG, 'AMSS_IMG', 'elf'],
   'amss_mbn': [ImageType.HASH_IMG, 'HASH_IMG', 'elf'],
   'apps': [ImageType.APPS_IMG, 'APPS_IMG', 'bin'],
   'hostdl': [ImageType.HOSTDL_IMG, 'HOSTDL_IMG', 'bin'],
   'ehostdl': [ImageType.EHOSTDL_IMG, 'EHOSTDL_IMG', 'bin'],
   'emmcbld': [ImageType.EHOSTDL_IMG, 'EMMCBLD_IMG', 'bin'],
   'qdsp6fw': [ImageType.DSP1_IMG, 'DSP1_IMG', 'elf'],
   'qdsp6sw': [ImageType.DSP2_IMG, 'DSP2_IMG', 'elf'],
   'qdsp5': [ImageType.ADSP_Q5_IMG, 'ADSP_Q5_IMG', 'bin'],
   'tz': [ImageType.TZ_IMG, 'TZ_IMG', 'elf'],
   'tz_rumi': [ImageType.TZ_IMG, 'TZ_IMG', 'elf'],
   'tz_virtio': [ImageType.TZ_IMG, 'TZ_IMG', 'elf'],
   'tzbsp_no_xpu': [ImageType.TZ_IMG, 'TZ_IMG', 'elf'],
   'tzbsp_with_test': [ImageType.TZ_IMG, 'TZ_IMG', 'elf'],
   'rpm': [ImageType.RPM_IMG, 'RPM_IMG', 'elf'],
   'sbl1': [ImageType.SBL1_IMG, 'SBL1_IMG', 'bin'],
   'sbl2': [ImageType.SBL2_IMG, 'SBL2_IMG', 'bin'],
   'sbl3': [ImageType.SBL3_IMG, 'SBL3_IMG', 'bin'],
   'efs1': [ImageType.RAMFS1_IMG, 'RAMFS1_IMG', 'bin'],
   'efs2': [ImageType.RAMFS2_IMG, 'RAMFS2_IMG', 'bin'],
   'pmic': [ImageType.PSI_IMG, 'PSI_IMG', 'elf'],
   # DO NOT add any additional image information
}

#----------------------------------------------------------------------------
# Header Class Notes:
# In order to properly read and write the header structures as binary data,
# the Python Struct library is used to align and package up the header objects
# All Struct objects are initialized by a special string with the following
# notation. These structure objects are then used to decode binary data in order
# to fill out the appropriate class in Python, or they are used to package up
# the Python class so that we may write the binary data out.
#----------------------------------------------------------------------------
"""
      Format | C Type         | Python Type | Standard Size
      -----------------------------------------------------
    1) 'X's  | char *         | string      | 'X' bytes
    2) H     | unsigned short | integer     | 2 bytes
    3) I     | unsigned int   | integer     | 4 bytes

"""

#----------------------------------------------------------------------------
# ELF Header Class
#----------------------------------------------------------------------------
class Elf_Ehdr_common:
   # Structure object to align and package the ELF Header
   s = struct.Struct('16sHHI')

   def __init__(self, data):
      unpacked_data       = (Elf_Ehdr_common.s).unpack(data)
      self.unpacked_data  = unpacked_data
      self.e_ident        = unpacked_data[0]
      self.e_type         = unpacked_data[1]
      self.e_machine      = unpacked_data[2]
      self.e_version      = unpacked_data[3]

   def printValues(self):
      print("ATTRIBUTE / VALUE")
      for attr, value in self.__dict__.items():
         print(attr, value)



#----------------------------------------------------------------------------
# ELF Header Class
#----------------------------------------------------------------------------
class Elf32_Ehdr:
   # Structure object to align and package the ELF Header
   s = struct.Struct('16sHHIIIIIHHHHHH')

   def __init__(self, data):
      unpacked_data       = (Elf32_Ehdr.s).unpack(data)
      self.unpacked_data  = unpacked_data
      self.e_ident        = unpacked_data[0]
      self.e_type         = unpacked_data[1]
      self.e_machine      = unpacked_data[2]
      self.e_version      = unpacked_data[3]
      self.e_entry        = unpacked_data[4]
      self.e_phoff        = unpacked_data[5]
      self.e_shoff        = unpacked_data[6]
      self.e_flags        = unpacked_data[7]
      self.e_ehsize       = unpacked_data[8]
      self.e_phentsize    = unpacked_data[9]
      self.e_phnum        = unpacked_data[10]
      self.e_shentsize    = unpacked_data[11]
      self.e_shnum        = unpacked_data[12]
      self.e_shstrndx     = unpacked_data[13]

   def printValues(self):
      print("ATTRIBUTE / VALUE")
      for attr, value in self.__dict__.items():
         print(attr, value)

   def getPackedData(self):
      if type(self.e_ident) == str:
        packvalue = bytes(self.e_ident, 'utf-8')
      else:
        packvalue = self.e_ident
      values = [packvalue,
                self.e_type,
                self.e_machine,
                self.e_version,
                self.e_entry,
                self.e_phoff,
                self.e_shoff,
                self.e_flags,
                self.e_ehsize,
                self.e_phentsize,
                self.e_phnum,
                self.e_shentsize,
                self.e_shnum,
                self.e_shstrndx
               ]

      return (Elf32_Ehdr.s).pack(*values)

#----------------------------------------------------------------------------
# ELF Program Header Class
#----------------------------------------------------------------------------
class Elf32_Phdr:

   # Structure object to align and package the ELF Program Header
   s = struct.Struct('I' * 8)

   def __init__(self, data):
      unpacked_data       = (Elf32_Phdr.s).unpack(data)
      self.unpacked_data  = unpacked_data
      self.p_type         = unpacked_data[0]
      self.p_offset       = unpacked_data[1]
      self.p_vaddr        = unpacked_data[2]
      self.p_paddr        = unpacked_data[3]
      self.p_filesz       = unpacked_data[4]
      self.p_memsz        = unpacked_data[5]
      self.p_flags        = unpacked_data[6]
      self.p_align        = unpacked_data[7]

   def printValues(self):
      print("ATTRIBUTE / VALUE")
      for attr, value in self.__dict__.items():
         print(attr, value)

   def getPackedData(self):
      values = [self.p_type,
                self.p_offset,
                self.p_vaddr,
                self.p_paddr,
                self.p_filesz,
                self.p_memsz,
                self.p_flags,
                self.p_align
               ]

      return (Elf32_Phdr.s).pack(*values)

#----------------------------------------------------------------------------
# ELF Header Class
#----------------------------------------------------------------------------
class Elf64_Ehdr:
   # Structure object to align and package the ELF Header
   s = struct.Struct('16sHHIQQQIHHHHHH')

   def __init__(self, data):
      unpacked_data       = (Elf64_Ehdr.s).unpack(data)
      self.unpacked_data  = unpacked_data
      self.e_ident        = unpacked_data[0]
      self.e_type         = unpacked_data[1]
      self.e_machine      = unpacked_data[2]
      self.e_version      = unpacked_data[3]
      self.e_entry        = unpacked_data[4]
      self.e_phoff        = unpacked_data[5]
      self.e_shoff        = unpacked_data[6]
      self.e_flags        = unpacked_data[7]
      self.e_ehsize       = unpacked_data[8]
      self.e_phentsize    = unpacked_data[9]
      self.e_phnum        = unpacked_data[10]
      self.e_shentsize    = unpacked_data[11]
      self.e_shnum        = unpacked_data[12]
      self.e_shstrndx     = unpacked_data[13]

   def printValues(self):
      print("ATTRIBUTE / VALUE")
      for attr, value in self.__dict__.items():
         print(attr, value)

   def getPackedData(self):
      if type(self.e_ident) == str:
        packvalue = bytes(self.e_ident, 'utf-8')
      else:
        packvalue = self.e_ident
      values = [packvalue,
                self.e_type,
                self.e_machine,
                self.e_version,
                self.e_entry,
                self.e_phoff,
                self.e_shoff,
                self.e_flags,
                self.e_ehsize,
                self.e_phentsize,
                self.e_phnum,
                self.e_shentsize,
                self.e_shnum,
                self.e_shstrndx
               ]

      return (Elf64_Ehdr.s).pack(*values)

#----------------------------------------------------------------------------
# ELF Program Header Class
#----------------------------------------------------------------------------
class Elf64_Phdr:

   # Structure object to align and package the ELF Program Header
   s = struct.Struct('IIQQQQQQ')

   def __init__(self, data):
      unpacked_data       = (Elf64_Phdr.s).unpack(data)
      self.unpacked_data  = unpacked_data
      self.p_type         = unpacked_data[0]
      self.p_flags        = unpacked_data[1]
      self.p_offset       = unpacked_data[2]
      self.p_vaddr        = unpacked_data[3]
      self.p_paddr        = unpacked_data[4]
      self.p_filesz       = unpacked_data[5]
      self.p_memsz        = unpacked_data[6]
      self.p_align        = unpacked_data[7]

   def printValues(self):
      print("ATTRIBUTE / VALUE")
      for attr, value in self.__dict__.items():
         print(attr, value)

   def getPackedData(self):
      values = [self.p_type,
                self.p_flags,
                self.p_offset,
                self.p_vaddr,
                self.p_paddr,
                self.p_filesz,
                self.p_memsz,
                self.p_align
               ]

      return (Elf64_Phdr.s).pack(*values)


#----------------------------------------------------------------------------
# ELF Segment Information Class
#----------------------------------------------------------------------------
class SegmentInfo:
   def __init__(self):
      self.flag  = 0
   def printValues(self):
      print('Flag: ' + str(self.flag))

#----------------------------------------------------------------------------
# Regular Boot Header Class
#----------------------------------------------------------------------------
class Boot_Hdr:
   def __init__(self, init_val):
      self.image_id = ImageType.NONE_IMG
      self.flash_parti_ver = 3
      self.image_src = init_val
      self.image_dest_ptr = init_val
      self.image_size = init_val
      self.code_size = init_val
      self.sig_ptr = init_val
      self.sig_size = init_val
      self.cert_chain_ptr = init_val
      self.cert_chain_size = init_val
      self.magic_number1 = init_val
      self.version = init_val
      self.OS_type = init_val
      self.boot_apps_parti_entry = init_val
      self.boot_apps_size_entry = init_val
      self.boot_apps_ram_loc = init_val
      self.reserved_ptr = init_val
      self.reserved_1 = init_val
      self.reserved_2 = init_val
      self.reserved_3 = init_val

   def getLength(self):
      return BOOT_HEADER_LENGTH

   def writePackedData(self, target, write_full_hdr):
      values = [self.image_id,
                self.flash_parti_ver,
                self.image_src,
                self.image_dest_ptr,
                self.image_size,
                self.code_size ,
                self.sig_ptr,
                self.sig_size,
                self.cert_chain_ptr,
                self.cert_chain_size,
                self.magic_number1,
                self.version,
                self.OS_type,
                self.boot_apps_parti_entry,
                self.boot_apps_size_entry,
                self.boot_apps_ram_loc,
                self.reserved_ptr,
                self.reserved_1,
                self.reserved_2,
                self.reserved_3 ]

      if self.flash_parti_ver >= 6:
        values.insert(10, self.metadata_size_qti)
        values.insert(11, self.metadata_size)

      if self.image_dest_ptr >= 0x100000000:
        values[3] = 0xFFFFFFFF

      if self.cert_chain_ptr >= 0x100000000:
        values[6] = 0xFFFFFFFF

      if self.sig_ptr >= 0x100000000:
        values[8] = 0xFFFFFFFF

      # Write 10 entries(40B) or 20 entries(80B) of boot header
      if write_full_hdr is False:
         if self.flash_parti_ver >= 6:
            s = struct.Struct('I'* 12)
            values = values[:12]
         else:
            s = struct.Struct('I'* 10)
            values = values[:10]
      else:
         s = struct.Struct('I' * self.getLength())

      packed_data = s.pack(*values)

      fp = OPEN(target,'wb')
      fp.write(packed_data)
      fp.close()

      return s.size

#----------------------------------------------------------------------------
# SBL Boot Header Class
#----------------------------------------------------------------------------
class Sbl_Hdr:
   def __init__(self, init_val):
      self.codeword = init_val
      self.magic = init_val
      self.image_id = init_val
      self.reserved_1 = init_val
      self.reserved_2 = init_val
      self.image_src = init_val
      self.image_dest_ptr = init_val
      self.image_size = init_val
      self.code_size = init_val
      self.sig_ptr = init_val
      self.sig_size = init_val
      self.cert_chain_ptr = init_val
      self.cert_chain_size = init_val
      self.oem_root_cert_sel = init_val
      self.oem_num_root_certs = init_val
      self.booting_image_config = init_val
      self.reserved_6 = init_val
      self.reserved_7 = init_val
      self.reserved_8 = init_val
      self.reserved_9 = init_val

   def getLength(self):
      return SBL_HEADER_LENGTH

   def writePackedData(self, target):
      values = [self.codeword,
                self.magic,
                self.image_id,
                self.reserved_1,
                self.reserved_2,
                self.image_src,
                self.image_dest_ptr,
                self.image_size,
                self.code_size,
                self.sig_ptr,
                self.sig_size,
                self.cert_chain_ptr,
                self.cert_chain_size,
                self.oem_root_cert_sel,
                self.oem_num_root_certs,
                self.booting_image_config,
                self.reserved_6,
                self.reserved_7,
                self.reserved_8,
                self.reserved_9 ]

      s = struct.Struct('I' * self.getLength())
      packed_data = s.pack(*values)

      fp = OPEN(target,'wb')
      fp.write(packed_data)
      fp.close()

      return s.size

#----------------------------------------------------------------------------
# CLASS DEFINITIONS END
#----------------------------------------------------------------------------

#------------------------------------------------------------------------------
# Hooks for Scons
#------------------------------------------------------------------------------
def exists(env):
   return env.Detect('mbn_tools')

def generate(env):

   #----------------------------------------------------------------------------
   # Generate Global Dictionary
   #----------------------------------------------------------------------------
   generate_global_dict(env)

   #----------------------------------------------------------------------------
   # Assign Build Configurable Values
   #----------------------------------------------------------------------------
   init_build_vars(env)

   #----------------------------------------------------------------------------
   # Add Methods to Environment
   #----------------------------------------------------------------------------
   env.AddMethod(filter_dictionary, "FilterDictionary")
   env.AddMethod(image_auth, "ImageAuth")
   env.AddMethod(image_header, "ImageHeader")
   env.AddMethod(pboot_gen_elf, "PBootGenElf")
   env.AddMethod(pboot_add_hash, "PBootAddHash")
   env.AddMethod(modify_elf_flags, "ModifyElfFlags")
   env.AddMethod(generate_code_hash, "GenerateCodeHash")
   env.AddMethod(insert_SBL1_magicCookie, "InsertSBLMagicCookie")
   env.AddMethod(modify_relocatable_flags, "ModifyRelocatableFlags")

   #----------------------------------------------------------------------------
   # Load Encryption Tools and Methods if required
   #----------------------------------------------------------------------------
   if 'USES_ENCRYPT_MBN' in env:
      # Add Encryption Tools to environment
      env.Tool('pil_encrypt', toolpath = ['${BUILD_ROOT}/core/securemsm/ssd/tools/pil_encrypt'])
      env.AddMethod(get_ssd_se_fname, "GetSSDSideEffectFileName")
      env.AddMethod(encrypt_elf_segments, "EncryptElfSegments")
      env.AddMethod(generate_meta_data, "GenerateMetaData")
      env.AddMethod(encrypt_mbn, "EncryptMBN")
   return None

#----------------------------------------------------------------------------
# BOOT TOOLS BEGIN
#----------------------------------------------------------------------------

#----------------------------------------------------------------------------
# generate_meta_data
#----------------------------------------------------------------------------
def generate_meta_data(env, meta_out_file_name, add_magic_num = False):

   '''
   Make call to SSD API to return buffer filled with XML header information.
   The XML header which we write contains information regarding the algorithms
   being used along with specific key values which are to be used for encrpytion.
   '''
   xml_header = env.SSDGetMetaData(add_magic_num)

   # Initialize
   xml_target_file = open(meta_out_file_name,'wb')
   xml_header_size = len(xml_header)

   # Write XML buffer into target file
   xml_target_file.write(xml_header)

   # Pad if necessary to the maximum size
   if xml_header_size <= XML_HEADER_MAXSIZE:
      bytes_to_pad = XML_HEADER_MAXSIZE - xml_header_size
      pad_file(xml_target_file, bytes_to_pad, PAD_BYTE_1)
      xml_target_file.close()
   else:
      xml_target_file.close()
      raise RuntimeError("XML Size too large: " + str(xml_header_size))

#----------------------------------------------------------------------------
# encrypt_mbn
#----------------------------------------------------------------------------
def encrypt_mbn(env, mbn_in_file_name, mbn_out_file_name):
    # Open Files
    mbn_in_fp = OPEN(mbn_in_file_name, "rb")
    mbn_out_fp = OPEN(mbn_out_file_name, "wb+")

    # encrypt the input file content and write to output file
    mbn_file_size = os.path.getsize(mbn_in_file_name)
    file_buff = mbn_in_fp.read(mbn_file_size)
    encrypted_buf = env.SSDEncryptSegment(0, file_buff, mbn_file_size)
    mbn_out_fp.write(encrypted_buf)

    # Close Files
    mbn_in_fp.close()
    mbn_out_fp.close()

    # Clean up encryption files
    env.SSDDeInit()

#----------------------------------------------------------------------------
# get_ssd_se_fname
#----------------------------------------------------------------------------
def get_ssd_se_fname(env):
   return env.SSDGetSideEffectFileName()

#----------------------------------------------------------------------------
# encrypt_elf_segments
#----------------------------------------------------------------------------
def encrypt_elf_segments(env, elf_in_file_name,
                              elf_out_file_name):

   # Open Files
   elf_in_fp = OPEN(elf_in_file_name, "rb")
   elf_out_fp = OPEN(elf_out_file_name, "wb+")

   # Initialize
   [elf_header, phdr_table] = preprocess_elf_file(elf_in_file_name)
   encrypted_seg_counter = 0

   # Copy input file to output file
   shutil.copyfileobj(elf_in_fp, elf_out_fp, os.path.getsize(elf_in_file_name))

   # Begin ELF segment encryption
   for i in range(elf_header.e_phnum):
      curr_phdr = phdr_table[i]

      # Only encrypt segments of LOAD_TYPE. Do not encrypt the hash segment.
      if curr_phdr.p_type == LOAD_TYPE and \
         MI_PBT_SEGMENT_TYPE_VALUE(curr_phdr.p_flags) != MI_PBT_HASH_SEGMENT:

         # Read full segment into buffer
         elf_in_fp.seek(curr_phdr.p_offset)
         data_len = curr_phdr.p_filesz
         file_buff = elf_in_fp.read(data_len)

         # Call encryption routine on buffer
         encrypted_buf = env.SSDEncryptSegment(encrypted_seg_counter, file_buff, data_len)
         encrypted_seg_counter += 1

         # Write encrypted segment into output file in same location
         elf_out_fp.seek(curr_phdr.p_offset)
         elf_out_fp.write(encrypted_buf)

   # Close Files
   elf_in_fp.close()
   elf_out_fp.close()

   # Clean up encryption files
   env.SSDDeInit()

#----------------------------------------------------------------------------
# Converts integer to bytes. If length after conversion
# is smaller than given length of byte string, returned value is right-filled
# with 0x00 bytes. Use Little-endian byte order.
#----------------------------------------------------------------------------
def convert_int_to_byte_string(n, l):
    return b''.join([chr((n >> ((l - i - 1) * 8)) % 256) for i in xrange(l)][::-1])

#----------------------------------------------------------------------------
# Create default elf header
#----------------------------------------------------------------------------
def create_elf_header( output_file_name,
                       image_dest,
                       image_size,
                       is_elf_64_bit = False):

   if (output_file_name is None):
      raise RuntimeError("Requires a ELF header file")

   # Create a elf header and program header
   # Write the headers to the output file
   elf_fp = file(output_file_name, "wb")

   if (is_elf_64_bit is True):
      # ELf header
      elf_fp.write(ELFINFO_MAG0)
      elf_fp.write(ELFINFO_MAG1)
      elf_fp.write(ELFINFO_MAG2)
      elf_fp.write(ELFINFO_MAG3)
      elf_fp.write(ELFINFO_CLASS_64)
      elf_fp.write(ELFINFO_DATA2LSB)
      elf_fp.write(ELFINFO_VERSION_CURRENT)
      elf_fp.write(''.rjust(9, chr(ELFINFO_RESERVED)))
      elf_fp.write(ELFINFO_EXEC_ETYPE)
      elf_fp.write(ELFINFO_ARM_MACHINETYPE)
      elf_fp.write(ELFINFO_VERSION_EV_CURRENT)
      elf_fp.write(convert_int_to_byte_string(image_dest, 8))
      elf_fp.write(convert_int_to_byte_string(ELF64_HDR_SIZE, 8))
      elf_fp.write(convert_int_to_byte_string(ELFINFO_SHOFF, 8))
      elf_fp.write(''.rjust(4, chr(ELFINFO_RESERVED)))
      elf_fp.write(convert_int_to_byte_string(ELF64_HDR_SIZE, 2))
      elf_fp.write(convert_int_to_byte_string(ELF64_PHDR_SIZE, 2))
      elf_fp.write(ELFINFO_PHNUM)
      elf_fp.write(''.rjust(6, chr(ELFINFO_RESERVED)))

      # Program Header
      elf_fp.write(convert_int_to_byte_string(LOAD_TYPE, 4))
      elf_fp.write(convert_int_to_byte_string(MI_PBT_RWE_SEGMENT, 4))
      elf_fp.write(convert_int_to_byte_string(ELF64_HDR_SIZE+ELF64_PHDR_SIZE, 8))
      elf_fp.write(convert_int_to_byte_string(image_dest, 8))
      elf_fp.write(convert_int_to_byte_string(image_dest, 8))
      elf_fp.write(convert_int_to_byte_string(image_size, 8))
      elf_fp.write(convert_int_to_byte_string(image_size, 8))
      elf_fp.write(convert_int_to_byte_string(ELF_BLOCK_ALIGN, 8))
   else:
      # ELf header
      elf_fp.write(ELFINFO_MAG0)
      elf_fp.write(ELFINFO_MAG1)
      elf_fp.write(ELFINFO_MAG2)
      elf_fp.write(ELFINFO_MAG3)
      elf_fp.write(ELFINFO_CLASS_32)
      elf_fp.write(ELFINFO_DATA2LSB)
      elf_fp.write(ELFINFO_VERSION_CURRENT)
      elf_fp.write(''.rjust(9, chr(ELFINFO_RESERVED)))
      elf_fp.write(ELFINFO_EXEC_ETYPE)
      elf_fp.write(ELFINFO_ARM_MACHINETYPE)
      elf_fp.write(ELFINFO_VERSION_EV_CURRENT)
      elf_fp.write(convert_int_to_byte_string(image_dest, 4))
      elf_fp.write(convert_int_to_byte_string(ELF32_HDR_SIZE, 4))
      elf_fp.write(convert_int_to_byte_string(ELFINFO_SHOFF, 4))
      elf_fp.write(''.rjust(4, chr(ELFINFO_RESERVED)))
      elf_fp.write(convert_int_to_byte_string(ELF32_HDR_SIZE, 2))
      elf_fp.write(convert_int_to_byte_string(ELF32_PHDR_SIZE, 2))
      elf_fp.write(ELFINFO_PHNUM)
      elf_fp.write(''.rjust(6, chr(ELFINFO_RESERVED)))

      # Program Header
      elf_fp.write(convert_int_to_byte_string(LOAD_TYPE, 4))
      elf_fp.write(convert_int_to_byte_string(ELF32_HDR_SIZE+ELF32_PHDR_SIZE, 4))
      elf_fp.write(convert_int_to_byte_string(image_dest, 4))
      elf_fp.write(convert_int_to_byte_string(image_dest, 4))
      elf_fp.write(convert_int_to_byte_string(image_size, 4))
      elf_fp.write(convert_int_to_byte_string(image_size, 4))
      elf_fp.write(convert_int_to_byte_string(MI_PBT_RWE_SEGMENT, 4))
      elf_fp.write(convert_int_to_byte_string(ELF_BLOCK_ALIGN, 4))

   elf_fp.close()
   return 0

#----------------------------------------------------------------------------
# image_header
#----------------------------------------------------------------------------
def image_header(env, gen_dict,
                      code_file_name,
                      output_file_name,
                      secure_type,
                      header_format = 'reg',
                      requires_preamble = False,
                      preamble_file_name = None,
                      elf_file_name = None,
                      write_full_hdr = False,
                      in_code_size = None,
                      cert_chain_size_in = CERT_CHAIN_ONEROOT_MAXSIZE,
                      num_of_pages = None,
                      header_version = None):

   # Preliminary checks
   if (requires_preamble is True) and (preamble_file_name is None):
      raise RuntimeError("Image Header requires a preamble file")

   if (gen_dict['IMAGE_KEY_MBN_TYPE'] == 'elf') and (elf_file_name is None):
      raise RuntimeError("ELF Image Headers require an elf file")

   if (in_code_size is None) and (os.path.exists(code_file_name) is False):
      raise RuntimeError("Code size unavailable, and input file does not exist")

   # Initialize
   if in_code_size is not None:
      code_size = in_code_size
   else:
      code_size = os.path.getsize(code_file_name)

   image_dest = 0
   image_source = 0

   # If secure build, set signature and cert chain sizes
   if secure_type == 'secure':
      signature_size = SHA256_SIGNATURE_SIZE
      cert_chain_size = cert_chain_size_in
      image_size = code_size + cert_chain_size + signature_size
      if (image_size % 4) != 0:
         image_size += (4 - (image_size % 4))
   else:
      signature_size = 0
      cert_chain_size = 0
      image_size = code_size

   if header_version:
      assert header_version in [3, 5, 6], 'Not a valid MBN header version'

   # For ELF or hashed images, image destination will be determined from an ELF input file
   if gen_dict['IMAGE_KEY_MBN_TYPE'] == 'elf':
      image_dest = get_hash_address(elf_file_name) + (header_size(header_version))
   elif gen_dict['IMAGE_KEY_MBN_TYPE'] == 'bin':
      image_dest = gen_dict['IMAGE_KEY_IMAGE_DEST']
      image_source = gen_dict['IMAGE_KEY_IMAGE_SOURCE']

   # Build the header based on format specified
   if header_format == 'sbl':
      boot_sbl_header = Sbl_Hdr(init_val = int('0xFFFFFFFF',16))
      boot_sbl_header.codeword = FLASH_CODE_WORD
      boot_sbl_header.magic = MAGIC_NUM
      boot_sbl_header.image_id = gen_dict['IMAGE_KEY_IMAGE_ID']
      boot_sbl_header.image_src = MI_BOOT_SBL_HDR_SIZE
      boot_sbl_header.image_dest_ptr = image_dest
      boot_sbl_header.image_size = image_size
      boot_sbl_header.code_size = code_size
      boot_sbl_header.sig_ptr = image_dest + code_size
      boot_sbl_header.sig_size = signature_size
      boot_sbl_header.cert_chain_ptr = image_dest + code_size + signature_size
      boot_sbl_header.cert_chain_size = cert_chain_size
      boot_sbl_header.oem_root_cert_sel = gen_dict['IMAGE_KEY_OEM_ROOT_CERT_SEL']
      boot_sbl_header.oem_num_root_certs = gen_dict['IMAGE_KEY_OEM_NUM_ROOT_CERTS']
      if 'USES_SBL_FOR_AARCH64' in env:
         boot_sbl_header.booting_image_config = SBL_AARCH64
      elif 'USES_SBL_FOR_AARCH632' in env:
         boot_sbl_header.booting_image_config = SBL_AARCH32

      # If preamble is required, output the preamble file and update the boot_sbl_header
      if requires_preamble is True:
         boot_sbl_header = image_preamble(gen_dict, preamble_file_name, boot_sbl_header, num_of_pages)

      # Package up the header and write to output file
      boot_sbl_header.writePackedData(target = output_file_name)

   elif header_format == 'reg':
      boot_header = Boot_Hdr(init_val = int('0x0',16))
      boot_header.image_id = gen_dict['IMAGE_KEY_IMAGE_ID']
      boot_header.image_src = image_source
      boot_header.image_dest_ptr = image_dest
      boot_header.image_size = image_size
      boot_header.code_size = code_size
      boot_header.sig_ptr = image_dest + code_size
      boot_header.sig_size = signature_size
      boot_header.cert_chain_ptr = image_dest + code_size + signature_size
      boot_header.cert_chain_size = cert_chain_size
      boot_header.flash_parti_ver = header_version  # version

      if header_version >= 5:
         boot_header.image_src = 0                            # sig_size_qc
         boot_header.image_dest_ptr = 0                       # cert_chain_size_qc

      if header_version >= 6:
         boot_header.metadata_size_qti = 0                    # qti_metadata size
         boot_header.metadata_size = 0                        # oem_metadata size

      # If preamble is required, output the preamble file and update the boot_header
      if requires_preamble is True:
         boot_header = image_preamble(gen_dict, preamble_file_name, boot_header, num_of_pages)

      # Package up the header and write to output file
      boot_header.writePackedData(target = output_file_name, write_full_hdr = write_full_hdr)

   else:
      raise RuntimeError("Header format not supported: " + str(header_format))
   return 0


#----------------------------------------------------------------------------
# pboot_gen_elf
#----------------------------------------------------------------------------
def pboot_gen_elf(env, elf_in_file_name,
                       hash_out_file_name,
                       elf_out_file_name,
                       secure_type = 'non_secure',
                       hash_seg_max_size = None,
                       last_phys_addr = None,
                       append_xml_hdr = False,
                       is_sha256_algo = True,
                       cert_chain_size_in = CERT_CHAIN_ONEROOT_MAXSIZE,
                       header_version = None):
   sha_algo = 'SHA1'
   if is_sha256_algo:
       sha_algo = 'SHA256'

   if header_version >= 6:
       sha_algo = 'SHA384'
   image_header_size = header_size(header_version)

   if (sha_algo == 'SHA384'):
      mi_prog_boot_digest_size = 48
   elif sha_algo == 'SHA256':
      mi_prog_boot_digest_size = 32
   else:
      mi_prog_boot_digest_size = 20

   # Open Files
   elf_in_fp = OPEN(elf_in_file_name, "rb")
   hash_out_fp = OPEN(hash_out_file_name, "wb+")

   if elf_out_file_name is not None:
      elf_out_fp = OPEN(elf_out_file_name, "wb+")

   # Initialize
   [elf_header, phdr_table] = preprocess_elf_file(elf_in_file_name)
   num_phdrs = elf_header.e_phnum
   phdr_total_size = num_phdrs * elf_header.e_phentsize
   phdr_size = elf_header.e_phentsize
   hashtable_size = 0
   hashtable_shift = 0

   if elf_header.e_ident[ELFINFO_CLASS_INDEX] == ELFINFO_CLASS_64:
      new_phdr = Elf64_Phdr(b'\0' * ELF64_PHDR_SIZE)
      elf_header_size = ELF64_HDR_SIZE
      is_elf64 = True
   else:
      new_phdr = Elf32_Phdr(b'\0' * ELF32_PHDR_SIZE)
      elf_header_size = ELF32_HDR_SIZE
      is_elf64 = False

   hash = b'\0' * mi_prog_boot_digest_size
   phdr_start = 0
   bytes_to_pad = 0
   hash_seg_end = 0

   # Process program headers if an output elf is specified
   if elf_out_file_name is not None:
      # Assert limit on number of program headers in input ELF
      if num_phdrs > MAX_PHDR_COUNT:
         raise RuntimeError("Input ELF has exceeded maximum number of program headers")

      # Create new program header for the ELF Header + Program Headers
      new_phdr.p_type = NULL_TYPE
      new_phdr.p_flags = MI_PBT_ELF_PHDR_SEGMENT

      # If hash table program header is not found, make sure to include it
      elf_header.e_phnum += 2

      # Create an empty hash entry for PHDR_TYPE
      hash_out_fp.write(b'\0' * mi_prog_boot_digest_size)
      hashtable_size += mi_prog_boot_digest_size

      # Create an empty hash entry for the hash segment itself
      hash_out_fp.write(b'\0' * mi_prog_boot_digest_size)
      hashtable_size += mi_prog_boot_digest_size

   # Begin hash table generation
   for i in range(num_phdrs):
      curr_phdr = phdr_table[i]

      if (MI_PBT_PAGE_MODE_VALUE(curr_phdr.p_flags) == MI_PBT_PAGED_SEGMENT):
         seg_offset = curr_phdr.p_offset
         seg_size = curr_phdr.p_filesz
         hash_size = 0

         # Check if the vaddr is page aligned
         off = curr_phdr.p_vaddr & (ELF_BLOCK_ALIGN - 1)
         if int(off) is not 0:
            seg_size -= (ELF_BLOCK_ALIGN - off)
            seg_offset += (ELF_BLOCK_ALIGN - off)

         # Seg_size should be page aligned
         if (seg_size & (ELF_BLOCK_ALIGN - 1)) > 0:
            raise RuntimeError("seg_size: " + hex(seg_size) + " is not ELF page aligned!")

         off = seg_offset + seg_size

         while seg_offset < off:

            if seg_offset < ELF_BLOCK_ALIGN:
               hash_size = seg_offset
            else:
               hash_size = ELF_BLOCK_ALIGN

            elf_in_fp.seek(seg_offset)
            fbuf = elf_in_fp.read(hash_size)

            if MI_PBT_CHECK_FLAG_TYPE(curr_phdr.p_flags) is True:
               hash = generate_hash(fbuf, sha_algo)
            else:
               hash = b'\0' * mi_prog_boot_digest_size

            # Write hash to file
            hash_out_fp.write(hash)

            hashtable_size += mi_prog_boot_digest_size
            seg_offset += ELF_BLOCK_ALIGN

      # Copy the hash entry for all that are PAGED segments and those that are not the PHDR type. This is for
      # backward tool compatibility where some images are generated using older exe tools.
      elif((MI_PBT_PAGE_MODE_VALUE(curr_phdr.p_flags) == MI_PBT_NON_PAGED_SEGMENT) and (curr_phdr.p_type is not PHDR_TYPE)):
         # Read full hash entry into buffer
         elf_in_fp.seek(curr_phdr.p_offset)
         data_len = curr_phdr.p_filesz
         file_buff = elf_in_fp.read(data_len)

         if (MI_PBT_CHECK_FLAG_TYPE(curr_phdr.p_flags) is True) and (data_len > 0):
            hash = generate_hash(file_buff, sha_algo)
         else:
            hash = b'\0' *  mi_prog_boot_digest_size

         # Write hash to file
         hash_out_fp.write(hash)

         hashtable_size += mi_prog_boot_digest_size
   # End hash table generation

   # Generate the rest of the ELF output file if specified
   if elf_out_file_name is not None:

     # Preempt hash table size if necessary
     if secure_type == 'secure':
        hashtable_size += (SHA256_SIGNATURE_SIZE + cert_chain_size_in)

     if append_xml_hdr is True:
        hashtable_size += XML_HEADER_MAXSIZE

     # Initialize the hash table program header
     [hash_Phdr, pad_hash_segment, hash_tbl_end_addr, hash_tbl_offset] = \
            initialize_hash_phdr(elf_in_file_name, hashtable_size, image_header_size, ELF_BLOCK_ALIGN, is_elf64)

     # Check if hash segment max size parameter was passed
     if (hash_seg_max_size is not None):
         # Error checking for hash segment size validity
        if hashtable_size > hash_seg_max_size:
           raise RuntimeError("Hash table exceeds maximum hash segment size: " + hex(hash_seg_max_size))
        if (hash_seg_max_size & (ELF_BLOCK_ALIGN-1)) is not 0:
           raise RuntimeError("Hash segment size passed is not ELF Block Aligned: " + hex(hash_seg_max_size))

     # Check if hash physical address parameter was passed
     if last_phys_addr is not None:
        hash_Phdr.p_vaddr = last_phys_addr
        hash_Phdr.p_paddr = last_phys_addr

     # Check if hash segment max size was passed
     if hash_seg_max_size is not None:
        hash_Phdr.p_memsz = hash_seg_max_size

     # Determine the end of the hash segment, make sure it's block aligned
     bytes_to_pad = ELF_BLOCK_ALIGN - pad_hash_segment
     hash_seg_end = hash_tbl_end_addr + bytes_to_pad

     # Check if a shifting is required to accommodate for the hash segment.
     # Get the minimum offset by going through the program headers.
     # Note that the program headers in the input file do not contain
     # the dummy program header for ELF + Program header, and the
     # program header for the hashtable.
     min_offset = phdr_table[0].p_offset
     for i in range(num_phdrs):
        curr_phdr = phdr_table[i]
        if curr_phdr.p_offset < min_offset:
            min_offset = curr_phdr.p_offset

     if min_offset < hash_seg_end:
        hashtable_shift = hash_seg_end - min_offset

     # Move program headers to after ELF header
     phdr_start = elf_header_size

     # We copy over no section headers so assign these values to 0 in ELF Header
     elf_header.e_shnum = 0
     elf_header.e_shstrndx = 0
     elf_header.e_shoff = 0

     # Output remaining ELF segments
     for i in range(num_phdrs):

         # Increment the file offset before writing to the destination file
         curr_phdr = phdr_table[i]

         # We do not copy over program headers of PHDR type, decrement the program
         # header count and continue the loop
         if curr_phdr.p_type is PHDR_TYPE:
            elf_header.e_phnum -= 1
            continue

         src_offset = curr_phdr.p_offset

         # Copy the ELF segment
         file_copy_offset(elf_in_fp, src_offset, elf_out_fp, curr_phdr.p_offset + hashtable_shift, curr_phdr.p_filesz)

     # Output remaining program headers and ELF segments
     elf_header.e_phoff = phdr_start

     # Output new program headers which we have generated
     elf_out_fp.seek(phdr_start)
     new_phdr.p_filesz = elf_header_size + (elf_header.e_phnum * phdr_size)
     elf_out_fp.write(new_phdr.getPackedData())
     elf_out_fp.write(hash_Phdr.getPackedData())
     phdr_start += (2 * phdr_size)

     # Increment the file offset before writing to the destination file
     for i in range(num_phdrs):
         curr_phdr = phdr_table[i]

         if curr_phdr.p_type is PHDR_TYPE:
            continue

         curr_phdr.p_offset += hashtable_shift

         # Copy the program header
         elf_out_fp.seek(phdr_start)
         elf_out_fp.write(curr_phdr.getPackedData())

         # Update phdr_start
         phdr_start += phdr_size

     # Finally, copy the new ELF header to the destination file
     elf_out_fp.seek(0)
     elf_out_fp.write(elf_header.getPackedData())

     # Recalculate hash of ELF + program headers and output to hash output file
     elf_out_fp.seek(0)
     # Read the elf header
     elfhdr_buff = elf_out_fp.read(elf_header_size)
     # Seek to the program header offset listed in elf header.
     elf_out_fp.seek(elf_header.e_phoff)
     # Read the program header and compute hash
     proghdr_buff = elf_out_fp.read(elf_header.e_phnum * phdr_size)

     hash = generate_hash(elfhdr_buff + proghdr_buff, sha_algo)

     # Write hash to file as first hash table entry
     hash_out_fp.seek(0)
     hash_out_fp.write(hash)

   # Close files
   elf_in_fp.close()
   hash_out_fp.close()

   if elf_out_file_name is not None:
      elf_out_fp.close()

   return 0


#----------------------------------------------------------------------------
# pboot_add_hash
#----------------------------------------------------------------------------
def pboot_add_hash(env, elf_in_file_name,
                        hash_tbl_file_name,
                        elf_out_file_name):

   # Open files
   elf_in_fp = OPEN(elf_in_file_name, "rb")
   hash_tbl_fp = OPEN(hash_tbl_file_name, "rb")
   elf_out_fp = OPEN(elf_out_file_name, "wb+")

   # Initialize
   [elf_header, phdr_table] = preprocess_elf_file(elf_in_file_name)

   hash_size = os.path.getsize(hash_tbl_file_name)
   hash_segment_found = False

   # Attempt to find the location of the hash program header
   for i in range(elf_header.e_phnum):
      curr_phdr = phdr_table[i]
      if curr_phdr.p_flags == MI_PBT_ELF_HASH_SEGMENT:
         hash_segment_found = True
         break

   if hash_segment_found is True:
      # Copy input file to output file
      shutil.copyfileobj(elf_in_fp, elf_out_fp, os.path.getsize(elf_in_file_name))

      # Update ELF to insert hash table at corresponding file offset
      hash_hdr_offset = curr_phdr.p_offset
      file_copy_offset(hash_tbl_fp, 0, elf_out_fp, hash_hdr_offset, hash_size)

   else:
      raise RuntimeError("Hash segment program header not found in file " + elf_in_file_name)

   # Close files
   elf_in_fp.close()
   hash_tbl_fp.close()
   elf_out_fp.close()

   return 0

#----------------------------------------------------------------------------
# image_auth
#----------------------------------------------------------------------------
def image_auth(env, *args):

   if len(args) < 7 or len(args) > 8:
      raise RuntimeError("Usage Invalid")

   # Initialize File Names
   binary_in            = args[0]
   signature            = args[1]
   attestation_cert     = args[2]
   attestation_ca_cert  = args[3]
   root_cert            = args[4]
   cert_chain_out       = args[5]
   signed_image_out     = args[6]
   if len(args) == 8:
     cert_size_max_in   = args[7]
   else:
     cert_size_max_in   = CERT_CHAIN_ONEROOT_MAXSIZE

   # Creating list of certificates to support creation of certificate chains
   # of lenth 1, 2, or 3 certificates
   cert_list = []
   num_certs = 0
   if (os.path.exists(attestation_cert)):
      cert_list.append(attestation_cert)
      num_certs = num_certs + 1
   if (os.path.exists(attestation_ca_cert)):
      cert_list.append(attestation_ca_cert)
      num_certs = num_certs + 1
   if (os.path.exists(root_cert)):
      cert_list.append(root_cert)
      num_certs = num_certs + 1

   if (num_certs == 0):
      raise RuntimeError("Missing file(s) required for signing.\n")

   # Create the Certificate Chain
   concat_files (cert_chain_out, cert_list)

   # Pad to ensure Certificate Chain Size is CERT_CHAIN_MAX_SIZE
   cert_size = os.path.getsize(cert_chain_out)

   if cert_size <= cert_size_max_in:
      bytes_to_pad = cert_size_max_in - cert_size
      cert_fp = OPEN(cert_chain_out,'ab')
      pad_file(cert_fp, bytes_to_pad, PAD_BYTE_1)
      cert_fp.close()
   else:
      raise RuntimeError("Certificate Size too large: " + str(cert_size))

   # Create the Final Signed Image File
   concat_files (signed_image_out, [binary_in, signature, cert_chain_out])

   return 0

#----------------------------------------------------------------------------
# modify_relocatable_flags
#----------------------------------------------------------------------------
def modify_relocatable_flags(env, output_elf ):

   # Offset into program header where the p_flags field is stored
   phdr_align_flag_offset = 28
   phdr_reloc_flag_offset = 24

   # Initialize
   [elf_header, phdr_table] = preprocess_elf_file(output_elf)

   if elf_header.e_ident[ELFINFO_CLASS_INDEX] == ELFINFO_CLASS_64:
      curr_phdr = Elf64_Phdr('\0' * ELF64_PHDR_SIZE)
      elf_header_size = ELF64_HDR_SIZE
      is_elf64 = True
   else:
      curr_phdr = Elf32_Phdr('\0' * ELF32_PHDR_SIZE)
      elf_header_size = ELF32_HDR_SIZE
      is_elf64 = False

   # Open files
   elf_in_fp = OPEN(output_elf, "r+")

   # Go to the start of the p_flag entry in the first program header
   file_offset_align_flag = elf_header.e_phoff + phdr_align_flag_offset

   # Change the align field in the program header in the ELF file
   elf_in_fp.seek(file_offset_align_flag)
   curr_phdr = phdr_table[0]

   #default alignment value is 1MB unless otherwise specified
   if 'USES_RELOC_ALIGN_VALUE_4MB' in env:
       alignment_value = ALIGNVALUE_4MB
   else:
       alignment_value = ALIGNVALUE_1MB



   #create new alignment value
   new_align = (curr_phdr.p_align & 0) | alignment_value

   # Create structure to package new flag field
   s = struct.Struct('I')
   new_flag_bytes = s.pack(new_align)

   # Write the new flag value and incr ement offset
   elf_in_fp.write(new_flag_bytes)

   # Go to the start of the p_flag entry in the first program header
   file_offset_reloc_flag = elf_header.e_phoff + phdr_reloc_flag_offset

   # Change each program header flag in the ELF file with relocatable flag
   for i in range(elf_header.e_phnum):
      # Seek to correct location and create new p_flag value
      elf_in_fp.seek(file_offset_reloc_flag)
      curr_phdr = phdr_table[i]
      new_flag = (curr_phdr.p_flags & ~MI_PBT_FLAGS_MASK) | (MI_PBT_ELF_AMSS_RELOCATABLE_IMAGE)

      # Create structure to package new flag field
      s = struct.Struct('I')
      new_flag_bytes = s.pack(new_flag)

      # Write the new flag value and increment offset
      elf_in_fp.write(new_flag_bytes)
      file_offset_reloc_flag += elf_header.e_phentsize

   # Close files
   elf_in_fp.close()


   return 0


#----------------------------------------------------------------------------
# modify_elf_flags
#----------------------------------------------------------------------------
def modify_elf_flags(env, elf_in_file_name,
                          scl_file_name):

   # Initialize
   [elf_header, phdr_table] = preprocess_elf_file(elf_in_file_name)
   segment_list = readSCL(scl_file_name, env['GLOBAL_DICT'])

   if elf_header.e_ident[ELFINFO_CLASS_INDEX] == ELFINFO_CLASS_64:
     curr_phdr = Elf64_Phdr('\0' * ELF64_PHDR_SIZE)
     # Offset into program header where the p_flags field is stored
     phdr_flag_off = 4
   else:
     curr_phdr = Elf32_Phdr('\0' * ELF32_PHDR_SIZE)
     # Offset into program header where the p_flags field is stored
     phdr_flag_off = 24

   # Open files
   elf_in_fp = OPEN(elf_in_file_name, "r+")

   # Check for corresponding number of segments
   if len(segment_list) is not elf_header.e_phnum:
      raise RuntimeError('SCL file and ELF file have different number of segments!')

   # Go to the start of the p_flag entry in the first program header
   file_offset = elf_header.e_phoff + phdr_flag_off

   # Change each program header flag in the ELF file based off the SCL file
   for i in range(elf_header.e_phnum):
      # Seek to correct location and create new p_flag value
      elf_in_fp.seek(file_offset)
      curr_phdr = phdr_table[i]
      new_flag = (curr_phdr.p_flags & ~MI_PBT_FLAGS_MASK) | (segment_list[i].flag)

      # Create structure to package new flag field
      s = struct.Struct('I')
      new_flag_bytes = s.pack(new_flag)

      # Write the new flag value and increment offset
      elf_in_fp.write(new_flag_bytes)
      file_offset += elf_header.e_phentsize

   # Close files
   elf_in_fp.close()

   return 0

#----------------------------------------------------------------------------
# generate_code_hash
#----------------------------------------------------------------------------
def generate_code_hash(env, elf_in_file_name):

   # Initialize
   [elf_header, phdr_table] = preprocess_elf_file(elf_in_file_name)

   if elf_header.e_ident[ELFINFO_CLASS_INDEX] == ELFINFO_CLASS_64:
     curr_phdr = Elf64_Phdr('\0' * ELF64_PHDR_SIZE)
     # Offset into program header where the p_flags field is stored
     phdr_flag_off = 4
   else:
     curr_phdr = Elf32_Phdr('\0' * ELF32_PHDR_SIZE)
     # Offset into program header where the p_flags field is stored
     phdr_flag_off = 24

   # Open files
   elf_in_fp = OPEN(elf_in_file_name, "rb+")

   # Go to the start of the p_flag entry in the first program header
   file_offset = elf_header.e_phoff + phdr_flag_off

   # XXX Get these from env?
   DP_CODE_ALIGN = 0x100
   DP_PAGE_SIZE  = 4096
   DP_HASH_SIZE  = 32  # SHA-256
   DP_HASH_MAGIC = 0xC0DEDEC0
   PH_PERM_RW   = 0x06
   PH_PERM_RX   = 0x05
   PH_PERM_RO   = 0x04
   PH_PERM_MASK = 0x07

   page_size = DP_PAGE_SIZE
   hash_size = DP_HASH_SIZE

   # First identify the hash segment. It is the first RW section.
   # Its Align should be 8, and its size a multiple of DP_HASH_SIZE;

   hash_seg_idx = -1
   for i in range(elf_header.e_phnum):
      curr_phdr = phdr_table[i]

      if (curr_phdr.p_align == 8 and
          (curr_phdr.p_flags & PH_PERM_MASK) == PH_PERM_RW and
          curr_phdr.p_filesz != 0 and (curr_phdr.p_filesz % DP_HASH_SIZE) == 0):
         hash_seg_idx = i
         # Validate the contents of the hash segment. It should be
         # filled with DP_HASH_MAGIC
         elf_in_fp.seek(curr_phdr.p_offset)
         hash_data = "";
         while (len(hash_data) < curr_phdr.p_filesz):
            hash_data = hash_data + elf_in_fp.read(curr_phdr.p_filesz - len(hash_data))

         hash_data = struct.unpack("I" * (curr_phdr.p_filesz / 4), hash_data)

         for v in hash_data[:]:
            if (v != DP_HASH_MAGIC):
               hash_seg_idx = -1
               break;

         if (hash_seg_idx != -1):
            break

   if (hash_seg_idx == -1):
      # return if there is no hash segment.
      return 0

   hash_phdr = phdr_table[hash_seg_idx]

   # Now find the code segment for the hashes. Look for matching number of pages
   code_seg_idx = -1
   code_seg_pages = hash_phdr.p_filesz / DP_HASH_SIZE

   for i in range(elf_header.e_phnum):
      curr_phdr = phdr_table[i]
      curr_pages = (curr_phdr.p_filesz + DP_PAGE_SIZE - 1) / DP_PAGE_SIZE

      if (curr_phdr.p_align == DP_CODE_ALIGN and
          (curr_phdr.p_flags & PH_PERM_MASK) == PH_PERM_RX and
          curr_pages == code_seg_pages):
         if (code_seg_idx != -1):
            raise RuntimeError('Multiple code segments match for: ' + code_seg_pages + ' pages')
         code_seg_idx = i

   if (code_seg_idx == -1):
      raise RuntimeError('No matching code segment found')

   code_phdr = phdr_table[code_seg_idx]

   # Now hash the pages in the code segment
   hashes = []
   elf_in_fp.seek(code_phdr.p_offset)
   bytes_left = code_phdr.p_filesz;
   while (bytes_left > 0):
      bytes_in_page = min(bytes_left, DP_PAGE_SIZE)
      page = "";
      while (len(page) < bytes_in_page):
         page = page + elf_in_fp.read(bytes_in_page - len(page))
      if (len(page) < DP_PAGE_SIZE):
         page = page + (struct.pack('b', 0) * (DP_PAGE_SIZE - len(page)))
      hashes = hashes + [generate_hash(page, 'SHA256')]
      bytes_left -= bytes_in_page

   # And write them to the hash segment
   elf_in_fp.seek(hash_phdr.p_offset)

   for h in hashes[:]:
      elf_in_fp.write(h)

   # Close files
   elf_in_fp.close()

   return 0

#----------------------------------------------------------------------------
# BOOT TOOLS END
#----------------------------------------------------------------------------

#----------------------------------------------------------------------------
# HELPER FUNCTIONS BEGIN
#----------------------------------------------------------------------------

#----------------------------------------------------------------------------
# Create a list to hold all segment information from an input SCL file
#----------------------------------------------------------------------------
def readSCL(filename, global_dict):

  scl_fp = OPEN(filename,'r')

  # Initialize
  file_data = scl_fp.readlines()
  num_lines = len(file_data)
  current_line = ''
  previous_line = ''
  strip_chars = '(){}[]'
  i = 0
  bracket_counter = 0
  seg_list = []

  # Parse through all lines
  while i < num_lines:

     # Save the last line read
     previous_line = current_line
     current_line = file_data[i]

     # Look for the symbol '{' for the line to read.
     # Use bracket counter to skip nested '{ }'
     if ('{' in current_line):
        if bracket_counter is 0:
           # Create a new SegmentInfo class and set up tokens
           new_scl_entry = SegmentInfo()
           previous_line = previous_line.strip()
           tokens = previous_line.split(' ')

           # Check that at least two tokens were parsed
           # Token 1: Segment Name
           # Token 2: Start Address -- not used in MBN tools
           if len(tokens) < 2:
              raise RuntimeError('SCL Segment Syntax malformed: ' + previous_line)

           # Get the segment flags corresponding to the segment name description
           new_scl_entry.flag = getSegmentFlag(tokens[0].strip(strip_chars))
           seg_list.append(new_scl_entry)

        bracket_counter += 1
     elif '}' in current_line:
        bracket_counter -= 1

     i+=1

  scl_fp.close()
  return seg_list

#----------------------------------------------------------------------------
# Given a string parsed from a SCL file, returns the ELF segment flags
#----------------------------------------------------------------------------
def getSegmentFlag(seg_info):

   ret_val = None

   # Define string values for various types of segments
   RO = "RO"
   RW = "RW"
   ZI = "ZI"
   PAGEABLE = "PAGED"
   NOTPAGEABLE = "NOTPAGED"
   SWAPABLE = "SWAPPED"
   SWAP_POOL = "SWAP_POOL"
   RESERVED = "RESERVED"
   HASHTBL = "HASH"
   SHARED = "SHARED"
   NOTUSED = "NOTUSED"
   BOOT_SEGMENT = "BOOT_SEGMENT"
   CODE = "CODE"
   L4BSP = "L4BSP"
   POOL_INDEX_0 = "INDEX_0"
   POOL_INDEX_1 = "INDEX_1"

   # New definitions for EOS demand paging
   NONPAGE = "NONPAGE"
   PAGEUNLOCKED = "PAGEUNLOCKED"
   PAGELOCKED = "PAGELOCKED"
   UNSECURE = "UNSECURE"

   if seg_info is None or len(seg_info) is 0:
      raise RuntimeError('Invalid segment information passed: ' + seg_info)

   # Conditional checks and assignments of the corresponding segment flag values
   if NOTPAGEABLE in seg_info:
      if RO in seg_info:
         ret_val = MI_PBT_ELF_AMSS_NON_PAGED_RO_SEGMENT
      elif CODE in seg_info:
         ret_val = MI_PBT_ELF_AMSS_NON_PAGED_RO_SEGMENT
      elif ZI in seg_info:
         if SWAP_POOL in seg_info:
            if POOL_INDEX_0 in seg_info:
               ret_val = MI_PBT_ELF_SWAP_POOL_NON_PAGED_ZI_SEGMENT_INDEX0
            else:
               ret_val = MI_PBT_ELF_SWAP_POOL_NON_PAGED_ZI_SEGMENT_INDEX1
         else:
            ret_val = MI_PBT_ELF_AMSS_NON_PAGED_ZI_SEGMENT

      elif NOTUSED in seg_info:
         ret_val = MI_PBT_ELF_AMSS_NON_PAGED_NOTUSED_SEGMENT

      elif SHARED in seg_info:
         ret_val = MI_PBT_ELF_AMSS_NON_PAGED_SHARED_SEGMENT
      elif HASHTBL in seg_info:
         ret_val = MI_PBT_ELF_HASH_SEGMENT
      elif BOOT_SEGMENT in seg_info:
         ret_val = MI_PBT_ELF_BOOT_SEGMENT
      elif L4BSP in seg_info:
         ret_val = MI_PBT_ELF_NON_PAGED_L4BSP_SEGMENT
      else:
         ret_val = MI_PBT_ELF_AMSS_NON_PAGED_RW_SEGMENT

   elif PAGEABLE in seg_info:
      if RO in seg_info or CODE in seg_info:
         if SWAPABLE in seg_info:
            if POOL_INDEX_0 in seg_info:
               ret_val = MI_PBT_ELF_SWAPPED_PAGED_RO_SEGMENT_INDEX0
            else:
               ret_val = MI_PBT_ELF_SWAPPED_PAGED_RO_SEGMENT_INDEX1
         else:
            ret_val = MI_PBT_ELF_AMSS_PAGED_RO_SEGMENT
      elif ZI in seg_info:
         ret_val = MI_PBT_ELF_AMSS_PAGED_ZI_SEGMENT

      elif NOTUSED in seg_info:
         ret_val = MI_PBT_ELF_AMSS_PAGED_NOTUSED_SEGMENT
      elif SHARED in seg_info:
         ret_val = MI_PBT_ELF_AMSS_PAGED_SHARED_SEGMENT
      elif L4BSP in seg_info:
         ret_val = MI_PBT_ELF_PAGED_L4BSP_SEGMENT
      else:
         ret_val = MI_PBT_ELF_AMSS_PAGED_RW_SEGMENT

   elif PAGELOCKED in seg_info:
      ret_val = MI_PBT_ELF_PAGED_LOCKED_SEGMENT
   elif PAGEUNLOCKED in seg_info:
      ret_val = MI_PBT_ELF_PAGED_UNLOCKED_SEGMENT
   elif NONPAGE in seg_info:
      ret_val = MI_PBT_ELF_RESIDENT_SEGMENT
   elif UNSECURE in seg_info:
      ret_val = MI_PBT_ELF_UNSECURE_SEGMENT

   else:
      raise RuntimeError('The segment name is wrongly defined in the SCL file: ' + seg_info)

   return ret_val

#----------------------------------------------------------------------------
# Pad a file with specific number of bytes
# Note: Assumes the fp is seeked to the correct location of padding
#----------------------------------------------------------------------------
def pad_file(fp, num_bytes, value):

   if num_bytes < 0:
      raise RuntimeError("Number of bytes to pad must be greater than zero")

   while num_bytes > 0:
      fp.write('%c' % value)
      num_bytes -= 1

   return

#----------------------------------------------------------------------------
# Concatenates the files listed in 'sources' in order and writes to 'target'
#----------------------------------------------------------------------------
def concat_files (target, sources):
   if type(sources) is not list:
      sources = [sources]

   target_file = OPEN(target,'wb')

   for fname in sources:
      file = OPEN(fname,'rb')
      while True:
         bin_data = file.read(65536)
         if not bin_data:
            break
         target_file.write(bin_data)
      file.close()
   target_file.close()

#----------------------------------------------------------------------------
# Parse build configurable values and assign to global variables for tools
#----------------------------------------------------------------------------
def init_build_vars(env):

   # Maximum size of Certificate Chain used in Secure Boot
   global CERT_CHAIN_ONEROOT_MAXSIZE
   CERT_CHAIN_ONEROOT_MAXSIZE = get_dict_value(env['GLOBAL_DICT'], 'CERT_CHAIN_MAXSIZE', (6*1024))

   # Maximum size of the XML Header used in encrypted ELF images
   global XML_HEADER_MAXSIZE
   XML_HEADER_MAXSIZE = get_dict_value(env['GLOBAL_DICT'], 'XML_HEADER_MAXSIZE', (2*1024))

#----------------------------------------------------------------------------
# Generates the global dictionary and add to the environment
#----------------------------------------------------------------------------
def generate_global_dict(env):

   # Get file names for 'cust' and 'targ' auto-generated files inside 'build/ms'
   cust_h = env.subst('CUST${BUILD_ID}.H').lower()
   targ_h = env.subst('TARG${BUILD_ID}.H').lower()
   cust_file_name = str(env.FindFile(cust_h, "${INC_ROOT}/build/ms"))
   targ_file_name = str(env.FindFile(targ_h, "${INC_ROOT}/build/ms"))

   # Check that files are present
   if (os.path.exists(cust_file_name) is True) and \
      (os.path.exists(targ_file_name) is True):

      # Populate the dictionary from the auto-generated files
      global_dict = populate_dictionary(targ_file_name, cust_file_name)
   else:
      global_dict = {}

   # Add the dictionary to the environment
   env.Replace(GLOBAL_DICT = global_dict)

#----------------------------------------------------------------------------
# Populate the dictionary from a list of input files
#----------------------------------------------------------------------------
def populate_dictionary(*args):

   if len(args) < 1:
      raise RuntimeError("At least 1 file must be specified as an input")

   global_dict = {}
   Fields = ["Define", "Key", "Value"]

   # For each input file
   for i in range(len(args)):

      template_file_path = args[i]
      instream = OPEN(template_file_path, 'r')
      # Tokenize each line with a white space
      values = csv.DictReader(instream, Fields, delimiter=" ")

      for values in itertools.izip(values):
         new_entry = values[0]
         # Verify the parsed tokens
         if (new_entry['Define'] == '#define') and \
            (new_entry['Key'] != None) and \
            (new_entry['Value'] != None):

            new_key   = new_entry['Key'].strip()
            new_value = new_entry['Value'].strip()

            # If value pair is empty string, assume feature definition is true
            if new_value == '':
               new_value = 'yes'

            # Check for and handle text replacements as we parse
            if global_dict is not None and len(global_dict.keys()) > 0:
               for key in global_dict:
                  new_value = new_value.replace(key, str(global_dict.get(key)))

            # Attempt to evaluate value
            try:
               new_value = eval(new_value)
            # Catch exceptions and do not evaluate
            except:
               pass

            # Add to global dictionary
            global_dict[new_key] = new_value
      instream.close()

   return global_dict

#----------------------------------------------------------------------------
# Filter out a generic dictionary from the global dictionary
#----------------------------------------------------------------------------
def filter_dictionary(env, global_dict, **kwargs):

   # Check for Image Type
   # If IMAGE_TYPE parameter is not provided, raise error
   if not kwargs.has_key('IMAGE_TYPE'):
      raise RuntimeError("IMAGE_TYPE must be defined to use FilterDictionary.")
   else:
      image_type = kwargs.get('IMAGE_TYPE')
      if type(image_type) is not str:
         raise RuntimeError("IMAGE_TYPE must be of string type.")

   # Check for Flash Type
   # If FLASH_TYPE parameter is not provided, default to 'nand'
   if not kwargs.has_key('FLASH_TYPE'):
      flash_type = 'nand'
   else:
      flash_type = kwargs.get('FLASH_TYPE')
      if type(flash_type) is not str:
         raise RuntimeError("FLASH_TYPE must be of string type. ")

   # Check for MBN Type
   # If MBN_TYPE parameter is not provided, default to 'elf'
   if not kwargs.has_key('MBN_TYPE'):
      mbn_type = 'elf'
   else:
      mbn_type = kwargs.get('MBN_TYPE')
      if mbn_type != 'elf' and mbn_type != 'bin':
         raise RuntimeError("MBN_TYPE currently not supported: " + mbn_type)

   # Check for Image ID
   # If IMAGE_ID parameter is not provided, default to ID 0
   if not kwargs.has_key('IMAGE_ID'):
      image_id = ImageType.NONE_IMG
   else:
      image_id = kwargs.get('IMAGE_ID')
      if type(image_id) is not int:
         raise RuntimeError("IMAGE_ID must be of integer type.")

   # Initialize
   gen_dict = {}
   image_dest = 0
   image_source = 0

   # Check for image_type
   if image_type not in image_id_table:
      id = image_id
      id_match_str = image_type.upper() + "_IMG"
      id_mbn_type = mbn_type
   else:
      id = image_id_table[image_type][0]
      id_match_str = image_id_table[image_type][1]
      id_mbn_type = image_id_table[image_type][2]

   # Handle MBN Type and assign image destination address
   if id_mbn_type is 'elf':
      pass
   elif id_mbn_type is 'bin':
      template_key_match = 'IMAGE_KEY_' + id_match_str + "_DEST_ADDR"
      if template_key_match in global_dict:
         image_dest = global_dict[template_key_match]
      else:
         raise RuntimeError("Builds file does not have IMAGE_KEY pair for: " + image_type)
   else:
      raise RuntimeError("MBN_TYPE currently not supported: " + mbn_type)

   # Assign generic dictionary key/value pairs
   gen_dict['IMAGE_KEY_IMAGE_ID'] = id
   gen_dict['IMAGE_KEY_IMAGE_DEST'] = image_dest
   gen_dict['IMAGE_KEY_IMAGE_SOURCE'] = image_source
   gen_dict['IMAGE_KEY_FLASH_TYPE'] = flash_type
   gen_dict['IMAGE_KEY_MBN_TYPE'] = id_mbn_type
   gen_dict['IMAGE_KEY_ID_MATCH_STR'] = id_match_str
   gen_dict['IMAGE_KEY_FLASH_AUTO_DETECT_MAX_PAGE'] = \
      get_dict_value(global_dict,'FLASH_AUTO_DETECT_MAX_PAGE', 8192)
   gen_dict['IMAGE_KEY_FLASH_AUTO_DETECT_MIN_PAGE'] = \
      get_dict_value(global_dict,'FLASH_AUTO_DETECT_MIN_PAGE', 2048)
   gen_dict['IMAGE_KEY_MAX_SIZE_OF_VERIFY_BUFFER'] = \
      get_dict_value(global_dict,'MAX_SIZE_OF_VERIFY_BUFFER', 8192)
   gen_dict['IMAGE_KEY_BOOT_SMALL_PREAMBLE'] = \
      get_dict_value(global_dict,'BOOT_SMALL_PREAMBLE', 1)

   # Get OEM root certificate select and number
   oem_root_cert_sel = get_dict_value(global_dict,'OEM_ROOT_CERT_SEL', 1)
   oem_num_root_certs = get_dict_value(global_dict,'OEM_NUM_ROOT_CERTS', 1)

   # Error checking for OEM configurable values
   if oem_root_cert_sel in range(1, MAX_NUM_ROOT_CERTS + 1) and \
      oem_num_root_certs in range(1, MAX_NUM_ROOT_CERTS + 1) and \
      oem_root_cert_sel <= oem_num_root_certs:

      gen_dict['IMAGE_KEY_OEM_ROOT_CERT_SEL'] = oem_root_cert_sel
      gen_dict['IMAGE_KEY_OEM_NUM_ROOT_CERTS'] = oem_num_root_certs

   else:
      raise RuntimeError("Invalid OEM root certificate configuration values")

   # Assign additional dictionary key/values pair as needed by tools.

   return gen_dict


#----------------------------------------------------------------------------
# Get index value from dictionary if exists, otherwise return default
#----------------------------------------------------------------------------
def get_dict_value(dict, key_string, default):

   key = 'IMAGE_KEY_' + key_string

   if key in dict:
      return dict[key]
   else:
      return default

#----------------------------------------------------------------------------
# Preprocess an ELF file and return the ELF Header Object and an
# array of ELF Program Header Objects
#----------------------------------------------------------------------------
def preprocess_elf_file(elf_file_name):

   # Initialize
   elf_fp = OPEN(elf_file_name, 'rb')
   elf_header = Elf_Ehdr_common(elf_fp.read(ELF_HDR_COMMON_SIZE))

   if verify_elf_header(elf_header) is False:
      raise RuntimeError("ELF file failed verification: " + elf_file_name)

   elf_fp.seek(0)

   if elf_header.e_ident[ELFINFO_CLASS_INDEX] == ELFINFO_CLASS_64:
      elf_header = Elf64_Ehdr(elf_fp.read(ELF64_HDR_SIZE))
   else:
      elf_header = Elf32_Ehdr(elf_fp.read(ELF32_HDR_SIZE))

   phdr_table = []

   # Verify ELF header information
   if verify_elf_header(elf_header) is False:
      raise RuntimeError("ELF file failed verification: " + elf_file_name)

   # Get program header size
   phdr_size = elf_header.e_phentsize

   # Find the program header offset
   file_offset = elf_header.e_phoff
   elf_fp.seek(file_offset)

   # Read in the program headers
   for i in range(elf_header.e_phnum):
      if elf_header.e_ident[ELFINFO_CLASS_INDEX] == ELFINFO_CLASS_64:
         phdr_table.append(Elf64_Phdr(elf_fp.read(phdr_size)))
      else:
         phdr_table.append(Elf32_Phdr(elf_fp.read(phdr_size)))

   elf_fp.close()
   return [elf_header, phdr_table]

#----------------------------------------------------------------------------
# Get the hash table address from an input ELF file
#----------------------------------------------------------------------------
def get_hash_address(elf_file_name):

   [elf_header, phdr_table] = preprocess_elf_file(elf_file_name)

   last_paddr = 0
   last_paddr_segment = 0

   # Find the segment with the largest physical address.
   # Hash segment's physical address will be immediately after this segment.
   for i in range(elf_header.e_phnum):
      curr_phdr = phdr_table[i]
      if curr_phdr.p_paddr > last_paddr:
         # Skip the demand paging segment as it would be outside the physical RAM location
         if MI_PBT_SEGMENT_TYPE_VALUE(curr_phdr.p_flags) != MI_PBT_XBL_SEC_SEGMENT:
            last_paddr = curr_phdr.p_paddr;
            last_paddr_segment = i;

   max_phdr = phdr_table[last_paddr_segment]

   ret_val  = (((max_phdr.p_paddr + max_phdr.p_memsz - 1) & \
              ~(ELF_BLOCK_ALIGN-1)) + ELF_BLOCK_ALIGN)

   return ret_val

#----------------------------------------------------------------------------
# Verify ELF header contents from an input ELF file
#----------------------------------------------------------------------------
def verify_elf_header(elf_header):
   if (elf_header.e_ident[ELFINFO_MAG0_INDEX] != ELFINFO_MAG0):
      print("MAG0[{:d}]\n".format((elf_header.e_ident[ELFINFO_MAG0_INDEX])))
      return False
   if (elf_header.e_ident[ELFINFO_MAG1_INDEX] != ELFINFO_MAG1):
      print("MAG1[{:d}]\n".format((elf_header.e_ident[ELFINFO_MAG1_INDEX])))
      return False
   if (elf_header.e_ident[ELFINFO_MAG2_INDEX] != ELFINFO_MAG2):
      print("MAG2[{:d}]\n".format((elf_header.e_ident[ELFINFO_MAG2_INDEX])))
      return False
   if (elf_header.e_ident[ELFINFO_MAG3_INDEX] != ELFINFO_MAG3):
      print("MAG3[{:d}]\n".format((elf_header.e_ident[ELFINFO_MAG3_INDEX])))
      return False
   if ((elf_header.e_ident[ELFINFO_CLASS_INDEX] != ELFINFO_CLASS_64) and \
       (elf_header.e_ident[ELFINFO_CLASS_INDEX] != ELFINFO_CLASS_32)):
      print("ELFINFO_CLASS_INDEX[{:d}]\n".format((elf_header.e_ident[ELFINFO_CLASS_INDEX])))
      return False
   if (elf_header.e_ident[ELFINFO_VERSION_INDEX] != ELFINFO_VERSION_CURRENT):
      print("ELFINFO_VERSION_INDEX[{:d}]\n".format((elf_header.e_ident[ELFINFO_VERSION_INDEX])))
      return False
   return True

#----------------------------------------------------------------------------
# Perform file copy given offsets and the number of bytes to copy
#----------------------------------------------------------------------------
def file_copy_offset(in_fp, in_off, out_fp, out_off, num_bytes):
   in_fp.seek(in_off)
   read_in = in_fp.read(num_bytes)
   out_fp.seek(out_off)
   out_fp.write(read_in)

   return num_bytes

#----------------------------------------------------------------------------
# sha1/sha256 hash routine wrapper
#----------------------------------------------------------------------------
def header_size(header_version):
    if header_version >= 6:
        return 48
    else:
        return 40

#----------------------------------------------------------------------------
# sha1/sha256 hash routine wrapper
#----------------------------------------------------------------------------
def generate_hash(in_buf, sha_algo):
   # Initialize a SHA1 object from the Python hash library
   if sha_algo == 'SHA384':
      m = hashlib.sha384()
   elif sha_algo == 'SHA256':
      m = hashlib.sha256()
   else:
      m = hashlib.sha1()

   # Set the input buffer and return the output digest
   m.update(in_buf)
   return m.digest()

#----------------------------------------------------------------------------
# Initialize the hash program header.
#----------------------------------------------------------------------------
def initialize_hash_phdr(elf_in_file_name, hash_tbl_size, hdr_size, hdr_offset, is_elf64):
   # Set hash header offset to page size boundary. Hash table will be
   # located at first segment of elf image.
   hash_hdr_size =  hdr_size
   hash_hdr_offset = hdr_offset
   hash_tbl_offset = hash_hdr_offset + hash_hdr_size
   hash_tbl_end_addr = hash_tbl_offset + hash_tbl_size;
   pad_hash_segment = (hash_tbl_end_addr) & (ELF_BLOCK_ALIGN-1)

   # Update the hash table program header
   if is_elf64 is True:
      hash_Phdr = Elf64_Phdr(b'\0'*ELF64_PHDR_SIZE)
   else:
      hash_Phdr = Elf32_Phdr(b'\0'*ELF32_PHDR_SIZE)
   hash_Phdr.p_flags = MI_PBT_ELF_HASH_SEGMENT
   hash_Phdr.p_align = ELF_BLOCK_ALIGN
   hash_Phdr.p_offset = hash_hdr_offset
   hash_Phdr.p_memsz = hash_hdr_size + hash_tbl_size + (ELF_BLOCK_ALIGN - pad_hash_segment)
   hash_Phdr.p_filesz = hash_hdr_size + hash_tbl_size
   hash_Phdr.p_type = NULL_TYPE
   hash_Phdr.p_vaddr = get_hash_address(elf_in_file_name)
   hash_Phdr.p_paddr = hash_Phdr.p_vaddr

   return [hash_Phdr, pad_hash_segment, hash_tbl_end_addr, hash_tbl_offset]

#----------------------------------------------------------------------------
# image_preamble
#----------------------------------------------------------------------------
def image_preamble(gen_dict, preamble_file_name, boot_sbl_header, num_of_pages=None):
   # Generate the preamble file
   preamble_fp = OPEN(preamble_file_name, 'wb')

   # Initialize
   max_size_verify = gen_dict['IMAGE_KEY_MAX_SIZE_OF_VERIFY_BUFFER']
   flash_max_page = gen_dict['IMAGE_KEY_FLASH_AUTO_DETECT_MAX_PAGE']
   flash_min_page = gen_dict['IMAGE_KEY_FLASH_AUTO_DETECT_MIN_PAGE']
   autodetectpage = [int('0xFFFFFFFF',16)] * max_size_verify

   # The first three entries in the preamble must include the following values
   autodetectpage[0] = FLASH_CODE_WORD
   autodetectpage[1] = MAGIC_NUM
   if (num_of_pages == 64):
     autodetectpage[2] = AUTODETECT_PAGE_SIZE_MAGIC_NUM64
   elif (num_of_pages == 128):
     autodetectpage[2] = AUTODETECT_PAGE_SIZE_MAGIC_NUM128
   else:
     autodetectpage[2] = AUTODETECT_PAGE_SIZE_MAGIC_NUM

   # Package the list into binary data to be written to the preamble
   s = struct.Struct('I' * max_size_verify)
   packed_data = s.pack(*autodetectpage)

   # Output preamble pages based on maximum/minimum page size support
   for i in range(flash_max_page/flash_min_page):
      preamble_fp.write(packed_data[:flash_min_page])

   # Determine appropriate amount of padding for the preamble and
   # update the boot_sbl_header accordingly
   if gen_dict['IMAGE_KEY_BOOT_SMALL_PREAMBLE'] == 1:
      boot_sbl_header.image_src += (flash_max_page + flash_min_page)
      amount_to_write = flash_min_page
   else:
      boot_sbl_header.image_src += flash_max_page * 2
      amount_to_write = flash_max_page

   pad_file(preamble_fp, amount_to_write, PAD_BYTE_1)
   preamble_fp.close()

   return boot_sbl_header

#----------------------------------------------------------------------------
# Helper functions to parse ELF program headers
#----------------------------------------------------------------------------
def MI_PBT_SEGMENT_TYPE_VALUE(x):
    return ( ((x) & MI_PBT_FLAG_SEGMENT_TYPE_MASK) >> MI_PBT_FLAG_SEGMENT_TYPE_SHIFT )

def MI_PBT_PAGE_MODE_VALUE(x):
    return ( ((x) & MI_PBT_FLAG_PAGE_MODE_MASK) >> MI_PBT_FLAG_PAGE_MODE_SHIFT )

def MI_PBT_ACCESS_TYPE_VALUE(x):
    return ( ((x) & MI_PBT_FLAG_ACCESS_TYPE_MASK) >> MI_PBT_FLAG_ACCESS_TYPE_SHIFT )

def MI_PBT_CHECK_FLAG_TYPE(x):
    return (MI_PBT_SEGMENT_TYPE_VALUE(x) != MI_PBT_HASH_SEGMENT) and \
           (MI_PBT_ACCESS_TYPE_VALUE(x) != MI_PBT_NOTUSED_SEGMENT) and \
           (MI_PBT_ACCESS_TYPE_VALUE(x) != MI_PBT_SHARED_SEGMENT)


#----------------------------------------------------------------------------
# Helper functions to open a file and return a valid file object
#----------------------------------------------------------------------------
def OPEN(file_name, mode):
    try:
       fp = open(file_name, mode)
    except IOError:
       raise RuntimeError("The file could not be opened: " + file_name)

    # File open has succeeded with the given mode, return the file object
    return fp

#----------------------------------------------------------------------------
# Helper functions to insert MCs in SBL1(Badger) if ENABLE_VIRTUAL_BLK is ON
#----------------------------------------------------------------------------
def insert_SBL1_magicCookie (env, target):
    file = open(target, "rb")
    #read the file contents
    filedata = file.read()
    length = len(filedata)
    file.close()

    if (length <= VIRTUAL_BLOCK_SIZE):
        return None
    else:
        #remove the previous file
        os.remove(target)
        #generate new file for appending target data + required MCs
        file = open(target, "ab")

        while length > VIRTUAL_BLOCK_SIZE:
            filedata_till_128kb = filedata[0:VIRTUAL_BLOCK_SIZE]
            filedata_after_128kb = filedata[VIRTUAL_BLOCK_SIZE:length]

            a = str(hex(FLASH_CODE_WORD))
            mc1 = chr(int(a[8:10],16)) + chr(int(a[6:8],16)) + chr(int(a[4:6],16)) + chr(int(a[2:4],16))

            b = str(hex(MAGIC_NUM))
            mc2 = chr(int(b[8:10],16)) + chr(int(b[6:8],16)) + chr(int(b[4:6],16)) + chr(int(b[2:4],16))

            c = str(hex(SBL_VIRTUAL_BLOCK_MAGIC_NUM))
            mc3 = chr(int(c[8:10],16)) + chr(int(c[6:8],16)) + chr(int(c[4:6],16)) + chr(int(c[2:4],16))

            MC_inserted_data = filedata_till_128kb + mc1 + mc2 + mc3
            file.write(MC_inserted_data)

            filedata = filedata_after_128kb
            length = len(filedata)

        #copy the leftover data (<128KB) in output file
        if length > 0:
            file.write(filedata)

        #close the final output file
        file.close()
    # MC_insertion code end

#----------------------------------------------------------------------------
# Helper functions to remove MCs in SBL1(Badger)
#----------------------------------------------------------------------------
def remove_SBL1_magicCookie (env, target, dest):
    file = open(target, "rb")
    #read the file contents
    filedata = file.read()
    length = len(filedata)
    file.close()

    #generate new file for appending target data + required MCs
    file = open(dest, "ab")

    while length > VIRTUAL_BLOCK_SIZE:
        filedata_till_128kb = filedata[0:VIRTUAL_BLOCK_SIZE]
        # skipped 12 byte of Virtual Block Magic Cookie Header
        filedata_after_128kb = filedata[VIRTUAL_BLOCK_SIZE+MAGIC_COOKIE_LENGTH:length]

        file.write(filedata_till_128kb)

        filedata = filedata_after_128kb
        length = len(filedata)

    #copy the leftover data (<128KB) in output file
    if length > 0:
        file.write(filedata)

    #close the final output file
    file.close()

    # MC_removal code end

#----------------------------------------------------------------------------
# Helper functions to pad SBL1 image
# min_size defaults to 256k
# If page_size or num_of_pages is set to 0, the variable is unset
#----------------------------------------------------------------------------
def pad_SBL1_image (env, target, min_size_with_pad=MIN_IMAGE_SIZE_WITH_PAD, page_size=0, num_of_pages=0):
    file = open(target, "rb")
    #read the file contents
    filedata = file.read()
    length = len(filedata)
    file.close()

    multiple = 1
    alignment = page_size * num_of_pages

    if (length > alignment and alignment > 0):
      import math
      multiple = math.ceil(length/float(alignment))

    final_image_size = max(min_size_with_pad, multiple * alignment)

    if length < final_image_size:
      sbl1_fp = open(target, 'ab')
      pad_file (sbl1_fp, (final_image_size-length), PAD_BYTE_0)
      sbl1_fp.close()

   # SBL1 pad code end
#----------------------------------------------------------------------------
# HELPER FUNCTIONS END
#----------------------------------------------------------------------------
