## @file
# Module that encodes and decodes a EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER with
# a payload.
#
# Copyright (c) 2018 - 2019, Intel Corporation. All rights reserved.<BR>
# SPDX-License-Identifier: BSD-2-Clause-Patent
#

'''
FmpCapsuleHeader
'''

import struct
import uuid

class FmpCapsuleImageHeaderClass (object):
    # typedef struct {
    #   UINT32   Version;
    #
    #   ///
    #   /// Used to identify device firmware targeted by this update. This guid is matched by
    #   /// system firmware against ImageTypeId field within a EFI_FIRMWARE_IMAGE_DESCRIPTOR
    #   ///
    #   EFI_GUID UpdateImageTypeId;
    #
    #   ///
    #   /// Passed as ImageIndex in call to EFI_FIRMWARE_MANAGEMENT_PROTOCOL.SetImage ()
    #   ///
    #   UINT8    UpdateImageIndex;
    #   UINT8    reserved_bytes[3];
    #
    #   ///
    #   /// Size of the binary update image which immediately follows this structure
    #   ///
    #   UINT32   UpdateImageSize;
    #
    #   ///
    #   /// Size of the VendorCode bytes which optionally immediately follow binary update image in the capsule
    #   ///
    #   UINT32   UpdateVendorCodeSize;
    #
    #   ///
    #   /// The HardwareInstance to target with this update. If value is zero it means match all
    #   /// HardwareInstances. This field allows update software to target only a single device in
    #   /// cases where there are more than one device with the same ImageTypeId GUID.
    #   /// This header is outside the signed data of the Authentication Info structure and
    #   /// therefore can be modified without changing the Auth data.
    #   ///
    #   UINT64   UpdateHardwareInstance;
    #
    #   ///
    #   /// Bits which indicate authentication and depex information for the image that follows this structure
    #   ///
    #   UINT64   ImageCapsuleSupport
    # } EFI_FIRMWARE_MANAGEMENT_CAPSULE_IMAGE_HEADER;
    #
    #  #define EFI_FIRMWARE_MANAGEMENT_CAPSULE_IMAGE_HEADER_INIT_VERSION 0x00000003

    _StructFormat = '<I16sB3BIIQQ'
    _StructSize   = struct.calcsize (_StructFormat)

    EFI_FIRMWARE_MANAGEMENT_CAPSULE_IMAGE_HEADER_INIT_VERSION = 0x00000003

    def __init__ (self):
        self._Valid                 = False
        self.Version                = self.EFI_FIRMWARE_MANAGEMENT_CAPSULE_IMAGE_HEADER_INIT_VERSION
        self.UpdateImageTypeId      = uuid.UUID ('00000000-0000-0000-0000-000000000000')
        self.UpdateImageIndex       = 0
        self.UpdateImageSize        = 0
        self.UpdateVendorCodeSize   = 0
        self.UpdateHardwareInstance = 0x0000000000000000
        self.ImageCapsuleSupport    = 0x0000000000000000
        self.Payload                = b''
        self.VendorCodeBytes        = b''

    def Encode (self):
        self.UpdateImageSize      = len (self.Payload)
        self.UpdateVendorCodeSize = len (self.VendorCodeBytes)
        FmpCapsuleImageHeader = struct.pack (
                                         self._StructFormat,
                                         self.Version,
                                         self.UpdateImageTypeId.bytes_le,
                                         self.UpdateImageIndex,
                                         0,0,0,
                                         self.UpdateImageSize,
                                         self.UpdateVendorCodeSize,
                                         self.UpdateHardwareInstance,
                                         self.ImageCapsuleSupport
                                         )
        self._Valid = True
        return FmpCapsuleImageHeader + self.Payload + self.VendorCodeBytes

    def Decode (self, Buffer):
        if len (Buffer) < self._StructSize:
            raise ValueError ('Buffer is too small for decoding')
        (Version, UpdateImageTypeId, UpdateImageIndex, r0, r1, r2, UpdateImageSize, UpdateVendorCodeSize, UpdateHardwareInstance, ImageCapsuleSupport) = \
            struct.unpack (
                     self._StructFormat,
                     Buffer[0:self._StructSize]
                     )

        if Version < self.EFI_FIRMWARE_MANAGEMENT_CAPSULE_IMAGE_HEADER_INIT_VERSION:
            raise ValueError ('Incorrect capsule image header version')
        if UpdateImageIndex < 1:
            raise ValueError ('Update image index is less than 1')
        if UpdateImageSize + UpdateVendorCodeSize != len (Buffer[self._StructSize:]):
            raise ValueError ('Non-vendor and vendor parts do not add up')

        self.Version                = Version
        self.UpdateImageTypeId      = uuid.UUID (bytes_le = UpdateImageTypeId)
        self.UpdateImageIndex       = UpdateImageIndex
        self.UpdateImageSize        = UpdateImageSize
        self.UpdateVendorCodeSize   = UpdateVendorCodeSize
        self.UpdateHardwareInstance = UpdateHardwareInstance
        self.ImageCapsuleSupport    = ImageCapsuleSupport
        self.Payload                = Buffer[self._StructSize:self._StructSize + UpdateImageSize]
        self.VendorCodeBytes        = Buffer[self._StructSize + UpdateImageSize:]
        self._Valid                 = True
        return Buffer[self._StructSize:]

    def DumpInfo (self):
        if not self._Valid:
            raise ValueError ('Can not dump an invalid header')
        print ('EFI_FIRMWARE_MANAGEMENT_CAPSULE_IMAGE_HEADER.Version                = {Version:08X}'.format (Version = self.Version))
        print ('EFI_FIRMWARE_MANAGEMENT_CAPSULE_IMAGE_HEADER.UpdateImageTypeId      = {UpdateImageTypeId}'.format (UpdateImageTypeId = str(self.UpdateImageTypeId).upper()))
        print ('EFI_FIRMWARE_MANAGEMENT_CAPSULE_IMAGE_HEADER.UpdateImageIndex       = {UpdateImageIndex:08X}'.format (UpdateImageIndex = self.UpdateImageIndex))
        print ('EFI_FIRMWARE_MANAGEMENT_CAPSULE_IMAGE_HEADER.UpdateImageSize        = {UpdateImageSize:08X}'.format (UpdateImageSize = self.UpdateImageSize))
        print ('EFI_FIRMWARE_MANAGEMENT_CAPSULE_IMAGE_HEADER.UpdateVendorCodeSize   = {UpdateVendorCodeSize:08X}'.format (UpdateVendorCodeSize = self.UpdateVendorCodeSize))
        print ('EFI_FIRMWARE_MANAGEMENT_CAPSULE_IMAGE_HEADER.UpdateHardwareInstance = {UpdateHardwareInstance:016X}'.format (UpdateHardwareInstance = self.UpdateHardwareInstance))
        print ('EFI_FIRMWARE_MANAGEMENT_CAPSULE_IMAGE_HEADER.ImageCapsuleSupport    = {ImageCapsuleSupport:016X}'.format (ImageCapsuleSupport = self.ImageCapsuleSupport))
        print ('sizeof (Payload)                                                    = {Size:08X}'.format (Size = len (self.Payload)))
        print ('sizeof (VendorCodeBytes)                                            = {Size:08X}'.format (Size = len (self.VendorCodeBytes)))

class FmpCapsuleHeaderClass (object):
    # typedef struct {
    #   UINT32 Version;
    #
    #   ///
    #   /// The number of drivers included in the capsule and the number of corresponding
    #   /// offsets stored in ItemOffsetList array.
    #   ///
    #   UINT16 EmbeddedDriverCount;
    #
    #   ///
    #   /// The number of payload items included in the capsule and the number of
    #   /// corresponding offsets stored in the ItemOffsetList array.
    #   ///
    #   UINT16 PayloadItemCount;
    #
    #   ///
    #   /// Variable length array of dimension [EmbeddedDriverCount + PayloadItemCount]
    #   /// containing offsets of each of the drivers and payload items contained within the capsule
    #   ///
    #   // UINT64 ItemOffsetList[];
    # } EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER;
    #
    #  #define EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER_INIT_VERSION       0x00000001
    _StructFormat = '<IHH'
    _StructSize   = struct.calcsize (_StructFormat)

    _ItemOffsetFormat = '<Q'
    _ItemOffsetSize   = struct.calcsize (_ItemOffsetFormat)

    EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER_INIT_VERSION = 0x00000001
    CAPSULE_SUPPORT_AUTHENTICATION = 0x0000000000000001
    CAPSULE_SUPPORT_DEPENDENCY     = 0x0000000000000002

    def __init__ (self):
        self._Valid                     = False
        self.Version                    = self.EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER_INIT_VERSION
        self.EmbeddedDriverCount        = 0
        self.PayloadItemCount           = 0
        self._ItemOffsetList            = []
        self._EmbeddedDriverList        = []
        self._PayloadList               = []
        self._FmpCapsuleImageHeaderList = []

    def AddEmbeddedDriver (self, EmbeddedDriver):
        self._EmbeddedDriverList.append (EmbeddedDriver)

    def GetEmbeddedDriver (self, Index):
        if Index > len (self._EmbeddedDriverList):
            raise ValueError ('Invalid embedded driver index')
        return self._EmbeddedDriverList[Index]

    def AddPayload (self, UpdateImageTypeId, Payload = b'', VendorCodeBytes = b'', HardwareInstance = 0, UpdateImageIndex = 1, CapsuleSupport = 0):
        self._PayloadList.append ((UpdateImageTypeId, Payload, VendorCodeBytes, HardwareInstance, UpdateImageIndex, CapsuleSupport))

    def GetFmpCapsuleImageHeader (self, Index):
        if Index >= len (self._FmpCapsuleImageHeaderList):
            raise ValueError ('Invalid capsule image index')
        return self._FmpCapsuleImageHeaderList[Index]

    def Encode (self):
        self.EmbeddedDriverCount = len (self._EmbeddedDriverList)
        self.PayloadItemCount    = len (self._PayloadList)

        FmpCapsuleHeader = struct.pack (
                                    self._StructFormat,
                                    self.Version,
                                    self.EmbeddedDriverCount,
                                    self.PayloadItemCount
                                    )

        FmpCapsuleData = b''
        Offset = self._StructSize + (self.EmbeddedDriverCount + self.PayloadItemCount) * self._ItemOffsetSize
        for EmbeddedDriver in self._EmbeddedDriverList:
            FmpCapsuleData = FmpCapsuleData + EmbeddedDriver
            self._ItemOffsetList.append (Offset)
            Offset = Offset + len (EmbeddedDriver)
        Index = 1
        for (UpdateImageTypeId, Payload, VendorCodeBytes, HardwareInstance, UpdateImageIndex, CapsuleSupport) in self._PayloadList:
            FmpCapsuleImageHeader = FmpCapsuleImageHeaderClass ()
            FmpCapsuleImageHeader.UpdateImageTypeId      = UpdateImageTypeId
            FmpCapsuleImageHeader.UpdateImageIndex       = UpdateImageIndex
            FmpCapsuleImageHeader.Payload                = Payload
            FmpCapsuleImageHeader.VendorCodeBytes        = VendorCodeBytes
            FmpCapsuleImageHeader.UpdateHardwareInstance = HardwareInstance
            FmpCapsuleImageHeader.ImageCapsuleSupport    = CapsuleSupport
            FmpCapsuleImage = FmpCapsuleImageHeader.Encode ()
            FmpCapsuleData = FmpCapsuleData + FmpCapsuleImage

            self._ItemOffsetList.append (Offset)
            self._FmpCapsuleImageHeaderList.append (FmpCapsuleImageHeader)

            Offset = Offset + len (FmpCapsuleImage)
            Index = Index + 1

        for Offset in self._ItemOffsetList:
          FmpCapsuleHeader = FmpCapsuleHeader + struct.pack (self._ItemOffsetFormat, Offset)

        self._Valid = True
        return FmpCapsuleHeader + FmpCapsuleData

    def Decode (self, Buffer):
        if len (Buffer) < self._StructSize:
            raise ValueError ('Buffer is too small for decoding')
        (Version, EmbeddedDriverCount, PayloadItemCount) = \
            struct.unpack (
                     self._StructFormat,
                     Buffer[0:self._StructSize]
                     )
        if Version < self.EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER_INIT_VERSION:
            raise ValueError ('Incorrect capsule header version')

        self.Version                    = Version
        self.EmbeddedDriverCount        = EmbeddedDriverCount
        self.PayloadItemCount           = PayloadItemCount
        self._ItemOffsetList            = []
        self._EmbeddedDriverList        = []
        self._PayloadList               = []
        self._FmpCapsuleImageHeaderList = []

        #
        # Parse the ItemOffsetList values
        #
        Offset = self._StructSize
        for Index in range (0, EmbeddedDriverCount + PayloadItemCount):
            ItemOffset = struct.unpack (self._ItemOffsetFormat, Buffer[Offset:Offset + self._ItemOffsetSize])[0]
            if ItemOffset >= len (Buffer):
                raise ValueError ('Item offset is outside of buffer')
            self._ItemOffsetList.append (ItemOffset)
            Offset = Offset + self._ItemOffsetSize
        Result = Buffer[Offset:]

        #
        # Parse the EmbeddedDrivers
        #
        for Index in range (0, EmbeddedDriverCount):
            Offset = self._ItemOffsetList[Index]
            if Index < (len (self._ItemOffsetList) - 1):
                Length = self._ItemOffsetList[Index + 1] - Offset
            else:
                Length = len (Buffer) - Offset
            self.AddEmbeddedDriver (Buffer[Offset:Offset + Length])

        #
        # Parse the Payloads that are FMP Capsule Images
        #
        for Index in range (EmbeddedDriverCount, EmbeddedDriverCount + PayloadItemCount):
            Offset = self._ItemOffsetList[Index]
            if Index < (len (self._ItemOffsetList) - 1):
                Length = self._ItemOffsetList[Index + 1] - Offset
            else:
                Length = len (Buffer) - Offset
            FmpCapsuleImageHeader = FmpCapsuleImageHeaderClass ()
            FmpCapsuleImageHeader.Decode (Buffer[Offset:Offset + Length])
            self.AddPayload (
                   FmpCapsuleImageHeader.UpdateImageTypeId,
                   FmpCapsuleImageHeader.Payload,
                   FmpCapsuleImageHeader.VendorCodeBytes
                   )
            self._FmpCapsuleImageHeaderList.append (FmpCapsuleImageHeader)

        self._Valid = True
        return Result

    def DumpInfo (self):
        if not self._Valid:
            raise ValueError ('Can not dump an invalid header')
        print ('EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER.Version             = {Version:08X}'.format (Version = self.Version))
        print ('EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER.EmbeddedDriverCount = {EmbeddedDriverCount:08X}'.format (EmbeddedDriverCount = self.EmbeddedDriverCount))
        for EmbeddedDriver in self._EmbeddedDriverList:
            print ('  sizeof (EmbeddedDriver)                                  = {Size:08X}'.format (Size = len (EmbeddedDriver)))
        print ('EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER.PayloadItemCount    = {PayloadItemCount:08X}'.format (PayloadItemCount = self.PayloadItemCount))
        print ('EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER.ItemOffsetList      = ')
        for Offset in self._ItemOffsetList:
            print ('  {Offset:016X}'.format (Offset = Offset))
        for FmpCapsuleImageHeader in self._FmpCapsuleImageHeaderList:
            FmpCapsuleImageHeader.DumpInfo ()
