# Copyright (C) 2026, Advanced Micro Devices, Inc.  All rights reserved.
# Portions of this file consist of AI-generated content.
# SPDX-License-Identifier: MIT

"""Zynqmp boot image loader module.

Reference: https://docs.amd.com/r/en-US/ug1283-bootgen-user-guide
"""

from typing import Optional
from enum import IntEnum
import struct
from .loader import (
    BootImageLoader,
    BootHeader,
    ImageHeaderTable,
    ImageHeader,
    PartitionHeader,
)


class ZynqmpBootHeader(BootHeader):
    """Zynqmp-specific boot header with additional fields."""
    
    # Header versions
    VERSION_ZYNQ = 0x01010000
    VERSION_ZYNQMP = 0x01020000
    
    # Boot header sizes and counts
    MAX_BOOT_VECTORS = 8
    UDF_BH_SIZE = 10  # User Defined Fields size in words
    UDF_BH_SIZE_BYTES = 40  # User Defined Fields size in bytes
    
    # Boot header offsets
    BH_ARM_VECTOR_TABLE_OFFSET = 0x00
    BH_WIDTH_DETECTION_OFFSET = 0x20
    BH_IDENTIFICATION_OFFSET = 0x24
    BH_KEY_SOURCE_OFFSET = 0x28
    BH_FSBL_EXEC_ADDR_OFFSET = 0x2C
    BH_SOURCE_OFFSET = 0x30
    BH_PMU_IMAGE_LENGTH_OFFSET = 0x34
    BH_TOTAL_PMU_FW_LENGTH_OFFSET = 0x38
    BH_FSBL_IMAGE_LENGTH = 0x3C
    BH_TOTAL_FSBL_LENGTH_OFFSET = 0x40
    BH_FSBL_IMAGE_ATTRIBUTES_OFFSET = 0x44
    BH_CHECKSUM_OFFSET = 0x48
    BH_BLACK_KEY_OFFSET = 0x4C
    BH_SHUTTER_VALUE_OFFSET = 0x6C
    BH_UDF_OFFSET = 0x70
    BH_IHT_OFFSET = 0x98
    BH_PHT_OFFSET = 0x9C
    BH_SECURE_HDR_IV_OFFSET = 0xA0
    BH_BLACK_KEY_IV_OFFSET = 0xAC
    
    # FSBL Image Attributes bit shifts (at offset 0x44)
    BH_ATTR_PUF_HD_SHIFT = 6
    BH_ATTR_HASHING_SELECT_SHIFT = 8
    BH_ATTR_CPU_SELECT_SHIFT = 10
    BH_ATTR_RSA_AUTH_SHIFT = 14
    
    # FSBL Image Attributes bit masks
    BH_ATTR_PUF_HD_MASK = 0x3
    BH_ATTR_HASHING_SELECT_MASK = 0x3
    BH_ATTR_CPU_SELECT_MASK = 0x3
    BH_ATTR_RSA_AUTH_MASK = 0x3

    class CpuSelect(IntEnum):
        """CPU Select types (bits 11:10)."""
        R5_SINGLE = 0
        A53_SINGLE_32BIT = 1
        A53_SINGLE_64BIT = 2
        R5_DUAL = 3

    class HashingSelect(IntEnum):
        """Hashing Select types (bits 9:8)."""
        NO_INTEGRITY_CHECK_0 = 0
        NO_INTEGRITY_CHECK_1 = 1
        RESERVED_2 = 2
        SHA3 = 3

    class RsaAuthentication(IntEnum):
        """RSA Authentication types (bits 15:14)."""
        EFUSE_0 = 0
        EFUSE_1 = 1
        EFUSE_2 = 2
        RSA_AUTH = 3

    class PufHdLocation(IntEnum):
        """PUF HD Location types (bits 7:6)."""
        EFUSE_0 = 0
        EFUSE_1 = 1
        EFUSE_2 = 2
        BOOT_HEADER = 3
    
    def __init__(self):
        """Initialize the Zynqmp boot header."""
        super().__init__()
        self.arm_vector_table: Optional[bytes] = None  # 0x00 - 0x1F
        # Width detection word at 0x20
        # Header signature at 0x24
        self.key_source: Optional[int] = None  # 0x28
        self.fsbl_execution_address: Optional[int] = None  # 0x2C
        self.source_offset: Optional[int] = None  # 0x30
        self.pmu_image_length: Optional[int] = None  # 0x34
        self.fsbl_image_length: Optional[int] = None  # 0x3C
        self.total_pmu_fw_length: Optional[int] = None  # 0x38
        self.total_fsbl_length: Optional[int] = None  # 0x40
        self.fsbl_image_attributes: Optional[int] = None  # 0x44
        self.boot_header_checksum: Optional[int] = None  # 0x48
        self.black_key_storage: Optional[bytes] = None  # 0x4C-0x6B (32 bytes, 8 words)
        self.shutter_value: Optional[int] = None  # 0x6C
        self.user_defined_fields: Optional[bytes] = None  # 0x70-0x97 (40 bytes, 10 words)
        self.image_header_table_offset: Optional[int] = None  # 0x98
        self.partition_header_table_offset: Optional[int] = None  # 0x9C
        self.secure_header_iv: Optional[bytes] = None  # 0xA0-0xAB (12 bytes, 3 words)
        self.black_key_iv: Optional[bytes] = None  # 0xAC-0xB7 (12 bytes, 3 words)

    def compute_checksum(self) -> int:
        """Compute the checksum over the boot header fields.
        
        The checksum covers words from 0x20 to 0x44 inclusive:
        width_detection_word, header_signature, key_source, fsbl_execution_address,
        source_offset, pmu_image_length, total_pmu_fw_length, fsbl_image_length,
        total_fsbl_length, and fsbl_image_attributes.
        
        Returns:
            The computed checksum as a 32-bit unsigned integer.
        """
        # width_detection_word and header_signature are stored as raw file bytes;
        # unpack as little-endian to get the integer values for checksum computation.
        width_word = struct.unpack('<I', self.width_detection_word or b'\x00\x00\x00\x00')[0]
        signature_word = struct.unpack('<I', self.header_signature or b'\x00\x00\x00\x00')[0]
        
        words = [
            width_word,                          # 0x20
            signature_word,                      # 0x24
            self.key_source or 0,                # 0x28
            self.fsbl_execution_address or 0,    # 0x2C
            self.source_offset or 0,             # 0x30
            self.pmu_image_length or 0,          # 0x34
            self.total_pmu_fw_length or 0,       # 0x38
            self.fsbl_image_length or 0,         # 0x3C
            self.total_fsbl_length or 0,         # 0x40
            self.fsbl_image_attributes or 0,     # 0x44
        ]
        
        # Sum all words and mask to 32 bits
        checksum = sum(words) & 0xFFFFFFFF
        
        # Invert (one's complement)
        checksum = (~checksum) & 0xFFFFFFFF
        
        return checksum

    def verify_checksum(self) -> bool:
        """Verify the boot header checksum.
        
        Returns:
            True if the checksum is valid, False otherwise.
        """
        if self.boot_header_checksum is None:
            return False
        return self.compute_checksum() == self.boot_header_checksum

    def get_cpu_select(self) -> 'ZynqmpBootHeader.CpuSelect':
        """Extract CPU select from FSBL image attributes (bits 11:10).
        
        Returns:
            CpuSelect enum value.
        """
        if self.fsbl_image_attributes is None:
            return ZynqmpBootHeader.CpuSelect.R5_SINGLE
        return ZynqmpBootHeader.CpuSelect((self.fsbl_image_attributes >> 10) & 0x3)

    def get_hashing_select(self) -> 'ZynqmpBootHeader.HashingSelect':
        """Extract hashing select from FSBL image attributes (bits 9:8).
        
        Returns:
            HashingSelect enum value.
        """
        if self.fsbl_image_attributes is None:
            return ZynqmpBootHeader.HashingSelect.NO_INTEGRITY_CHECK_0
        return ZynqmpBootHeader.HashingSelect((self.fsbl_image_attributes >> 8) & 0x3)

    def get_rsa_authentication(self) -> 'ZynqmpBootHeader.RsaAuthentication':
        """Extract RSA authentication from FSBL image attributes (bits 15:14).
        
        Returns:
            RsaAuthentication enum value.
        """
        if self.fsbl_image_attributes is None:
            return ZynqmpBootHeader.RsaAuthentication.EFUSE_0
        return ZynqmpBootHeader.RsaAuthentication((self.fsbl_image_attributes >> 14) & 0x3)

    def get_puf_hd_location(self) -> 'ZynqmpBootHeader.PufHdLocation':
        """Extract PUF HD location from FSBL image attributes (bits 7:6).
        
        Returns:
            PufHdLocation enum value.
        """
        if self.fsbl_image_attributes is None:
            return ZynqmpBootHeader.PufHdLocation.EFUSE_0
        return ZynqmpBootHeader.PufHdLocation((self.fsbl_image_attributes >> 6) & 0x3)

    def get_fsbl_attributes_summary(self) -> str:
        """Get a human-readable summary of the FSBL image attributes.
        
        Returns:
            String describing the FSBL image attributes.
        """
        return (f"CPU={self.get_cpu_select().name}, "
                f"Hashing={self.get_hashing_select().name}, "
                f"RSA={self.get_rsa_authentication().name}, "
                f"PUF_HD={self.get_puf_hd_location().name}")


class ZynqmpImageHeaderTable(ImageHeaderTable):
    """Zynqmp-specific image header table."""
    
    # Valid versions
    VERSION_1_1 = 0x01010000
    VERSION_1_2 = 0x01020000
    
    # Image Header Table offsets
    IHT_VERSION_OFFSET = 0x00
    IHT_COUNT_OFFSET = 0x04
    IHT_FIRST_PHT_OFFSET = 0x08
    IHT_FIRST_IH_OFFSET = 0x0C
    IHT_AUTH_CERT_OFFSET = 0x10
    IHT_SECONDARY_BOOT_DEVICE_OFFSET = 0x14
    IHT_RESERVED_START = 0x18
    IHT_RESERVED_END = 0x38
    IHT_CHECKSUM_OFFSET = 0x3C
    
    # Image Header Table size
    IMAGE_HEADER_TABLE_SIZE = 64  # bytes

    class SecondaryBootDevice(IntEnum):
        """Secondary boot device types (at offset 0x14)."""
        SAME_BOOT_DEVICE = 0
        QSPI_32 = 1
        QSPI_24 = 2
        NAND = 3
        SD0 = 4
        SD1 = 5
        SDLS = 6
        EMMC_MMC = 7
        USB = 8
        ETHERNET = 9
        PCIE = 10
        SATA = 11
    
    def __init__(self):
        """Initialize the Zynqmp image header table."""
        super().__init__()
        self.version: Optional[int] = None  # 0x00 - Versions 0x01010000 and 0x01020000
        self.count_of_image_headers: Optional[int] = None  # 0x04
        self.first_partition_header_offset: Optional[int] = None  # 0x08 (word offset)
        self.first_image_header_offset: Optional[int] = None  # 0x0C (word offset)
        self.header_authentication_certificate_offset: Optional[int] = None  # 0x10 (word offset, version 0x01020000 only)
        self.secondary_boot_device: Optional[int] = None  # 0x14
        # Reserved 0x18-0x38
        self.checksum: Optional[int] = None  # 0x3C

    def get_secondary_boot_device(self) -> 'ZynqmpImageHeaderTable.SecondaryBootDevice':
        """Extract secondary boot device.
        
        Returns:
            SecondaryBootDevice enum value.
        """
        if self.secondary_boot_device is None:
            return ZynqmpImageHeaderTable.SecondaryBootDevice.SAME_BOOT_DEVICE
        try:
            return ZynqmpImageHeaderTable.SecondaryBootDevice(self.secondary_boot_device)
        except ValueError:
            return ZynqmpImageHeaderTable.SecondaryBootDevice.SAME_BOOT_DEVICE

    def is_version_valid(self) -> bool:
        """Check if the version is valid.
        
        Returns:
            True if version is 0x01010000 or 0x01020000, False otherwise.
        """
        return self.version in (self.VERSION_1_1, self.VERSION_1_2)


class ZynqmpImageHeader(ImageHeader):
    """Zynqmp-specific image header."""
    
    # Image Header offsets
    IH_NEXT_IMAGE_HEADER_OFFSET = 0x00
    IH_CORRESPONDING_PARTITION_HEADER_OFFSET = 0x04
    IH_RESERVED_OFFSET = 0x08
    IH_PARTITION_COUNT_OFFSET = 0x0C
    IH_IMAGE_NAME_OFFSET = 0x10
    
    def __init__(self):
        """Initialize the Zynqmp image header."""
        super().__init__()
        self.next_image_header: Optional[int] = None  # 0x00 (word offset)
        self.corresponding_partition_header: Optional[int] = None  # 0x04 (word offset)
        # Reserved 0x08 - always 0x00000000
        self.partition_count: Optional[int] = None  # 0x0C
        self.image_name: Optional[str] = None  # 0x10-N: Null-terminated ASCII string (loaded by words)


class ZynqmpPartitionHeader(PartitionHeader):
    """Zynqmp-specific partition header."""
    
    # Maximum partitions
    MAX_NUM_PARTITIONS = 32
    
    # Partition header offsets
    PH_ENCRYPTED_LENGTH_OFFSET = 0x00
    PH_UNENCRYPTED_LENGTH_OFFSET = 0x04
    PH_TOTAL_LENGTH_OFFSET = 0x08
    PH_NEXT_PARTITION_OFFSET = 0x0C
    PH_EXEC_ADDR_LO_OFFSET = 0x10
    PH_EXEC_ADDR_HI_OFFSET = 0x14
    PH_LOAD_ADDR_LO_OFFSET = 0x18
    PH_LOAD_ADDR_HI_OFFSET = 0x1C
    PH_DATA_OFFSET = 0x20
    PH_ATTRIBUTES_OFFSET = 0x24
    PH_SECTION_COUNT_OFFSET = 0x28
    PH_CHECKSUM_OFFSET_FIELD = 0x2C
    PH_IH_OFFSET = 0x30
    PH_AC_OFFSET = 0x34
    PH_PARTITION_ID_OFFSET = 0x38
    PH_HEADER_CHECKSUM_OFFSET = 0x3C
    
    # Partition header size
    PARTITION_HEADER_SIZE = 64  # bytes
    
    # Partition attribute bit shifts
    PH_TRUSTZONE_SHIFT = 0
    PH_EXCEPTION_LEVEL_SHIFT = 1
    PH_A5X_EXEC_STATE_SHIFT = 3
    PH_DEST_DEVICE_SHIFT = 4
    PH_ENCRYPTION_SHIFT = 7
    PH_DEST_CPU_SHIFT = 8
    PH_CHECKSUM_SHIFT = 12
    PH_AC_FLAG_SHIFT = 15
    PH_OWNER_SHIFT = 16
    PH_ENDIANNESS_SHIFT = 18
    PH_EARLY_HANDOFF_SHIFT = 19
    PH_VECTOR_LOCATION_SHIFT = 23
    
    # Partition attribute bit masks
    PH_TRUSTZONE_MASK = 0x1
    PH_EXCEPTION_LEVEL_MASK = 0x3
    PH_A5X_EXEC_STATE_MASK = 0x1
    PH_DEST_DEVICE_MASK = 0x7
    PH_ENCRYPTION_MASK = 0x1
    PH_DEST_CPU_MASK = 0xF
    PH_CHECKSUM_MASK = 0x7
    PH_AC_FLAG_MASK = 0x1
    PH_OWNER_MASK = 0x3
    PH_ENDIANNESS_MASK = 0x1
    PH_EARLY_HANDOFF_MASK = 0x1
    PH_VECTOR_LOCATION_MASK = 0x1
    
    # Encryption header signature (for detecting encrypted partitions)
    ENCRYPTION_HEADER = bytes([
        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
        0xBB, 0x00, 0x00, 0x00, 0x44, 0x00, 0x22, 0x11,
        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
        0x66, 0x55, 0x99, 0xAA
    ])
    ENCRYPTION_HEADER_SIZE = 28

    class PartitionOwner(IntEnum):
        """Partition owner types (bits 17:16)."""
        FSBL = 0
        UBOOT = 1
        RESERVED_2 = 2
        RESERVED_3 = 3

    class ChecksumType(IntEnum):
        """Checksum types (bits 14:12)."""
        NONE = 0
        RESERVED_1 = 1
        RESERVED_2 = 2
        SHA3 = 3
        RESERVED_4 = 4
        RESERVED_5 = 5
        RESERVED_6 = 6
        RESERVED_7 = 7

    class DestinationDevice(IntEnum):
        """Destination device types (bits 6:4)."""
        NONE = 0
        PS = 1
        PL = 2
        RESERVED_3 = 3
        RESERVED_4 = 4
        RESERVED_5 = 5
        RESERVED_6 = 6
        RESERVED_7 = 7

    class DestinationCpu(IntEnum):
        """Destination CPU types (bits 11:8)."""
        NONE = 0
        A53_0 = 1
        A53_1 = 2
        A53_2 = 3
        A53_3 = 4
        R5_0 = 5
        R5_1 = 6
        R5_LOCKSTEP = 7
        PMU = 8
        RESERVED_9 = 9
        RESERVED_10 = 10
        RESERVED_11 = 11
        RESERVED_12 = 12
        RESERVED_13 = 13
        RESERVED_14 = 14
        RESERVED_15 = 15

    class ExceptionLevel(IntEnum):
        """Exception level types (bits 2:1)."""
        EL0 = 0
        EL1 = 1
        EL2 = 2
        EL3 = 3

    class A5xExecState(IntEnum):
        """A5x execution state types (bit 3)."""
        AARCH64 = 0
        AARCH32 = 1

    class VectorLocation(IntEnum):
        """Vector location types (bit 23)."""
        LOVEC = 0
        HIVEC = 1

    class Endianness(IntEnum):
        """Endianness types (bit 18)."""
        LITTLE_ENDIAN = 0
        BIG_ENDIAN = 1

    class Trustzone(IntEnum):
        """Trustzone types (bit 0)."""
        NON_SECURE = 0
        SECURE = 1

    def __init__(self):
        """Initialize the Zynqmp partition header."""
        super().__init__()
        self.encrypted_partition_length: Optional[int] = None  # 0x00 (word length)
        self.unencrypted_partition_length: Optional[int] = None  # 0x04 (word length)
        self.total_partition_word_length: Optional[int] = None  # 0x08 (word length)
        self.next_partition_header_offset: Optional[int] = None  # 0x0C (word offset)
        self.destination_execution_address_lo: Optional[int] = None  # 0x10 (low 32-bits)
        self.destination_execution_address_hi: Optional[int] = None  # 0x14 (high 32-bits)
        self.destination_load_address_lo: Optional[int] = None  # 0x18 (low 32-bits)
        self.destination_load_address_hi: Optional[int] = None  # 0x1C (high 32-bits)
        self.actual_partition_word_offset: Optional[int] = None  # 0x20 (word offset)
        self.attribute_bits: Optional[int] = None  # 0x24
        self.section_count: Optional[int] = None  # 0x28
        self.checksum_word_offset: Optional[int] = None  # 0x2C (word offset)
        self.image_header_word_offset: Optional[int] = None  # 0x30 (word offset)
        self.authentication_certificate_word_offset: Optional[int] = None  # 0x34 (word offset)
        self.partition_id: Optional[int] = None  # 0x38
        self.header_checksum: Optional[int] = None  # 0x3C

    def get_destination_execution_address(self) -> int:
        """Get the full 64-bit destination execution address.
        
        Returns:
            The 64-bit execution address.
        """
        lo = self.destination_execution_address_lo or 0
        hi = self.destination_execution_address_hi or 0
        return (hi << 32) | lo

    def get_destination_load_address(self) -> int:
        """Get the full 64-bit destination load address.
        
        Returns:
            The 64-bit load address.
        """
        lo = self.destination_load_address_lo or 0
        hi = self.destination_load_address_hi or 0
        return (hi << 32) | lo

    def get_trustzone(self) -> 'ZynqmpPartitionHeader.Trustzone':
        """Extract trustzone from attribute bits (bit 0).
        
        Returns:
            Trustzone enum value.
        """
        if self.attribute_bits is None:
            return ZynqmpPartitionHeader.Trustzone.NON_SECURE
        return ZynqmpPartitionHeader.Trustzone(self.attribute_bits & 0x1)

    def get_exception_level(self) -> 'ZynqmpPartitionHeader.ExceptionLevel':
        """Extract exception level from attribute bits (bits 2:1).
        
        Returns:
            ExceptionLevel enum value.
        """
        if self.attribute_bits is None:
            return ZynqmpPartitionHeader.ExceptionLevel.EL0
        return ZynqmpPartitionHeader.ExceptionLevel((self.attribute_bits >> 1) & 0x3)

    def get_a5x_exec_state(self) -> 'ZynqmpPartitionHeader.A5xExecState':
        """Extract A5x execution state from attribute bits (bit 3).
        
        Returns:
            A5xExecState enum value.
        """
        if self.attribute_bits is None:
            return ZynqmpPartitionHeader.A5xExecState.AARCH64
        return ZynqmpPartitionHeader.A5xExecState((self.attribute_bits >> 3) & 0x1)

    def get_destination_device(self) -> 'ZynqmpPartitionHeader.DestinationDevice':
        """Extract destination device from attribute bits (bits 6:4).
        
        Returns:
            DestinationDevice enum value.
        """
        if self.attribute_bits is None:
            return ZynqmpPartitionHeader.DestinationDevice.NONE
        return ZynqmpPartitionHeader.DestinationDevice((self.attribute_bits >> 4) & 0x7)

    def get_encryption_present(self) -> bool:
        """Extract encryption present flag from attribute bits (bit 7).
        
        Returns:
            True if partition is encrypted, False otherwise.
        """
        if self.attribute_bits is None:
            return False
        return bool((self.attribute_bits >> 7) & 0x1)

    def get_destination_cpu(self) -> 'ZynqmpPartitionHeader.DestinationCpu':
        """Extract destination CPU from attribute bits (bits 11:8).
        
        Returns:
            DestinationCpu enum value.
        """
        if self.attribute_bits is None:
            return ZynqmpPartitionHeader.DestinationCpu.NONE
        return ZynqmpPartitionHeader.DestinationCpu((self.attribute_bits >> 8) & 0xF)

    def get_checksum_type(self) -> 'ZynqmpPartitionHeader.ChecksumType':
        """Extract checksum type from attribute bits (bits 14:12).
        
        Returns:
            ChecksumType enum value.
        """
        if self.attribute_bits is None:
            return ZynqmpPartitionHeader.ChecksumType.NONE
        return ZynqmpPartitionHeader.ChecksumType((self.attribute_bits >> 12) & 0x7)

    def get_rsa_signature_present(self) -> bool:
        """Extract RSA signature present flag from attribute bits (bit 15).
        
        Returns:
            True if RSA signature is present, False otherwise.
        """
        if self.attribute_bits is None:
            return False
        return bool((self.attribute_bits >> 15) & 0x1)

    def get_partition_owner(self) -> 'ZynqmpPartitionHeader.PartitionOwner':
        """Extract partition owner from attribute bits (bits 17:16).
        
        Returns:
            PartitionOwner enum value.
        """
        if self.attribute_bits is None:
            return ZynqmpPartitionHeader.PartitionOwner.FSBL
        return ZynqmpPartitionHeader.PartitionOwner((self.attribute_bits >> 16) & 0x3)

    def get_endianness(self) -> 'ZynqmpPartitionHeader.Endianness':
        """Extract endianness from attribute bits (bit 18).
        
        Returns:
            Endianness enum value.
        """
        if self.attribute_bits is None:
            return ZynqmpPartitionHeader.Endianness.LITTLE_ENDIAN
        return ZynqmpPartitionHeader.Endianness((self.attribute_bits >> 18) & 0x1)

    def get_early_handoff(self) -> bool:
        """Extract early handoff flag from attribute bits (bit 19).
        
        Returns:
            True if early handoff is enabled, False otherwise.
        """
        if self.attribute_bits is None:
            return False
        return bool((self.attribute_bits >> 19) & 0x1)

    def get_vector_location(self) -> 'ZynqmpPartitionHeader.VectorLocation':
        """Extract vector location from attribute bits (bit 23).
        
        Returns:
            VectorLocation enum value.
        """
        if self.attribute_bits is None:
            return ZynqmpPartitionHeader.VectorLocation.LOVEC
        return ZynqmpPartitionHeader.VectorLocation((self.attribute_bits >> 23) & 0x1)

    def get_attribute_summary(self) -> str:
        """Get a human-readable summary of the attribute bits.
        
        Returns:
            String describing the partition attributes.
        """
        return (f"Owner={self.get_partition_owner().name}, "
                f"CPU={self.get_destination_cpu().name}, "
                f"Device={self.get_destination_device().name}, "
                f"TZ={self.get_trustzone().name}, "
                f"EL={self.get_exception_level().name}, "
                f"ExecState={self.get_a5x_exec_state().name}")

    def get_encrypted_partition_length_bytes(self) -> int:
        """Get the encrypted partition length in bytes.
        
        The length is stored in words (4 bytes each) in the header.
        
        Returns:
            The encrypted partition length in bytes.
        """
        if self.encrypted_partition_length is None:
            return 0
        return self.encrypted_partition_length * 4

    def get_unencrypted_partition_length_bytes(self) -> int:
        """Get the unencrypted partition length in bytes.
        
        The length is stored in words (4 bytes each) in the header.
        
        Returns:
            The unencrypted partition length in bytes.
        """
        if self.unencrypted_partition_length is None:
            return 0
        return self.unencrypted_partition_length * 4

    def get_total_partition_length_bytes(self) -> int:
        """Get the total partition length in bytes.
        
        The length is stored in words (4 bytes each) in the header.
        
        Returns:
            The total partition length in bytes.
        """
        if self.total_partition_word_length is None:
            return 0
        return self.total_partition_word_length * 4

    def get_data_offset_bytes(self) -> int:
        """Get the data offset in bytes.
        
        The offset is stored in words (4 bytes each) in the header.
        
        Returns:
            The data offset in bytes.
        """
        if self.actual_partition_word_offset is None:
            return 0
        return self.actual_partition_word_offset * 4

    def compute_checksum(self) -> int:
        """Compute the checksum over the partition header fields.
        
        The checksum is the sum of all 32-bit words in the header,
        excluding the checksum field itself (last 4 bytes).
        
        Returns:
            The computed checksum as a 32-bit unsigned integer.
        """
        # Pack the header fields into words (excluding checksum)
        words = [
            self.encrypted_partition_length or 0,          # 0x00
            self.unencrypted_partition_length or 0,        # 0x04
            self.total_partition_word_length or 0,         # 0x08
            self.next_partition_header_offset or 0,        # 0x0C
            self.destination_execution_address_lo or 0,    # 0x10
            self.destination_execution_address_hi or 0,    # 0x14
            self.destination_load_address_lo or 0,         # 0x18
            self.destination_load_address_hi or 0,         # 0x1C
            self.actual_partition_word_offset or 0,        # 0x20
            self.attribute_bits or 0,                      # 0x24
            self.section_count or 0,                       # 0x28
            self.checksum_word_offset or 0,                # 0x2C
            self.image_header_word_offset or 0,            # 0x30
            self.authentication_certificate_word_offset or 0,  # 0x34
            self.partition_id or 0,                        # 0x38
        ]
        
        # Sum all words and mask to 32 bits
        checksum = sum(words) & 0xFFFFFFFF
        
        # Invert (one's complement)
        checksum = (~checksum) & 0xFFFFFFFF
        
        return checksum

    def verify_checksum(self) -> bool:
        """Verify the partition header checksum.
        
        Returns:
            True if the checksum is valid, False otherwise.
        """
        if self.header_checksum is None:
            return False
        return self.compute_checksum() == self.header_checksum


class ZynqmpBootImageLoader(BootImageLoader):
    """Zynqmp-specific boot image loader implementation."""

    def __init__(self, image_path=None, image_path_offset=0):
        """Initialize the Zynqmp boot image loader.
        
        Args:
            image_path: Path to the boot image file.
            image_path_offset: Offset within the image file to start reading from.
        """
        super().__init__(image_path, image_path_offset)
        self.boot_header = ZynqmpBootHeader()
        self.image_header_table = ZynqmpImageHeaderTable()
        self.image = []  # List of tuples (image_header, [partition_headers])

    def verify_magic(self) -> bool:
        """Verify the magic bytes at offset 0x24.
        
        Checks for "XLNX" (0x584c4e58 in little endian) at offset 0x24.
        
        Returns:
            True if the magic bytes are correct, False otherwise.
        
        Raises:
            RuntimeError: If no image data is loaded.
            ValueError: If image data is too small.
        """
        return self._verify_magic_at_offset(0x24)

    def detect_width(self) -> bool:
        """Detect the bus width at offset 0x20.
        
        Detects single or double stacked flash at offset 0x20.
        
        Returns:
            True if the width detection is successful, False otherwise.
        
        Raises:
            RuntimeError: If no image data is loaded.
            ValueError: If image data is too small.
        """
        return self._detect_width_at_offset(0x20)

    @property
    def image_headers(self):
        """Property to access image headers with their partition headers.
        
        Returns:
            List of image headers, each with partition_headers attribute set.
        """
        result = []
        for image_header, partition_headers in self.image:
            # Attach partition_headers to the image_header
            image_header.partition_headers = partition_headers
            result.append(image_header)
        return result

    def parse(self):
        """Parse the Zynqmp boot image.
        
        Raises:
            RuntimeError: If no image data is loaded.
        """
        if not self.image_fd:
            raise RuntimeError("No image data loaded. Call open() first.")
        
        # Validate the boot header before reading too much

        # Header Signature at 0x24 (already read by verify_magic)
        if not self.verify_magic():
            raise ValueError(f"Invalid boot image: magic bytes do not match {self.boot_header.header_signature.hex()}")
        
        if not self.detect_width():
            raise ValueError(f"Width detection indicates an unsupported configuration: {self.boot_header.width_detection_word.hex()}") 
        
        # Key Source at 0x28 (4 bytes)
        self._safe_seek(self.image_path_offset + 0x28, 4)
        self.boot_header.key_source = struct.unpack('<I', self.image_fd.read(4))[0]
        if self.boot_header.key_source != 0x00000000:
            raise ValueError("Encryption is not supported")
        
        # Read ARM Vector Table at 0x00-0x1F (32 bytes)
        self._safe_seek(self.image_path_offset + 0x00, 32)
        word1 = struct.unpack('>I', self.image_fd.read(4))[0].to_bytes(4, 'little')
        word2 = struct.unpack('>I', self.image_fd.read(4))[0].to_bytes(4, 'little')
        word3 = struct.unpack('>I', self.image_fd.read(4))[0].to_bytes(4, 'little')
        word4 = struct.unpack('>I', self.image_fd.read(4))[0].to_bytes(4, 'little')
        word5 = struct.unpack('>I', self.image_fd.read(4))[0].to_bytes(4, 'little')
        word6 = struct.unpack('>I', self.image_fd.read(4))[0].to_bytes(4, 'little')
        word7 = struct.unpack('>I', self.image_fd.read(4))[0].to_bytes(4, 'little')
        word8 = struct.unpack('>I', self.image_fd.read(4))[0].to_bytes(4, 'little')
        self.boot_header.arm_vector_table = word1 + word2 + word3 + word4 + word5 + word6 + word7 + word8

        # FSBL Execution Address at 0x2C (4 bytes, little-endian)
        self._safe_seek(self.image_path_offset + 0x2C, 4)
        self.boot_header.fsbl_execution_address = struct.unpack('<I', self.image_fd.read(4))[0]
        
        # Source Offset at 0x30 (4 bytes, little-endian)
        self._safe_seek(self.image_path_offset + 0x30, 4)
        self.boot_header.source_offset = struct.unpack('<I', self.image_fd.read(4))[0]
        
        # PMU Image Length at 0x34 (4 bytes, little-endian)
        self._safe_seek(self.image_path_offset + 0x34, 4)
        self.boot_header.pmu_image_length = struct.unpack('<I', self.image_fd.read(4))[0]
        
        # Total PMU FW Length at 0x38 (4 bytes, little-endian)
        self._safe_seek(self.image_path_offset + 0x38, 4)
        self.boot_header.total_pmu_fw_length = struct.unpack('<I', self.image_fd.read(4))[0]
        
        # FSBL Image Length at 0x3C (4 bytes, little-endian)
        self._safe_seek(self.image_path_offset + 0x3C, 4)
        self.boot_header.fsbl_image_length = struct.unpack('<I', self.image_fd.read(4))[0]
        
        # Total FSBL Length at 0x40 (4 bytes, little-endian)
        self._safe_seek(self.image_path_offset + 0x40, 4)
        self.boot_header.total_fsbl_length = struct.unpack('<I', self.image_fd.read(4))[0]
        
        # FSBL Image Attributes at 0x44 (4 bytes, little-endian)
        self._safe_seek(self.image_path_offset + 0x44, 4)
        self.boot_header.fsbl_image_attributes = struct.unpack('<I', self.image_fd.read(4))[0]
        
        # Boot Header Checksum at 0x48 (4 bytes, little-endian)
        self._safe_seek(self.image_path_offset + 0x48, 4)
        self.boot_header.boot_header_checksum = struct.unpack('<I', self.image_fd.read(4))[0]
        
        # Verify boot header checksum
        if not self.boot_header.verify_checksum():
            computed = self.boot_header.compute_checksum()
            expected = self.boot_header.boot_header_checksum
            raise ValueError(f"Boot header checksum verification failed: computed=0x{computed:08X}, expected=0x{expected:08X}")
        
        # Obfuscated/Black Key Storage at 0x4C-0x6B (32 bytes, 8 words)
        self._safe_seek(self.image_path_offset + 0x4C, 32)
        self.boot_header.black_key_storage = self.image_fd.read(32)
        
        # Shutter Value at 0x6C (4 bytes, little-endian)
        self._safe_seek(self.image_path_offset + 0x6C, 4)
        self.boot_header.shutter_value = struct.unpack('<I', self.image_fd.read(4))[0]
        
        # User Defined Fields at 0x70-0x97 (40 bytes, 10 words)
        self._safe_seek(self.image_path_offset + 0x70, 40)
        self.boot_header.user_defined_fields = self.image_fd.read(40)
        
        # Image Header Table Offset at 0x98 (4 bytes, little-endian)
        self._safe_seek(self.image_path_offset + 0x98, 4)
        self.boot_header.image_header_table_offset = struct.unpack('<I', self.image_fd.read(4))[0]
        
        # Partition Header Table Offset at 0x9C (4 bytes, little-endian)
        self._safe_seek(self.image_path_offset + 0x9C, 4)
        self.boot_header.partition_header_table_offset = struct.unpack('<I', self.image_fd.read(4))[0]
        
        # Secure Header IV at 0xA0-0xAB (12 bytes, 3 words)
        self._safe_seek(self.image_path_offset + 0xA0, 12)
        self.boot_header.secure_header_iv = self.image_fd.read(12)
        
        # Obfuscated/Black Key IV at 0xAC-0xB7 (12 bytes, 3 words)
        self._safe_seek(self.image_path_offset + 0xAC, 12)
        self.boot_header.black_key_iv = self.image_fd.read(12)

        # Load Image Header Table
        if self.boot_header.image_header_table_offset is not None:
            # Seek to Image Header Table offset
            self._safe_seek(self.image_path_offset + self.boot_header.image_header_table_offset, ZynqmpImageHeaderTable.IMAGE_HEADER_TABLE_SIZE)
            
            # Version at 0x00 (4 bytes)
            self.image_header_table.version = struct.unpack('<I', self.image_fd.read(4))[0]
            
            # Validate version
            if not self.image_header_table.is_version_valid():
                raise ValueError(f"Invalid Image Header Table version: 0x{self.image_header_table.version:08X}")
            
            # Count of Image Headers at 0x04 (4 bytes, little-endian)
            self.image_header_table.count_of_image_headers = struct.unpack('<I', self.image_fd.read(4))[0]
            
            # First Partition Header Offset at 0x08 (4 bytes, little-endian, word offset)
            self.image_header_table.first_partition_header_offset = struct.unpack('<I', self.image_fd.read(4))[0]
            
            # First Image Header Offset at 0x0C (4 bytes, little-endian, word offset)
            self.image_header_table.first_image_header_offset = struct.unpack('<I', self.image_fd.read(4))[0]
            
            # Header Authentication Certificate Offset at 0x10 (4 bytes, little-endian, word offset)
            # Only valid if version is 0x01020000
            if self.image_header_table.version == ZynqmpImageHeaderTable.VERSION_1_2:
                self.image_header_table.header_authentication_certificate_offset = struct.unpack('<I', self.image_fd.read(4))[0]
            else:
                _ = self.image_fd.read(4)  # Skip reserved field
                self.image_header_table.header_authentication_certificate_offset = None
            
            # Secondary Boot Device at 0x14 (4 bytes, little-endian)
            self.image_header_table.secondary_boot_device = struct.unpack('<I', self.image_fd.read(4))[0]
            
            # Skip reserved 0x18-0x38 (36 bytes)
            _ = self.image_fd.read(36)
            
            # Checksum at 0x3C (4 bytes, little-endian)
            self.image_header_table.checksum = struct.unpack('<I', self.image_fd.read(4))[0]

        # Load Image Headers and corresponding Partition Headers
        if self.image_header_table.first_image_header_offset is not None:
            current_offset = self.image_header_table.first_image_header_offset * 4 # Word to byte offset
            
            while current_offset != 0 and current_offset != 0xffffffff:
                # Create a new image header instance
                image_header = ZynqmpImageHeader()
                
                # Seek to current image header offset
                self._safe_seek(self.image_path_offset + current_offset, 20)  # At least 20 bytes needed for header fields
                
                # Next Image Header Offset at 0x00 (4 bytes, little-endian, word offset)
                image_header.next_image_header = struct.unpack('<I', self.image_fd.read(4))[0]
                
                # Corresponding Partition Header at 0x04 (4 bytes, little-endian, word offset)
                image_header.corresponding_partition_header = struct.unpack('<I', self.image_fd.read(4))[0]
                
                # Reserved at 0x08 (4 bytes)
                _ = self.image_fd.read(4)
                
                # Partition Count at 0x0C (4 bytes, little-endian)
                image_header.partition_count = struct.unpack('<I', self.image_fd.read(4))[0]
                
                # Image Name at 0x10-N (null-terminated ASCII string, loaded by words)
                image_name_bytes = b''
                while True:
                    word = struct.unpack('>I', self.image_fd.read(4))[0].to_bytes(4, 'little')
                    if word[0:1] == b'\x00' and word[1:2] == b'\x00' and word[2:3] == b'\x00' and word[3:4] == b'\x00':
                        break
                    image_name_bytes += word
                
                image_header.image_name = image_name_bytes.decode('ascii', errors='ignore')
                
                # Load all corresponding partition headers for this image
                partition_headers = []
                partition_header_offset = image_header.corresponding_partition_header * 4  # Convert word to byte offset
                partitions_to_read = image_header.partition_count or 1
                
                for _ in range(partitions_to_read):
                    if partition_header_offset == 0:
                        break
                    
                    partition_header = ZynqmpPartitionHeader()
                    
                    # Seek to partition header location
                    self._safe_seek(self.image_path_offset + partition_header_offset, ZynqmpPartitionHeader.PARTITION_HEADER_SIZE)
                    
                    # Encrypted Partition Length at 0x00 (4 bytes, little-endian)
                    partition_header.encrypted_partition_length = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Unencrypted Partition Length at 0x04 (4 bytes, little-endian)
                    partition_header.unencrypted_partition_length = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Total Partition Word Length at 0x08 (4 bytes, little-endian)
                    partition_header.total_partition_word_length = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Next Partition Header Offset at 0x0C (4 bytes, little-endian, word offset)
                    partition_header.next_partition_header_offset = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Destination Execution Address LO at 0x10 (4 bytes, little-endian)
                    partition_header.destination_execution_address_lo = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Destination Execution Address HI at 0x14 (4 bytes, little-endian)
                    partition_header.destination_execution_address_hi = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Destination Load Address LO at 0x18 (4 bytes, little-endian)
                    partition_header.destination_load_address_lo = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Destination Load Address HI at 0x1C (4 bytes, little-endian)
                    partition_header.destination_load_address_hi = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Actual Partition Word Offset at 0x20 (4 bytes, little-endian)
                    partition_header.actual_partition_word_offset = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Attribute Bits at 0x24 (4 bytes, little-endian)
                    partition_header.attribute_bits = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Section Count at 0x28 (4 bytes, little-endian)
                    partition_header.section_count = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Checksum Word Offset at 0x2C (4 bytes, little-endian)
                    partition_header.checksum_word_offset = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Image Header Word Offset at 0x30 (4 bytes, little-endian)
                    partition_header.image_header_word_offset = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Authentication Certificate Word Offset at 0x34 (4 bytes, little-endian)
                    partition_header.authentication_certificate_word_offset = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Partition ID at 0x38 (4 bytes, little-endian)
                    partition_header.partition_id = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Header Checksum at 0x3C (4 bytes, little-endian)
                    partition_header.header_checksum = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Verify partition header checksum
                    if not partition_header.verify_checksum():
                        raise ValueError(f"Partition header checksum verification failed for '{image_header.image_name}'")
                    
                    partition_headers.append(partition_header)
                    
                    # Move to next partition header for this image
                    partition_header_offset = partition_header.next_partition_header_offset * 4  # Word to byte offset
                
                # Add tuple to list (image_header, list of partition_headers)
                self.image.append((image_header, partition_headers))
                
                # Move to next image header (0 means this was the last one)
                current_offset = image_header.next_image_header * 4 # Word to byte offset

        return

    def debug(self):
        """Output debug information about the loaded Zynqmp boot image."""
        if not self.image_fd:
            raise RuntimeError("No image data loaded. Call open() first.")
        
        print("ZynqMP Boot Image Header:")
        print(f"  ARM Vector Table: {self.boot_header.arm_vector_table.hex() if self.boot_header.arm_vector_table else 'Not read'}")
        print(f"  Width Detection Word: {self.boot_header.width_detection_word.hex() if self.boot_header.width_detection_word else 'Not read'}")
        print(f"  Header Signature: {self.boot_header.header_signature.hex() if self.boot_header.header_signature else 'Not read'}")
        print(f"  Key Source: {hex(self.boot_header.key_source) if self.boot_header.key_source is not None else 'Not read'}")
        print(f"  FSBL Execution Address: {hex(self.boot_header.fsbl_execution_address) if self.boot_header.fsbl_execution_address is not None else 'Not read'}")
        print(f"  Source Offset: {hex(self.boot_header.source_offset) if self.boot_header.source_offset is not None else 'Not read'}")
        print(f"  PMU Image Length: {self.boot_header.pmu_image_length if self.boot_header.pmu_image_length is not None else 'Not read'}")
        print(f"  Total PMU FW Length: {self.boot_header.total_pmu_fw_length if self.boot_header.total_pmu_fw_length is not None else 'Not read'}")
        print(f"  FSBL Image Length: {self.boot_header.fsbl_image_length if self.boot_header.fsbl_image_length is not None else 'Not read'}")
        print(f"  Total FSBL Length: {self.boot_header.total_fsbl_length if self.boot_header.total_fsbl_length is not None else 'Not read'}")
        print(f"  FSBL Image Attributes: {hex(self.boot_header.fsbl_image_attributes) if self.boot_header.fsbl_image_attributes is not None else 'Not read'}")
        if self.boot_header.fsbl_image_attributes is not None:
            print(f"    CPU Select: {self.boot_header.get_cpu_select().name}")
            print(f"    Hashing Select: {self.boot_header.get_hashing_select().name}")
            print(f"    RSA Authentication: {self.boot_header.get_rsa_authentication().name}")
            print(f"    PUF HD Location: {self.boot_header.get_puf_hd_location().name}")
        print(f"  Boot Header Checksum: {hex(self.boot_header.boot_header_checksum) if self.boot_header.boot_header_checksum is not None else 'Not read'}")
        print(f"  Black Key Storage: {self.boot_header.black_key_storage.hex() if self.boot_header.black_key_storage else 'Not read'}")
        print(f"  Shutter Value: {hex(self.boot_header.shutter_value) if self.boot_header.shutter_value is not None else 'Not read'}")
        print(f"  User Defined Fields: {self.boot_header.user_defined_fields if self.boot_header.user_defined_fields else 'Not read'}")
        print(f"  Image Header Table Offset: {hex(self.boot_header.image_header_table_offset) if self.boot_header.image_header_table_offset is not None else 'Not read'}")
        print(f"  Partition Header Table Offset: {hex(self.boot_header.partition_header_table_offset) if self.boot_header.partition_header_table_offset is not None else 'Not read'}")
        print(f"  Secure Header IV: {self.boot_header.secure_header_iv.hex() if self.boot_header.secure_header_iv else 'Not read'}")
        print(f"  Black Key IV: {self.boot_header.black_key_iv.hex() if self.boot_header.black_key_iv else 'Not read'}")
        
        if self.boot_header.image_header_table_offset:
            print("\nZynqmp Image Header Table:")
            print(f"  Version: {hex(self.image_header_table.version) if self.image_header_table.version else 'Not read'}")
            print(f"  Count of Image Headers: {self.image_header_table.count_of_image_headers if self.image_header_table.count_of_image_headers is not None else 'Not read'}")
            print(f"  First Partition Header Offset: {hex(self.image_header_table.first_partition_header_offset) if self.image_header_table.first_partition_header_offset is not None else 'Not read'} (words)")
            print(f"  First Image Header Offset: {hex(self.image_header_table.first_image_header_offset) if self.image_header_table.first_image_header_offset is not None else 'Not read'} (words)")
            print(f"  Header Authentication Certificate Offset: {hex(self.image_header_table.header_authentication_certificate_offset) if self.image_header_table.header_authentication_certificate_offset is not None else 'Not read/Reserved'} (words)")
            print(f"  Secondary Boot Device: {self.image_header_table.get_secondary_boot_device().name if self.image_header_table.secondary_boot_device is not None else 'Not read'}")
            print(f"  Checksum: {hex(self.image_header_table.checksum) if self.image_header_table.checksum is not None else 'Not read'}")
        
        # Display Image Headers
        if self.image:
            print(f"\nZynqmp Image Headers (Total: {len(self.image)}):")
            for idx, (image_header, partition_headers) in enumerate(self.image):
                print(f"\n  Image Header #{idx}:")
                print(f"    Next Image Header: {hex(image_header.next_image_header) if image_header.next_image_header is not None else 'Not read'} (words)")
                print(f"    Corresponding Partition Header: {hex(image_header.corresponding_partition_header) if image_header.corresponding_partition_header is not None else 'Not read'} (words)")
                print(f"    Partition Count: {image_header.partition_count if image_header.partition_count is not None else 'Not read'}")
                print(f"    Image Name: '{image_header.image_name if image_header.image_name else 'Not read'}'")
                
                # Display all Partition Headers for this image
                for part_idx, partition_header in enumerate(partition_headers):
                    print(f"\n  Partition Header #{idx}.{part_idx}:")
                    print(f"    Encrypted Partition Length: {partition_header.encrypted_partition_length if partition_header.encrypted_partition_length is not None else 'Not read'}")
                    print(f"    Unencrypted Partition Length: {partition_header.unencrypted_partition_length if partition_header.unencrypted_partition_length is not None else 'Not read'}")
                    print(f"    Total Partition Word Length: {partition_header.total_partition_word_length if partition_header.total_partition_word_length is not None else 'Not read'}")
                    print(f"    Next Partition Header Offset: {hex(partition_header.next_partition_header_offset) if partition_header.next_partition_header_offset is not None else 'Not read'} (words)")
                    # Display 64-bit addresses combined
                    if partition_header.destination_execution_address_hi is not None and partition_header.destination_execution_address_lo is not None:
                        exec_addr = (partition_header.destination_execution_address_hi << 32) | partition_header.destination_execution_address_lo
                        print(f"    Destination Execution Address: {hex(exec_addr)}")
                    else:
                        print(f"    Destination Execution Address: Not read")
                    if partition_header.destination_load_address_hi is not None and partition_header.destination_load_address_lo is not None:
                        load_addr = (partition_header.destination_load_address_hi << 32) | partition_header.destination_load_address_lo
                        print(f"    Destination Load Address: {hex(load_addr)}")
                    else:
                        print(f"    Destination Load Address: Not read")
                    print(f"    Actual Partition Word Offset: {hex(partition_header.actual_partition_word_offset) if partition_header.actual_partition_word_offset is not None else 'Not read'}")
                    print(f"    Attribute Bits: {hex(partition_header.attribute_bits) if partition_header.attribute_bits is not None else 'Not read'}")
                    print(f"      Trustzone: {partition_header.get_trustzone().name}")
                    print(f"      Exception Level: {partition_header.get_exception_level().name}")
                    print(f"      A5x Exec State: {partition_header.get_a5x_exec_state().name}")
                    print(f"      Destination Device: {partition_header.get_destination_device().name}")
                    print(f"      Encryption Present: {partition_header.get_encryption_present()}")
                    print(f"      Destination CPU  : {partition_header.get_destination_cpu().name}")
                    print(f"      Checksum Type: {partition_header.get_checksum_type().name}")
                    print(f"      RSA Signature Present: {partition_header.get_rsa_signature_present()}")
                    print(f"      Owner: {partition_header.get_partition_owner().name}")
                    print(f"      Endianness: {partition_header.get_endianness().name}")
                    print(f"      Early Handoff: {partition_header.get_early_handoff()}")
                    print(f"      Vector Location: {partition_header.get_vector_location().name}")
                    print(f"    Section Count: {partition_header.section_count if partition_header.section_count is not None else 'Not read'}")
                    print(f"    Checksum Word Offset: {hex(partition_header.checksum_word_offset) if partition_header.checksum_word_offset is not None else 'Not read'}")
                    print(f"    Image Header Word Offset: {hex(partition_header.image_header_word_offset) if partition_header.image_header_word_offset is not None else 'Not read'}")
                    print(f"    Authentication Certificate Word Offset: {hex(partition_header.authentication_certificate_word_offset) if partition_header.authentication_certificate_word_offset is not None else 'Not read'}")
                    print(f"    Partition ID: {partition_header.partition_id if partition_header.partition_id is not None else 'Not read'}")
                    print(f"    Header Checksum: {hex(partition_header.header_checksum) if partition_header.header_checksum is not None else 'Not read'}")

    def _get_first_stage_loader_length(self, data_byte_offset: int, default_length: int) -> int:
        """Return the length for FSBL partition, or default_length otherwise.
        
        For ZynqMP, the FSBL (First Stage Boot Loader) partition length is stored
        in the boot header's total_fsbl_length field.
        
        Args:
            data_byte_offset: The data offset of the current partition.
            default_length: The default length from the partition header.
        
        Returns:
            The FSBL length if this is the FSBL partition, otherwise default_length.
        """
        if (self.boot_header.source_offset is not None and 
            data_byte_offset == self.boot_header.source_offset):
            return self.boot_header.total_fsbl_length
        return default_length
