## @file
#
# Copyright (c) 2011 - 2018, Intel Corporation. All rights reserved.<BR>
#
# SPDX-License-Identifier: BSD-2-Clause-Patent
#

from __future__ import print_function
import array
import uuid
import re
import os
import logging
import core.pe as pe

def GetLogger():
    return logging.getLogger('EFI Binary File')

class EFIBinaryError(Exception):
    def __init__(self, message):
        Exception.__init__(self)
        self._message = message

    def GetMessage(self):
        return self._message

class EfiFd(object):
    EFI_FV_HEADER_SIZE = 0x48

    def __init__(self):
        self._fvs = []

    def Load(self, fd, size):
        index = fd.tell()
        while (index + self.EFI_FV_HEADER_SIZE < size):
            fv = EfiFv(self)
            fv.Load(fd)
            self._fvs.append(fv)
            index += fv.GetHeader().GetFvLength()
            index = align(index, 8)
            fd.seek(index)

    def GetFvs(self):
        return self._fvs

class EfiFv(object):
    FILE_SYSTEM_GUID = uuid.UUID('{8c8ce578-8a3d-4f1c-9935-896185c32dd3}')

    def __init__(self, parent=None):
        self._size         = 0
        self._filename     = None
        self._fvheader     = None
        self._blockentries = []
        self._ffs          = []

        # following field is for FV in FD
        self._parent       = parent
        self._offset       = 0
        self._raw          = array.array('B')

    def Load(self, fd):
        self._offset   = fd.tell()
        self._filename = fd.name

        # get file header
        self._fvheader = EfiFirmwareVolumeHeader.Read(fd)
        #self._fvheader.Dump()

        self._size = self._fvheader.GetFvLength()

        if self._fvheader.GetFileSystemGuid() != self.FILE_SYSTEM_GUID:
            fd.seek(self._offset)
            self._raw.fromfile(fd, self.GetHeader().GetFvLength())
            return

        # read block map
        blockentry = BlockMapEntry.Read(fd)
        self._blockentries.append(blockentry)
        while (blockentry.GetNumberBlocks() != 0 and blockentry.GetLength() != 0):
            self._blockentries.append(blockentry)
            blockentry = BlockMapEntry.Read(fd)


        if self._fvheader.GetSize() + (len(self._blockentries)) * 8 != \
           self._fvheader.GetHeaderLength():
            raise EFIBinaryError("Volume Header length not consistent with block map!")

        index = align(fd.tell(), 8)
        count = 0
        while ((index + EfiFfs.FFS_HEADER_SIZE) < self._size):
            ffs = EfiFfs.Read(fd, self)
            if not isValidGuid(ffs.GetNameGuid()):
                break
            self._ffs.append(ffs)
            count += 1
            index = align(fd.tell(), 8)

        fd.seek(self._offset)
        self._raw.fromfile(fd, self.GetHeader().GetFvLength())

    def GetFfs(self):
        return self._ffs

    def GetHeader(self):
        return self._fvheader

    def GetBlockEntries(self):
        return self._blockentries

    def GetHeaderRawData(self):
        ret = []
        ret += self._fvheader.GetRawData()
        for block in self._blockentries:
            ret += block.GetRawData()
        return ret

    def GetOffset(self):
        return 0

    def GetRawData(self):
        return self._raw.tolist()

class BinaryItem(object):
    def __init__(self, parent=None):
        self._size = 0
        self._arr  = array.array('B')
        self._parent = parent

    @classmethod
    def Read(cls, fd, parent=None):
        item = cls(parent)
        item.fromfile(fd)
        return item

    def Load(self, fd):
        self.fromfile(fd)

    def GetSize(self):
        """should be implemented by inherited class"""

    def fromfile(self, fd):
        self._arr.fromfile(fd, self.GetSize())

    def GetParent(self):
        return self._parent

class EfiFirmwareVolumeHeader(BinaryItem):
    def GetSize(self):
        return 56

    def GetSigunature(self):
        list = self._arr.tolist()
        sig = ''
        for x in list[40:44]:
            sig += chr(x)
        return sig

    def GetAttribute(self):
        return list2int(self._arr.tolist()[44:48])

    def GetErasePolarity(self):
        list = self.GetAttrStrings()
        if 'EFI_FVB2_ERASE_POLARITY' in list:
            return True
        return False

    def GetAttrStrings(self):
        list = []
        value = self.GetAttribute()
        if (value & 0x01) != 0:
            list.append('EFI_FVB2_READ_DISABLED_CAP')
        if (value & 0x02) != 0:
            list.append('EFI_FVB2_READ_ENABLED_CAP')
        if (value & 0x04) != 0:
            list.append('EFI_FVB2_READ_STATUS')
        if (value & 0x08) != 0:
            list.append('EFI_FVB2_WRITE_DISABLED_CAP')
        if (value & 0x10) != 0:
            list.append('EFI_FVB2_WRITE_ENABLED_CAP')
        if (value & 0x20) != 0:
            list.append('EFI_FVB2_WRITE_STATUS')
        if (value & 0x40) != 0:
            list.append('EFI_FVB2_LOCK_CAP')
        if (value & 0x80) != 0:
            list.append('EFI_FVB2_LOCK_STATUS')
        if (value & 0x200) != 0:
            list.append('EFI_FVB2_STICKY_WRITE')
        if (value & 0x400) != 0:
            list.append('EFI_FVB2_MEMORY_MAPPED')
        if (value & 0x800) != 0:
            list.append('EFI_FVB2_ERASE_POLARITY')
        if (value & 0x1000) != 0:
            list.append('EFI_FVB2_READ_LOCK_CAP')
        if (value & 0x00002000) != 0:
            list.append('EFI_FVB2_READ_LOCK_STATUS')
        if (value & 0x00004000) != 0:
            list.append('EFI_FVB2_WRITE_LOCK_CAP')
        if (value & 0x00008000) != 0:
            list.append('EFI_FVB2_WRITE_LOCK_STATUS')

        if (value == 0):
            list.append('EFI_FVB2_ALIGNMENT_1')
        if (value & 0x001F0000) == 0x00010000:
            list.append('EFI_FVB2_ALIGNMENT_2')
        if (value & 0x001F0000) == 0x00020000:
            list.append('EFI_FVB2_ALIGNMENT_4')
        if (value & 0x001F0000) == 0x00030000:
            list.append('EFI_FVB2_ALIGNMENT_8')
        if (value & 0x001F0000) == 0x00040000:
            list.append('EFI_FVB2_ALIGNMENT_16')
        if (value & 0x001F0000) == 0x00050000:
            list.append('EFI_FVB2_ALIGNMENT_32')
        if (value & 0x001F0000) == 0x00060000:
            list.append('EFI_FVB2_ALIGNMENT_64')
        if (value & 0x001F0000) == 0x00070000:
            list.append('EFI_FVB2_ALIGNMENT_128')
        if (value & 0x001F0000) == 0x00080000:
            list.append('EFI_FVB2_ALIGNMENT_256')
        if (value & 0x001F0000) == 0x00090000:
            list.append('EFI_FVB2_ALIGNMENT_512')
        if (value & 0x001F0000) == 0x000A0000:
            list.append('EFI_FVB2_ALIGNMENT_1K')
        if (value & 0x001F0000) == 0x000B0000:
            list.append('EFI_FVB2_ALIGNMENT_2K')
        if (value & 0x001F0000) == 0x000C0000:
            list.append('EFI_FVB2_ALIGNMENT_4K')
        if (value & 0x001F0000) == 0x000D0000:
            list.append('EFI_FVB2_ALIGNMENT_8K')
        if (value & 0x001F0000) == 0x000E0000:
            list.append('EFI_FVB2_ALIGNMENT_16K')
        if (value & 0x001F0000) == 0x000F0000:
            list.append('EFI_FVB2_ALIGNMENT_32K')
        if (value & 0x001F0000) == 0x00100000:
            list.append('EFI_FVB2_ALIGNMENT_64K')
        if (value & 0x001F0000) == 0x00110000:
            list.append('EFI_FVB2_ALIGNMENT_128K')
        if (value & 0x001F0000) == 0x00120000:
            list.append('EFI_FVB2_ALIGNMENT_256K')
        if (value & 0x001F0000) == 0x00130000:
            list.append('EFI_FVB2_ALIGNMENT_512K')

        return list

    def GetHeaderLength(self):
        return list2int(self._arr.tolist()[48:50])

    def Dump(self):
        print('Signature: %s' % self.GetSigunature())
        print('Attribute: 0x%X' % self.GetAttribute())
        print('Header Length: 0x%X' % self.GetHeaderLength())
        print('File system Guid: ', self.GetFileSystemGuid())
        print('Revision: 0x%X' % self.GetRevision())
        print('FvLength: 0x%X' % self.GetFvLength())

    def GetFileSystemGuid(self):
        list = self._arr.tolist()
        return list2guid(list[16:32])

    def GetRevision(self):
        list = self._arr.tolist()
        return int(list[55])

    def GetFvLength(self):
        list = self._arr.tolist()
        return list2int(list[32:40])

    def GetRawData(self):
        return self._arr.tolist()

class BlockMapEntry(BinaryItem):
    def GetSize(self):
        return 8

    def GetNumberBlocks(self):
        list = self._arr.tolist()
        return list2int(list[0:4])

    def GetLength(self):
        list = self._arr.tolist()
        return list2int(list[4:8])

    def GetRawData(self):
        return self._arr.tolist()

    def __str__(self):
        return '[BlockEntry] Number = 0x%X, length=0x%X' % (self.GetNumberBlocks(), self.GetLength())

class EfiFfs(object):
    FFS_HEADER_SIZE  = 24

    def __init__(self, parent=None):
        self._header = None

        # following field is for FFS in FV file.
        self._parent  = parent
        self._offset  = 0
        self._sections = []

    def Load(self, fd):
        self._offset = align(fd.tell(), 8)

        self._header = EfiFfsHeader.Read(fd, self)

        if not isValidGuid(self.GetNameGuid()):
            return

        index = self._offset
        fileend = self._offset + self.GetSize()
        while (index + EfiSection.EFI_SECTION_HEADER_SIZE < fileend):
            section = EfiSection(self)
            section.Load(fd)
            if section.GetSize() == 0 and section.GetHeader().GetType() == 0:
                break
            self._sections.append(section)
            index = fd.tell()

        # rebase file pointer to next ffs file
        index = self._offset + self._header.GetFfsSize()
        index = align(index, 8)
        fd.seek(index)

    def GetOffset(self):
        return self._offset

    def GetSize(self):
        return self._header.GetFfsSize()

    @classmethod
    def Read(cls, fd, parent=None):
        item = cls(parent)
        item.Load(fd)
        return item

    def GetNameGuid(self):
        return self._header.GetNameGuid()

    def DumpContent(self):
        list  = self._content.tolist()
        line  = []
        count = 0
        for item in list:
            if count < 32:
                line.append('0x%X' % int(item))
                count += 1
            else:
                print(' '.join(line))
                count = 0
                line = []
                line.append('0x%X' % int(item))
                count += 1

    def GetHeader(self):
        return self._header

    def GetParent(self):
        return self._parent

    def GetSections(self):
        return self._sections

class EfiFfsHeader(BinaryItem):
    ffs_state_map = {0x01:'EFI_FILE_HEADER_CONSTRUCTION',
                     0x02:'EFI_FILE_HEADER_VALID',
                     0x04:'EFI_FILE_DATA_VALID',
                     0x08:'EFI_FILE_MARKED_FOR_UPDATE',
                     0x10:'EFI_FILE_DELETED',
                     0x20:'EFI_FILE_HEADER_INVALID'}

    def GetSize(self):
        return 24

    def GetNameGuid(self):
        list = self._arr.tolist()
        return list2guid(list[0:16])

    def GetType(self):
        list = self._arr.tolist()
        return int(list[18])


    def GetTypeString(self):
        value = self.GetType()
        if value == 0x01:
            return 'EFI_FV_FILETYPE_RAW'
        if value == 0x02:
            return 'EFI_FV_FILETYPE_FREEFORM'
        if value == 0x03:
            return 'EFI_FV_FILETYPE_SECURITY_CORE'
        if value == 0x04:
            return 'EFI_FV_FILETYPE_PEI_CORE'
        if value == 0x05:
            return 'EFI_FV_FILETYPE_DXE_CORE'
        if value == 0x06:
            return 'EFI_FV_FILETYPE_PEIM'
        if value == 0x07:
            return 'EFI_FV_FILETYPE_DRIVER'
        if value == 0x08:
            return 'EFI_FV_FILETYPE_COMBINED_PEIM_DRIVER'
        if value == 0x09:
            return 'EFI_FV_FILETYPE_APPLICATION'
        if value == 0x0B:
            return 'EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE'
        if value == 0xc0:
            return 'EFI_FV_FILETYPE_OEM_MIN'
        if value == 0xdf:
            return 'EFI_FV_FILETYPE_OEM_MAX'
        if value == 0xe0:
            return 'EFI_FV_FILETYPE_DEBUG_MIN'
        if value == 0xef:
            return 'EFI_FV_FILETYPE_DEBUG_MAX'
        if value == 0xf0:
            return 'EFI_FV_FILETYPE_FFS_PAD'
        if value == 0xff:
            return 'EFI_FV_FILETYPE_FFS_MAX'
        return 'Unknown FFS Type'

    def GetAttributes(self):
        list = self._arr.tolist()
        return int(list[19])

    def GetFfsSize(self):
        list = self._arr.tolist()
        return list2int(list[20:23])

    def GetState(self):
        list = self._arr.tolist()
        state = int(list[23])
        polarity = self.GetParent().GetParent().GetHeader().GetErasePolarity()
        if polarity:
            state = (~state) & 0xFF
        HighestBit = 0x80
        while (HighestBit != 0) and (HighestBit & state) == 0:
            HighestBit = HighestBit >> 1
        return HighestBit

    def GetStateString(self):
        state = self.GetState()
        if state in self.ffs_state_map.keys():
            return self.ffs_state_map[state]
        return 'Unknown Ffs State'

    def Dump(self):
        print("FFS name: ", self.GetNameGuid())
        print("FFS type: ", self.GetType())
        print("FFS attr: 0x%X" % self.GetAttributes())
        print("FFS size: 0x%X" % self.GetFfsSize())
        print("FFS state: 0x%X" % self.GetState())

    def GetRawData(self):
        return self._arr.tolist()


class EfiSection(object):
    EFI_SECTION_HEADER_SIZE = 4

    def __init__(self, parent=None):
        self._size   = 0
        self._parent = parent
        self._offset = 0
        self._contents = array.array('B')

    def Load(self, fd):
        self._offset = align(fd.tell(), 4)

        self._header = EfiSectionHeader.Read(fd, self)

        if self._header.GetTypeString() == "EFI_SECTION_PE32":
             pefile = pe.PEFile(self)
             pefile.Load(fd, self.GetContentSize())

        fd.seek(self._offset)
        self._contents.fromfile(fd, self.GetContentSize())

        # rebase file pointer to next section
        index = self._offset + self.GetSize()
        index = align(index, 4)
        fd.seek(index)

    def GetContentSize(self):
        return self.GetSize() - self.EFI_SECTION_HEADER_SIZE

    def GetContent(self):
        return self._contents.tolist()

    def GetSize(self):
        return self._header.GetSectionSize()

    def GetHeader(self):
        return self._header

    def GetSectionOffset(self):
        return self._offset + self.EFI_SECTION_HEADER_SIZE

class EfiSectionHeader(BinaryItem):
    section_type_map = {0x01: 'EFI_SECTION_COMPRESSION',
                        0x02: 'EFI_SECTION_GUID_DEFINED',
                        0x10: 'EFI_SECTION_PE32',
                        0x11: 'EFI_SECTION_PIC',
                        0x12: 'EFI_SECTION_TE',
                        0x13: 'EFI_SECTION_DXE_DEPEX',
                        0x14: 'EFI_SECTION_VERSION',
                        0x15: 'EFI_SECTION_USER_INTERFACE',
                        0x16: 'EFI_SECTION_COMPATIBILITY16',
                        0x17: 'EFI_SECTION_FIRMWARE_VOLUME_IMAGE',
                        0x18: 'EFI_SECTION_FREEFORM_SUBTYPE_GUID',
                        0x19: 'EFI_SECTION_RAW',
                        0x1B: 'EFI_SECTION_PEI_DEPEX'}
    def GetSize(self):
        return 4

    def GetSectionSize(self):
        list = self._arr.tolist()
        return list2int(list[0:3])

    def GetType(self):
        list = self._arr.tolist()
        return int(list[3])

    def GetTypeString(self):
        type = self.GetType()
        if type not in self.section_type_map.keys():
            return 'Unknown Section Type'
        return self.section_type_map[type]

    def Dump(self):
        print('size = 0x%X' % self.GetSectionSize())
        print('type = 0x%X' % self.GetType())



rMapEntry = re.compile('^(\w+)[ \(\w\)]* \(BaseAddress=([0-9a-fA-F]+), EntryPoint=([0-9a-fA-F]+), GUID=([0-9a-fA-F\-]+)')
class EfiFvMapFile(object):
    def __init__(self):
        self._mapentries = {}

    def Load(self, path):
        if not os.path.exists(path):
            return False

        try:
            file = open(path, 'r')
            lines = file.readlines()
            file.close()
        except:
            return False

        for line in lines:
            if line[0] != ' ':
                # new entry
                ret = rMapEntry.match(line)
                if ret is not None:
                    name     = ret.groups()[0]
                    baseaddr = int(ret.groups()[1], 16)
                    entry    = int(ret.groups()[2], 16)
                    guidstr  = '{' + ret.groups()[3] + '}'
                    guid     = uuid.UUID(guidstr)
                    self._mapentries[guid] = EfiFvMapFileEntry(name, baseaddr, entry, guid)
        return True

    def GetEntry(self, guid):
        if guid in self._mapentries.keys():
            return self._mapentries[guid]
        return None

class EfiFvMapFileEntry(object):
    def __init__(self, name, baseaddr, entry, guid):
        self._name     = name
        self._baseaddr = baseaddr
        self._entry    = entry
        self._guid     = guid

    def GetName(self):
        return self._name

    def GetBaseAddress(self):
        return self._baseaddr

    def GetEntryPoint(self):
        return self._entry

def list2guid(list):
    val1 = list2int(list[0:4])
    val2 = list2int(list[4:6])
    val3 = list2int(list[6:8])
    val4 = 0
    for item in list[8:16]:
        val4 = (val4 << 8) | int(item)

    val  = val1 << 12 * 8 | val2 << 10 * 8 | val3 << 8 * 8 | val4
    guid = uuid.UUID(int=val)
    return guid

def list2int(list):
    val = 0
    for index in range(len(list) - 1, -1, -1):
        val = (val << 8) | int(list[index])
    return val

def align(value, alignment):
    return (value + ((alignment - value) & (alignment - 1)))

gInvalidGuid = uuid.UUID(int=0xffffffffffffffffffffffffffffffff)
def isValidGuid(guid):
    if guid == gInvalidGuid:
        return False
    return True
