def unpack(self, buf, offset):
        """Unpack an element header from an FFFF header buffer

        Unpacks an element header from an FFFF header buffer at the specified
        offset.  Returns a flag indicating if the unpacked element is an
        end-of-table marker
        """
        element_hdr = unpack_from("<LLLLL", buf, offset)
        type_class = element_hdr[0]
        self.element_type = type_class & 0x000000ff
        self.element_class = (type_class >> 8) & 0x00ffffff
        self.element_id = element_hdr[1]
        self.element_length = element_hdr[2]
        self.element_location = element_hdr[3]
        self.element_generation = element_hdr[4]

        # Get the element data into our tftf_blob
        if self.element_type != FFFF_ELEMENT_END_OF_ELEMENT_TABLE:
            # Create a TFTF blob and load the contents from the specified
            # TFTF file
            span_start = self.element_location
            span_end = span_start + self.element_length
            self.tftf_blob = Tftf(0, None)
            span_start = self.element_location
            span_end = span_start + self.element_length
            self.tftf_blob.load_tftf_from_buffer(buf[span_start:span_end])
            return False
        else:
            # EOT is always valid
            self.in_range = True
            self.aligned = True
            self.valid_type = True
            return True
    def unpack(self, buf, offset):
        """Unpack an element header from an FFFF header buffer

        Unpacks an element header from an FFFF header buffer at the specified
        offset.  Returns a flag indicating if the unpacked element is an
        end-of-table marker
        """
        element_hdr = unpack_from("<LLLLL", buf, offset)
        type_class = element_hdr[0]
        self.element_type = type_class & 0x000000ff
        self.element_class = (type_class >> 8) & 0x00ffffff
        self.element_id = element_hdr[1]
        self.element_length = element_hdr[2]
        self.element_location = element_hdr[3]
        self.element_generation = element_hdr[4]

        # Get the element data into our tftf_blob
        if self.element_type != FFFF_ELEMENT_END_OF_ELEMENT_TABLE:
            # Create a TFTF blob and load the contents from the specified
            # TFTF file
            span_start = self.element_location
            span_end = span_start + self.element_length
            self.tftf_blob = Tftf(0, None)
            span_start = self.element_location
            span_end = span_start + self.element_length
            self.tftf_blob.load_tftf_from_buffer(buf[span_start:span_end])
            return False
        else:
            # EOT is always valid
            self.in_range = True
            self.aligned = True
            self.valid_type = True
            return True
    def init(self):
        """FFFF Element post-constructor initializer

        Loads the element from TFTF file, setting the element length to
        that of the file.  Returns a success flag if the file was loaded
        (no file is treated as success).
        """
        success = True
        # Try to size it from the TFTF file
        if self.filename and not self.tftf_blob:
            # Create a TFTF blob and load the contents from the specified
            # TFTF file
            self.tftf_blob = Tftf(0, self.filename)
            success = self.tftf_blob.load_tftf_file(self.filename)
            if success and self.tftf_blob.is_good():
                # element_length must be that of the entire TFTF blob,
                # not just the TFTF's "load_length" or "expanded_length".
                self.element_length = self.tftf_blob.tftf_length
            else:
                raise ValueError("Bad TFTF file: {0:x}".format(self.filename))
        return True
    def init(self):
        """FFFF Element post-constructor initializer

        Loads the element from TFTF file, setting the element length to
        that of the file.  Returns a success flag if the file was loaded
        (no file is treated as success).
        """
        success = True
        # Try to size it from the TFTF file
        if self.filename and not self.tftf_blob:
            # Create a TFTF blob and load the contents from the specified
            # TFTF file
            self.tftf_blob = Tftf(0, self.filename)
            success = self.tftf_blob.load_tftf_file(self.filename)
            if success and self.tftf_blob.is_good():
                # element_length must be that of the entire TFTF blob,
                # not just the TFTF's "load_length" or "expanded_length".
                self.element_length = self.tftf_blob.tftf_length
            else:
                raise ValueError("Bad TFTF file: {0:x}".format(self.filename))
        return True
class FfffElement:
    """Defines the contents of a Flash Format for Firmware (FFFF) element table

    Each element describes a region of flash memory, its type and the
    corresponding blob stored there (typically a TFTF "file").
    """

    def __init__(self, index, buf, buf_size, erase_block_size,
                 element_type, element_class, element_id, element_length,
                 element_location, element_generation, filename=None):
        """Constructor

        Note: The optional filename is merely stored here.  It is used
        later in init()
        """

        # Private vars
        self.filename = filename
        self.tftf_blob = None
        self.buf = buf
        self.buf_size = buf_size
        self.index = index
        self.erase_block_size = erase_block_size
        self.collisions = []
        self.duplicates = []
        if element_type == FFFF_ELEMENT_END_OF_ELEMENT_TABLE:
            # EOT is always valid
            self.in_range = True
            self.aligned = True
            self.valid_type = True
        else:
            self.in_range = False
            self.aligned = False
            self.valid_type = False

        # Element fields
        self.element_type = element_type
        self.element_class = element_class
        self.element_id = element_id
        self.element_length = element_length
        self.element_location = element_location
        self.element_generation = element_generation

    def init(self):
        """FFFF Element post-constructor initializer

        Loads the element from TFTF file, setting the element length to
        that of the file.  Returns a success flag if the file was loaded
        (no file is treated as success).
        """
        success = True
        # Try to size it from the TFTF file
        if self.filename and not self.tftf_blob:
            # Create a TFTF blob and load the contents from the specified
            # TFTF file
            self.tftf_blob = Tftf(0, self.filename)
            success = self.tftf_blob.load_tftf_file(self.filename)
            if success and self.tftf_blob.is_good():
                # element_length must be that of the entire TFTF blob,
                # not just the TFTF's "load_length" or "expanded_length".
                self.element_length = self.tftf_blob.tftf_length
            else:
                raise ValueError("Bad TFTF file: {0:x}".format(self.filename))
        return True

    def unpack(self, buf, offset):
        """Unpack an element header from an FFFF header buffer

        Unpacks an element header from an FFFF header buffer at the specified
        offset.  Returns a flag indicating if the unpacked element is an
        end-of-table marker
        """
        element_hdr = unpack_from("<LLLLL", buf, offset)
        type_class = element_hdr[0]
        self.element_type = type_class & 0x000000ff
        self.element_class = (type_class >> 8) & 0x00ffffff
        self.element_id = element_hdr[1]
        self.element_length = element_hdr[2]
        self.element_location = element_hdr[3]
        self.element_generation = element_hdr[4]

        # Get the element data into our tftf_blob
        if self.element_type != FFFF_ELEMENT_END_OF_ELEMENT_TABLE:
            # Create a TFTF blob and load the contents from the specified
            # TFTF file
            span_start = self.element_location
            span_end = span_start + self.element_length
            self.tftf_blob = Tftf(0, None)
            span_start = self.element_location
            span_end = span_start + self.element_length
            self.tftf_blob.load_tftf_from_buffer(buf[span_start:span_end])
            return False
        else:
            # EOT is always valid
            self.in_range = True
            self.aligned = True
            self.valid_type = True
            return True

    def pack(self, buf, offset):
        """Pack an element header into an FFFF header

        Packs an element header into into the FFFF header buffer at the
        specified offset and returns the offset for the next element
        """
        type_class = (self.element_class << 8) | self.element_type
        pack_into("<LLLLL", buf, offset,
                  type_class,
                  self.element_id,
                  self.element_length,
                  self.element_location,
                  self.element_generation)
        return offset + FFFF_ELT_LENGTH

    def validate(self, address_range_low, address_range_high):
        # Validate an element header
        #
        # Returns True if valid, False otherwise

        # EOT is always valid
        if self.element_type == FFFF_ELEMENT_END_OF_ELEMENT_TABLE:
            self.in_range = True
            self.aligned = True
            self.valid_type = True
            return True

        # Do we overlap the header
        self.in_range = self.element_location >= address_range_low and \
            self.element_location < address_range_high
        if not self.in_range:
            error("Element location " + format(self.element_location, "#x") +
                  " falls outside address range " +
                  format(address_range_low, "#x") +
                  "-" + format(address_range_high, "#x"))

        # check for alignment and type
        self.aligned = block_aligned(self.element_location,
                                     self.erase_block_size)
        if not self.aligned:
            error("Element location " + format(self.element_location, "#x") +
                  " unaligned to block size " +
                  format(self.erase_block_size, "#x"))
        self.valid_type = self.element_type >= \
            FFFF_ELEMENT_STAGE2_FIRMWARE_PACKAGE and \
            self.element_type <= FFFF_ELEMENT_DATA
        return self.in_range and self.aligned and self.valid_type

    def validate_against(self, other):
        # Validate an element header against another element header

        # Check for collision
        start_a = self.element_location
        end_a = start_a + self.element_length - 1
        start_b = other.element_location
        end_b = start_b + other.element_length - 1
        if end_b >= start_a and start_b <= end_a:
            self.collisions += [other.index]

        # Check for other duplicate entries per the specification:
        # "At most, one element table entry with a particular element
        # type, element ID, and element generation may be present in
        # the element table."
        if self.element_type == other.element_type and \
           self.element_id == other.element_id and \
           self.element_generation == other.element_generation:
            self.duplicates += [other.index]

    def same_as(self, other):
        """Determine if this TFTF is identical to another"""

        return self.element_type == other.element_type and \
            self.element_class == other.element_class and \
            self.element_id == other.element_id and \
            self.element_length == other.element_length and \
            self.element_location == other.element_location and \
            self.element_generation == other.element_generation

    def write(self, filename):
        """Write an element to a file

        Write the FFFF element from the FFFF buffer as a binary blob to
        the specified file.
        """

        # Output the entire FFFF element blob (less padding)
        with open(filename, 'wb') as wf:
            wf.write(self.buf[self.element_location:
                              self.element_location +
                              self.element_length])
            print("Wrote", filename)

    def element_name(self, element_type):
        # Convert an element type into textual form
        if element_type in element_names:
            return element_names[element_type]
        else:
            return "?"

    def element_short_name(self, element_type):
        # Convert an element type into a (short) textual form
        if element_type in element_short_names:
            return element_short_names[element_type]

    def display_table_header(self):
        # Print the element table column names
        print("     Type Class  ID       Length   Location Generation")

    def display(self, expand_type):
        """Print an element header

        Print an element header in numeric form, optionally expanding
        the element type into a textual form
        """
        # Print the element data
        element_string = "  {0:2d}".format(self.index)
        element_string += " {0:02x}  ".format(self.element_type)
        element_string += " {0:06x}".format(self.element_class)
        element_string += " {0:08x}".format(self.element_id)
        element_string += " {0:08x}".format(self.element_length)
        element_string += " {0:08x}".format(self.element_location)
        element_string += " {0:08x}".format(self.element_generation)
        if expand_type:
            element_string += \
                " ({0:s})".format(self.element_name(self.element_type))
        print(element_string)

        # Note any collisions and duplicates on separate lines
        if len(self.collisions) > 0:
            element_string = "           Collides with element(s):"
            for collision in self.collisions[self.index]:
                element_string += " {0:d}".format(collision)
            print(element_string)

        if len(self.duplicates) > 0:
            element_string = "           Duplicates element(s):"
            for duplicate in self.duplicates[self.index]:
                element_string += " {0:d}".format(duplicate)
            print(element_string)

        # Note any other errors
        element_string = ""
        if not self.in_range:
            element_string += "Out-of-range "
        if not self.aligned:
            element_string += "Misaligned "
        if not self.valid_type:
            element_string += "Invalid-type "
        if len(element_string) > 0:
            error(element_string)

    def display_element_data(self, header_index):
        """Print the data blob associated with this element

        Print an element header's TFTF info
        """
        if self.element_type != FFFF_ELEMENT_END_OF_ELEMENT_TABLE:
            self.tftf_blob.display("element [{0:d}]".format(self.index), "  ")
            self.tftf_blob.display_data("element [{0:d}]".
                                        format(self.index), "  ")

    def write_map_payload(self, wf,  base_offset, prefix=""):
        """Display the field names and offsets of a single FFFF header"""
        elt_name = "{0:s}element[{1:d}].{2:s}".\
                   format(prefix, self.index,
                          self.element_short_name(self.element_type))

        # Dump the element starts
        if self.tftf_blob:
            # We've got a TFTF, pass that on to TFTF to display
            self.tftf_blob.write_map(wf, self.element_location, elt_name)
        elif self.element_type != FFFF_ELEMENT_END_OF_ELEMENT_TABLE:
            # Just print the element payload location
            wf.write("{0:s}  {1:08x}\n".
                     format(prefix, self.element_location))
class FfffElement:
    """Defines the contents of a Flash Format for Firmware (FFFF) element table

    Each element describes a region of flash memory, its type and the
    corresponding blob stored there (typically a TFTF "file").
    """
    def __init__(self,
                 index,
                 buf,
                 buf_size,
                 erase_block_size,
                 element_type,
                 element_class,
                 element_id,
                 element_length,
                 element_location,
                 element_generation,
                 filename=None):
        """Constructor

        Note: The optional filename is merely stored here.  It is used
        later in init()
        """

        # Private vars
        self.filename = filename
        self.tftf_blob = None
        self.buf = buf
        self.buf_size = buf_size
        self.index = index
        self.erase_block_size = erase_block_size
        self.collisions = []
        self.duplicates = []
        if element_type == FFFF_ELEMENT_END_OF_ELEMENT_TABLE:
            # EOT is always valid
            self.in_range = True
            self.aligned = True
            self.valid_type = True
        else:
            self.in_range = False
            self.aligned = False
            self.valid_type = False

        # Element fields
        self.element_type = element_type
        self.element_class = element_class
        self.element_id = element_id
        self.element_length = element_length
        self.element_location = element_location
        self.element_generation = element_generation

    def init(self):
        """FFFF Element post-constructor initializer

        Loads the element from TFTF file, setting the element length to
        that of the file.  Returns a success flag if the file was loaded
        (no file is treated as success).
        """
        success = True
        # Try to size it from the TFTF file
        if self.filename and not self.tftf_blob:
            # Create a TFTF blob and load the contents from the specified
            # TFTF file
            self.tftf_blob = Tftf(0, self.filename)
            success = self.tftf_blob.load_tftf_file(self.filename)
            if success and self.tftf_blob.is_good():
                # element_length must be that of the entire TFTF blob,
                # not just the TFTF's "load_length" or "expanded_length".
                self.element_length = self.tftf_blob.tftf_length
            else:
                raise ValueError("Bad TFTF file: {0:x}".format(self.filename))
        return True

    def unpack(self, buf, offset):
        """Unpack an element header from an FFFF header buffer

        Unpacks an element header from an FFFF header buffer at the specified
        offset.  Returns a flag indicating if the unpacked element is an
        end-of-table marker
        """
        element_hdr = unpack_from("<LLLLL", buf, offset)
        type_class = element_hdr[0]
        self.element_type = type_class & 0x000000ff
        self.element_class = (type_class >> 8) & 0x00ffffff
        self.element_id = element_hdr[1]
        self.element_length = element_hdr[2]
        self.element_location = element_hdr[3]
        self.element_generation = element_hdr[4]

        # Get the element data into our tftf_blob
        if self.element_type != FFFF_ELEMENT_END_OF_ELEMENT_TABLE:
            # Create a TFTF blob and load the contents from the specified
            # TFTF file
            span_start = self.element_location
            span_end = span_start + self.element_length
            self.tftf_blob = Tftf(0, None)
            span_start = self.element_location
            span_end = span_start + self.element_length
            self.tftf_blob.load_tftf_from_buffer(buf[span_start:span_end])
            return False
        else:
            # EOT is always valid
            self.in_range = True
            self.aligned = True
            self.valid_type = True
            return True

    def pack(self, buf, offset):
        """Pack an element header into an FFFF header

        Packs an element header into into the FFFF header buffer at the
        specified offset and returns the offset for the next element
        """
        type_class = (self.element_class << 8) | self.element_type
        pack_into("<LLLLL", buf, offset, type_class, self.element_id,
                  self.element_length, self.element_location,
                  self.element_generation)
        return offset + FFFF_ELT_LENGTH

    def validate(self, address_range_low, address_range_high):
        # Validate an element header
        #
        # Returns True if valid, False otherwise

        # EOT is always valid
        if self.element_type == FFFF_ELEMENT_END_OF_ELEMENT_TABLE:
            self.in_range = True
            self.aligned = True
            self.valid_type = True
            return True

        # Do we overlap the header
        self.in_range = self.element_location >= address_range_low and \
            self.element_location < address_range_high
        if not self.in_range:
            error("Element location " + format(self.element_location, "#x") +
                  " falls outside address range " +
                  format(address_range_low, "#x") + "-" +
                  format(address_range_high, "#x"))

        # check for alignment and type
        self.aligned = block_aligned(self.element_location,
                                     self.erase_block_size)
        if not self.aligned:
            error("Element location " + format(self.element_location, "#x") +
                  " unaligned to block size " +
                  format(self.erase_block_size, "#x"))
        self.valid_type = self.element_type >= \
            FFFF_ELEMENT_STAGE2_FIRMWARE_PACKAGE and \
            self.element_type <= FFFF_ELEMENT_DATA
        return self.in_range and self.aligned and self.valid_type

    def validate_against(self, other):
        # Validate an element header against another element header

        # Check for collision
        start_a = self.element_location
        end_a = start_a + self.element_length - 1
        start_b = other.element_location
        end_b = start_b + other.element_length - 1
        if end_b >= start_a and start_b <= end_a:
            self.collisions += [other.index]

        # Check for other duplicate entries per the specification:
        # "At most, one element table entry with a particular element
        # type, element ID, and element generation may be present in
        # the element table."
        if self.element_type == other.element_type and \
           self.element_id == other.element_id and \
           self.element_generation == other.element_generation:
            self.duplicates += [other.index]

    def same_as(self, other):
        """Determine if this TFTF is identical to another"""

        return self.element_type == other.element_type and \
            self.element_class == other.element_class and \
            self.element_id == other.element_id and \
            self.element_length == other.element_length and \
            self.element_location == other.element_location and \
            self.element_generation == other.element_generation

    def write(self, filename):
        """Write an element to a file

        Write the FFFF element from the FFFF buffer as a binary blob to
        the specified file.
        """

        # Output the entire FFFF element blob (less padding)
        with open(filename, 'wb') as wf:
            wf.write(self.buf[self.element_location:self.element_location +
                              self.element_length])
            print("Wrote", filename)

    def element_name(self, element_type):
        # Convert an element type into textual form
        if element_type in element_names:
            return element_names[element_type]
        else:
            return "?"

    def element_short_name(self, element_type):
        # Convert an element type into a (short) textual form
        if element_type in element_short_names:
            return element_short_names[element_type]

    def display_table_header(self):
        # Print the element table column names
        print("     Type Class  ID       Length   Location Generation")

    def display(self, expand_type):
        """Print an element header

        Print an element header in numeric form, optionally expanding
        the element type into a textual form
        """
        # Print the element data
        element_string = "  {0:2d}".format(self.index)
        element_string += " {0:02x}  ".format(self.element_type)
        element_string += " {0:06x}".format(self.element_class)
        element_string += " {0:08x}".format(self.element_id)
        element_string += " {0:08x}".format(self.element_length)
        element_string += " {0:08x}".format(self.element_location)
        element_string += " {0:08x}".format(self.element_generation)
        if expand_type:
            element_string += \
                " ({0:s})".format(self.element_name(self.element_type))
        print(element_string)

        # Note any collisions and duplicates on separate lines
        if len(self.collisions) > 0:
            element_string = "           Collides with element(s):"
            for collision in self.collisions[self.index]:
                element_string += " {0:d}".format(collision)
            print(element_string)

        if len(self.duplicates) > 0:
            element_string = "           Duplicates element(s):"
            for duplicate in self.duplicates[self.index]:
                element_string += " {0:d}".format(duplicate)
            print(element_string)

        # Note any other errors
        element_string = ""
        if not self.in_range:
            element_string += "Out-of-range "
        if not self.aligned:
            element_string += "Misaligned "
        if not self.valid_type:
            element_string += "Invalid-type "
        if len(element_string) > 0:
            error(element_string)

    def display_element_data(self, header_index):
        """Print the data blob associated with this element

        Print an element header's TFTF info
        """
        if self.element_type != FFFF_ELEMENT_END_OF_ELEMENT_TABLE:
            self.tftf_blob.display("element [{0:d}]".format(self.index), "  ")
            self.tftf_blob.display_data("element [{0:d}]".format(self.index),
                                        "  ")

    def write_map_payload(self, wf, base_offset, prefix=""):
        """Display the field names and offsets of a single FFFF header"""
        elt_name = "{0:s}element[{1:d}].{2:s}".\
                   format(prefix, self.index,
                          self.element_short_name(self.element_type))

        # Dump the element starts
        if self.tftf_blob:
            # We've got a TFTF, pass that on to TFTF to display
            self.tftf_blob.write_map(wf, self.element_location, elt_name)
        elif self.element_type != FFFF_ELEMENT_END_OF_ELEMENT_TABLE:
            # Just print the element payload location
            wf.write("{0:s}  {1:08x}\n".format(prefix, self.element_location))