text: #!/usr/bin/env python3
  # SPDX-License-Identifier: BSD-3-Clause
  #
  # Copyright (c) 2019, Intel Corporation. All rights reserved.
  #
  # Author: Marcin Maka <marcin.maka@linux.intel.com>
  
  """ Parses manifests included in sof binary and prints extracted metadata
      in readable form.
  """
  
  # pylint: disable=too-few-public-methods
  # pylint: disable=too-many-arguments
  # pylint: disable=too-many-instance-attributes
  
  import sys
  import argparse
  import struct
  
  # Public keys signatures recognized by parse_css_manifest()
  # - add a new one as array of bytes and append entry to KNOWN_KEYS below.
  
  APL_INTEL_PROD_KEY = bytes([0x1f, 0xf4, 0x58, 0x74, 0x64, 0xd4, 0xae, 0x90,
                              0x03, 0xb6, 0x71, 0x0d, 0xb5, 0xaf, 0x6d, 0xd6,
                              0x96, 0xce, 0x28, 0x95, 0xd1, 0x5b, 0x40, 0x59,
                              0xcd, 0xdf, 0x0c, 0x55, 0xd2, 0xc1, 0xbd, 0x58,
                              0xc3, 0x0d, 0x83, 0xe2, 0xac, 0xfa, 0xe0, 0xcc,
                              0x54, 0xf6, 0x5f, 0x72, 0xc2, 0x11, 0x05, 0x93,
                              0x1d, 0xb7, 0xe4, 0x4f, 0xa4, 0x95, 0xf5, 0x84,
                              0x77, 0x07, 0x24, 0x6e, 0x72, 0xce, 0x57, 0x41,
                              0xf2, 0x0b, 0x49, 0x49, 0x0c, 0xe2, 0x76, 0xf8,
                              0x19, 0xc7, 0x9f, 0xe1, 0xca, 0x77, 0x20, 0x1b,
                              0x5d, 0x1d, 0xed, 0xee, 0x5c, 0x54, 0x1d, 0xf6,
                              0x76, 0x14, 0xce, 0x6a, 0x24, 0x80, 0xc9, 0xce,
                              0x2e, 0x92, 0xe9, 0x35, 0xc7, 0x1a, 0xe9, 0x97,
                              0x7f, 0x25, 0x2b, 0xa8, 0xf3, 0xc1, 0x4d, 0x6b,
                              0xae, 0xd9, 0xcd, 0x0c, 0xbb, 0x08, 0x6d, 0x2b,
                              0x01, 0x44, 0xe2, 0xb9, 0x44, 0x4e, 0x4d, 0x5c,
                              0xdf, 0x8a, 0x89, 0xa5, 0x3c, 0x27, 0xa0, 0x54,
                              0xde, 0xc5, 0x5b, 0xde, 0x58, 0x10, 0x8c, 0xaa,
                              0xc4, 0x37, 0x5b, 0x73, 0x58, 0xfb, 0xe3, 0xcf,
                              0x57, 0xf5, 0x65, 0xd3, 0x19, 0x06, 0xed, 0x36,
                              0x47, 0xb0, 0x91, 0x67, 0xec, 0xc1, 0xe1, 0x7b,
                              0x4f, 0x85, 0x66, 0x61, 0x31, 0x99, 0xfc, 0x98,
                              0x7a, 0x56, 0x70, 0x95, 0x85, 0x52, 0xa0, 0x30,
                              0x37, 0x92, 0x11, 0x9e, 0x7f, 0x33, 0x44, 0xd3,
                              0x81, 0xfd, 0x14, 0x74, 0x51, 0x1c, 0x01, 0x14,
                              0xc8, 0x4b, 0xf6, 0xd6, 0xeb, 0x67, 0xef, 0xfc,
                              0x0a, 0x5f, 0xcc, 0x31, 0x73, 0xf8, 0xa9, 0xe3,
                              0xcb, 0xb4, 0x8b, 0x91, 0xa1, 0xf0, 0xb9, 0x6e,
                              0x1f, 0xea, 0xd3, 0xa3, 0xe4, 0x0f, 0x96, 0x74,
                              0x3c, 0x17, 0x5b, 0x68, 0x7c, 0x87, 0xfc, 0x90,
                              0x10, 0x89, 0x23, 0xca, 0x5d, 0x17, 0x5b, 0xc1,
                              0xb5, 0xc2, 0x49, 0x4e, 0x2a, 0x5f, 0x47, 0xc2])
  
  CNL_INTEL_PROD_KEY = bytes([0x41, 0xa0, 0x3e, 0x14, 0x1e, 0x7e, 0x29, 0x72,
                              0x89, 0x97, 0xc2, 0xa7, 0x7d, 0xbc, 0x1d, 0x25,
                              0xf4, 0x9a, 0xa8, 0xb7, 0x89, 0x10, 0x73, 0x31,
                              0x58, 0xbd, 0x46, 0x55, 0x78, 0xcf, 0xd9, 0xe1,
                              0x7d, 0xfa, 0x24, 0x23, 0xfa, 0x5c, 0x7c, 0xc9,
                              0x3d, 0xc8, 0xb5, 0x74, 0x87, 0xa, 0x8c, 0xe7,
                              0x33, 0xc2, 0x71, 0x26, 0xb1, 0x4d, 0x32, 0x45,
                              0x23, 0x17, 0xcb, 0xa6, 0xa2, 0xd0, 0xcc, 0x9e,
                              0x2b, 0xa6, 0x9, 0x42, 0x52, 0xf1, 0xe6, 0xbd,
                              0x73, 0x92, 0x2a, 0xfb, 0x7f, 0xc4, 0x8d, 0x5,
                              0xec, 0x69, 0x7f, 0xd4, 0xa2, 0x6c, 0x46, 0xd4,
                              0x5d, 0x92, 0x1d, 0x17, 0x75, 0x39, 0x16, 0x4c,
                              0x61, 0xa8, 0xda, 0x93, 0xd6, 0x26, 0x23, 0xa,
                              0xc8, 0x2d, 0xcc, 0x81, 0xf4, 0xcc, 0x85, 0x42,
                              0xaa, 0xa3, 0x15, 0x8, 0x62, 0x8f, 0x72, 0x9b,
                              0x5f, 0x90, 0x2f, 0xd5, 0x94, 0xdc, 0xad, 0xf,
                              0xa9, 0x8, 0x8c, 0x2e, 0x20, 0xf4, 0xdf, 0x12,
                              0xf, 0xe2, 0x1e, 0xeb, 0xfb, 0xf7, 0xe9, 0x22,
                              0xef, 0xa7, 0x12, 0x3d, 0x43, 0x3b, 0x62, 0x8e,
                              0x2e, 0xeb, 0x78, 0x8, 0x6e, 0xd0, 0xb0, 0xea,
                              0x37, 0x43, 0x16, 0xd8, 0x11, 0x5a, 0xb5, 0x5,
                              0x60, 0xf2, 0x91, 0xa7, 0xaa, 0x7d, 0x7, 0x17,
                              0xb7, 0x5b, 0xec, 0x45, 0xf4, 0x4a, 0xaf, 0x5c,
                              0xa3, 0x30, 0x62, 0x8e, 0x4d, 0x63, 0x2, 0x2,
                              0xed, 0x4b, 0x1f, 0x1b, 0x9a, 0x2, 0x29, 0x9,
                              0xc1, 0x7a, 0xc5, 0xeb, 0xc7, 0xdb, 0xa1, 0x6f,
                              0x61, 0x31, 0xfa, 0x7b, 0x3b, 0xe0, 0x6a, 0x1c,
                              0xee, 0x55, 0xed, 0xf0, 0xf9, 0x7a, 0xaf, 0xaa,
                              0xc7, 0x76, 0xf5, 0xfb, 0x6a, 0xbc, 0x65, 0xde,
                              0x42, 0x3e, 0x1c, 0xdf, 0xcc, 0x69, 0x75, 0x1,
                              0x38, 0x8, 0x66, 0x20, 0xea, 0x6, 0x91, 0xb8,
                              0xcd, 0x1d, 0xfa, 0xfd, 0xe8, 0xa0, 0xba, 0x91])
  
  ICL_INTEL_PROD_KEY = bytes([0x63, 0xdf, 0x54, 0xe3, 0xc1, 0xe5, 0xd9, 0xd2,
                              0xb8, 0xb5, 0x13, 0xb3, 0xec, 0xc2, 0x64, 0xb5,
                              0x16, 0xb4, 0xfc, 0x56, 0x92, 0x67, 0x17, 0xc7,
                              0x91, 0x7b, 0x3d, 0xb0, 0x22, 0xbf, 0x7f, 0x92,
                              0x39, 0x35, 0xcc, 0x64, 0x1c, 0xad, 0x8, 0x75,
                              0xe7, 0x67, 0xb, 0x8, 0xf8, 0x57, 0xdb, 0x9c,
                              0xde, 0xab, 0xe, 0xbd, 0x27, 0x5f, 0x5, 0x51,
                              0xcf, 0x6e, 0x3e, 0xc9, 0xdd, 0xe6, 0x51, 0x14,
                              0x57, 0xe1, 0x8a, 0x23, 0xae, 0x7a, 0xa5, 0x5f,
                              0xdc, 0x16, 0x13, 0x1b, 0x28, 0x3b, 0xab, 0xf1,
                              0xc3, 0xb5, 0x73, 0xc0, 0x72, 0xd8, 0x86, 0x7a,
                              0x76, 0x3a, 0x2, 0xbe, 0x2f, 0x3e, 0xfe, 0x93,
                              0x83, 0xa1, 0xd, 0xa0, 0xfc, 0x26, 0x7f, 0x6b,
                              0x2e, 0x5a, 0xfd, 0xac, 0x6b, 0x53, 0xd3, 0xb8,
                              0xff, 0x5e, 0xc7, 0x5, 0x25, 0xff, 0xe7, 0x78,
                              0x9c, 0x45, 0xe4, 0x17, 0xbd, 0xf4, 0x52, 0x4e,
                              0x3c, 0xa2, 0xa, 0x4d, 0x54, 0xb5, 0x40, 0x30,
                              0xb3, 0x48, 0xba, 0x6c, 0xfa, 0x63, 0xc0, 0x65,
                              0x2e, 0xde, 0x9, 0x2e, 0xa1, 0x95, 0x85, 0xc0,
                              0x78, 0xd9, 0x98, 0x64, 0x3c, 0x29, 0x2e, 0x48,
                              0x66, 0x1e, 0xaf, 0x1d, 0xa0, 0x7c, 0x15, 0x3,
                              0x7f, 0x9e, 0x5f, 0x38, 0xf5, 0xc1, 0xe1, 0xe9,
                              0xbe, 0x77, 0xa2, 0x9c, 0x83, 0xf2, 0x25, 0x54,
                              0x22, 0xfe, 0x29, 0x66, 0x5, 0xc2, 0xc9, 0x6b,
                              0x8b, 0xa6, 0xa3, 0xf9, 0xb1, 0x6b, 0xaf, 0xe7,
                              0x14, 0x77, 0xff, 0x17, 0xc9, 0x7c, 0x7c, 0x4e,
                              0x83, 0x28, 0x2a, 0xe5, 0xc3, 0xcc, 0x6e, 0x25,
                              0xa, 0x62, 0xbb, 0x97, 0x44, 0x86, 0x7c, 0xa2,
                              0xd4, 0xf1, 0xd4, 0xf8, 0x8, 0x17, 0xf4, 0x6c,
                              0xcc, 0x95, 0x99, 0xd4, 0x86, 0x37, 0x4, 0x9f,
                              0x5, 0x76, 0x1b, 0x44, 0x55, 0x75, 0xd9, 0x32,
                              0x35, 0xf1, 0xec, 0x4d, 0x93, 0x73, 0xe6, 0xc4])
  
  COMMUNITY_KEY = bytes([0x85, 0x00, 0xe1, 0x68, 0xaa, 0xeb, 0xd2, 0x07,
                         0x1b, 0x7c, 0x5e, 0xed, 0xd6, 0xe7, 0xe5, 0xf9,
                         0xc1, 0x0e, 0x47, 0xd4, 0x4c, 0xab, 0x8c, 0xf0,
                         0xe8, 0xee, 0x8b, 0x40, 0x36, 0x35, 0x58, 0x8f,
                         0xf4, 0x6f, 0xfc, 0xfd, 0x0f, 0xdd, 0x55, 0x8b,
                         0x45, 0x8c, 0xf0, 0x47, 0xdc, 0xb4, 0xac, 0x21,
                         0x3b, 0x4b, 0x20, 0xe6, 0x81, 0xb3, 0xcc, 0x90,
                         0xd4, 0x5e, 0xf1, 0xa4, 0x9b, 0x68, 0x52, 0xc8,
                         0xf1, 0x2d, 0xf9, 0xc4, 0x77, 0xc6, 0x4d, 0xa9,
                         0x90, 0xc7, 0x10, 0xfd, 0x43, 0xc8, 0x4b, 0x6b,
                         0x23, 0x5e, 0x92, 0xf5, 0x8f, 0xac, 0xd5, 0x7d,
                         0x60, 0x27, 0x36, 0x7c, 0x21, 0x4e, 0x21, 0x99,
                         0xde, 0xcb, 0xc0, 0x45, 0xf3, 0x04, 0x22, 0xb8,
                         0x7d, 0x16, 0x68, 0x40, 0xf9, 0x5c, 0xf0, 0xb9,
                         0x7e, 0x8c, 0x05, 0xb6, 0xfc, 0x28, 0xbb, 0x3d,
                         0xd8, 0xff, 0xb6, 0xa4, 0xd4, 0x54, 0x27, 0x3b,
                         0x1a, 0x42, 0x4e, 0xf5, 0xa6, 0xa8, 0x5e, 0x44,
                         0xe2, 0x9e, 0xed, 0x68, 0x6a, 0x27, 0x60, 0x13,
                         0x8d, 0x2f, 0x27, 0x70, 0xcd, 0x57, 0xc9, 0x18,
                         0xa3, 0xb0, 0x30, 0xa1, 0xf4, 0xe6, 0x32, 0x12,
                         0x89, 0x2a, 0xaf, 0x40, 0xa5, 0xfd, 0x52, 0xf1,
                         0xaa, 0x8a, 0xa4, 0xef, 0x20, 0x3d, 0x10, 0xa3,
                         0x70, 0xf2, 0x39, 0xc5, 0x05, 0x99, 0x22, 0x10,
                         0x81, 0x83, 0x6e, 0x45, 0xa4, 0xf3, 0x5a, 0x9d,
                         0x6a, 0xb8, 0x88, 0xfe, 0x69, 0x40, 0xd1, 0xb1,
                         0xcb, 0x2a, 0xdb, 0x28, 0x05, 0xde, 0x54, 0xbf,
                         0x3d, 0x86, 0x5f, 0x39, 0x8b, 0xc1, 0xf4, 0xaf,
                         0x00, 0x61, 0x86, 0x01, 0xfa, 0x22, 0xac, 0xf6,
                         0x2c, 0xa4, 0x17, 0x6a, 0xa7, 0xd8, 0x0a, 0x8c,
                         0x9f, 0xbf, 0x1f, 0x62, 0xb2, 0x2e, 0x68, 0x52,
                         0x3f, 0x82, 0x8f, 0xe5, 0x28, 0x4d, 0xdb, 0xb5,
                         0x5a, 0x96, 0x28, 0x27, 0x19, 0xaf, 0x43, 0xb9])
  
  KNOWN_KEYS = {APL_INTEL_PROD_KEY : 'APL Intel prod key',
                CNL_INTEL_PROD_KEY : 'CNL Intel prod key',
                ICL_INTEL_PROD_KEY : 'ICL Intel prod key',
                COMMUNITY_KEY : 'Community key'}
  
  def parse_params():
      """ Parses parameters
      """
      parser = argparse.ArgumentParser(description='SOF Binary Info Utility')
      parser.add_argument('-v', '--verbose', help='increase output verbosity',
                          action='store_true')
      parser.add_argument('--headers', help='display headers only',
                          action='store_true')
      parser.add_argument('--full_bytes', help='display full byte arrays',
                          action='store_true')
      parser.add_argument('--no_colors', help='disable colors in output',
                          action='store_true')
      parser.add_argument('sof_ri_path', help='path to fw binary file to parse')
      parsed_args = parser.parse_args()
  
      return parsed_args
  
  # Helper Functions
  
  def change_color(color):
      """ Prints escape code to change text color
      """
      color_code = {'red':91, 'green':92, 'yellow':93, 'blue':94,
                    'magenta':95, 'cyan':96, 'white':98, 'none':0}
      return '\033[{}m'.format(color_code[color])
  
  def uint_to_string(uint, both=False):
      """ Prints uint in readable form
      """
      if both:
          return hex(uint) + ' (' + repr(uint) + ')'
      return hex(uint)
  
  def date_to_string(date):
      """ Prints BCD date in readable form
      """
      return date[2:6]+'/'+date[6:8]+'/'+date[8:10]
  
  def chararr_to_string(chararr, max_len):
      """ Prints array of characters (null terminated or till max_len)
          in readable form
      """
      out = ''
      for i in range(0, max_len):
          if chararr[i] == 0:
              return out
          out += '{:c}'.format(chararr[i])
      return out
  
  def mod_type_to_string(mod_type):
      """ Prints module type in readable form
      """
      out = '('
      # type
      if (mod_type & 0xf) == 0:
          out += ' builtin'
      elif (mod_type & 0xf) == 1:
          out += ' loadable'
      # Module that may be instantiated by fw on startup
      if ((mod_type >> 4) & 0x1) == 1:
          out += ' auto_start'
      # Module designed to run with low latency scheduler
      if ((mod_type >> 5) & 0x1) == 1:
          out += ' LL'
      # Module designed to run with edf scheduler
      if ((mod_type >> 6) & 0x1) == 1:
          out += ' DP'
      out += ' )'
      return out
  
  def seg_flags_to_string(flags):
      """ Prints module segment flags in readable form
      """
      out = '('
      if flags & 0x1 == 0x1:
          out = out + ' contents'
      if flags & 0x2 == 0x2:
          out = out + ' alloc'
      if flags & 0x4 == 0x4:
          out = out + ' load'
      if flags & 0x8 == 0x8:
          out = out + ' readonly'
      if flags & 0x10 == 0x10:
          out = out + ' code'
      if flags & 0x20 == 0x20:
          out = out + ' data'
      out = out + ' type=' + repr((flags>>8)&0xf)
      out = out + ' pages=' + repr((flags>>16)&0xffff)
      out = out + ' )'
      return out
  
  # Parsers
  
  def parse_extended_manifest_ae1(reader):
      ext_mft = ExtendedManifestAE1()
      hdr = Component('ext_mft_hdr', 'Header', 0)
      ext_mft.add_comp(hdr)
  
      sig = reader.read_bytes(4).decode()
      reader.info('Extended Manifest (' + sig + ')', -4)
      hdr.add_a(Astring('sig', sig))
  
      # Next dword is the total length of the extended manifest
      # (need to use it for further parsing)
      reader.ext_mft_length = reader.read_dw()
      hdr.add_a(Auint('length', reader.ext_mft_length))
      hdr.add_a(Astring('ver', '{}.{}'.format(reader.read_w(), reader.read_w())))
      hdr.add_a(Auint('entries', reader.read_dw()))
  
      reader.ff_data(reader.ext_mft_length-16)
      return ext_mft
  
  def parse_extended_manifest_xman(reader):
      ext_mft = ExtendedManifestXMan()
      hdr = Component('ext_mft_hdr', 'Header', 0)
      ext_mft.add_comp(hdr)
  
      sig = reader.read_bytes(4).decode()
      reader.info('Extended Manifest (' + sig + ')', -4)
      hdr.add_a(Astring('sig', sig))
  
      # Next dword is the total length of the extended manifest
      # (need to use it for further parsing)
      reader.ext_mft_length = reader.read_dw()
      hdr_length = reader.read_dw()
      hdr_ver = reader.read_dw()
  
      major = hdr_ver >> 24
      minor = (hdr_ver >> 12) & 0xFFF
      patch = hdr_ver & 0xFFF
  
      hdr.add_a(Auint('length', reader.ext_mft_length))
      hdr.add_a(Astring('ver', '{}.{}.{}'.format(major, minor, patch)))
      hdr.add_a(Auint('hdr_length', hdr_length))
  
      reader.ff_data(reader.ext_mft_length-16)
      return ext_mft
  
  def parse_extended_manifest(reader):
      """ Parses extended manifest from sof binary
      """
  
      # Try to detect signature first
      sig = reader.read_bytes(4).decode()
      reader.set_offset(0)
      if sig == '$AE1':
          ext_mft = parse_extended_manifest_ae1(reader)
      elif sig == 'XMan':
          ext_mft = parse_extended_manifest_xman(reader)
      else:
          ext_mft = ExtendedManifestAE1()
          hdr = Component('ext_mft_hdr', 'Header', 0)
          ext_mft.add_comp(hdr)
          reader.info('info: Extended Manifest not found (sig = '+sig+')')
          reader.ext_mft_length = 0
          hdr.add_a(Auint('length', reader.ext_mft_length))
  
      return ext_mft
  
  def parse_cse_manifest(reader, add_module_entries):
      """ Parses CSE manifest form sof binary
      """
      cse_mft = CseManifest(reader.get_offset())
  
      # Try to detect signature first
      sig = reader.read_bytes(4).decode()
      if sig != '$CPD':
          reader.error('CSE Manifest NOT found ' + sig + ')', -4)
          sys.exit(1)
      reader.info('CSE Manifest (' + sig + ')', -4)
  
      # Read the header
      hdr = Component('cse_mft_hdr', 'Header', reader.get_offset())
      cse_mft.add_comp(hdr)
      hdr.add_a(Astring('sig', sig))
      # read number of entries
      nb_entries = reader.read_dw()
      hdr.add_a(Adec('nb_entries', nb_entries))
      # read version (1byte for header ver and 1 byte for entry ver)
      ver = reader.read_w()
      hdr.add_a(Ahex('header_version', ver))
      header_length = reader.read_b()
      hdr.add_a(Ahex('header_length', header_length))
      hdr.add_a(Ahex('checksum', reader.read_b()))
      hdr.add_a(Astring('partition_name', reader.read_bytes(4).decode()))
  
      reader.set_offset(cse_mft.file_offset + header_length)
      # Read entries
      nb_index = 0
      while nb_index < nb_entries:
          entry_name = reader.read_bytes(12).decode()
          entry_offset = reader.read_dw()
          entry_length = reader.read_dw()
          # reserved field
          reader.read_dw()
  
          hdr_entry = Component('cse_hdr_entry', 'Entry', reader.get_offset())
          hdr_entry.add_a(Astring('entry_name', entry_name))
          hdr_entry.add_a(Ahex('entry_offset', entry_offset))
          hdr_entry.add_a(Ahex('entry_length', entry_length))
          hdr.add_comp(hdr_entry)
  
          if '.man' in entry_name:
              entry = CssManifest(entry_name, entry_offset)
              cur_off = reader.set_offset(reader.ext_mft_length + entry_offset)
              parse_css_manifest(entry, reader,
                                 reader.ext_mft_length + entry_offset + entry_length)
              reader.set_offset(cur_off)
          elif '.met' in entry_name:
              cur_off = reader.set_offset(reader.ext_mft_length + entry_offset)
              entry = parse_mft_extension(reader, 0)
              entry.name = '{} ({})'.format(entry_name, entry.name)
              reader.set_offset(cur_off)
          else:
              # override offset with precise one
              cur_off = reader.set_offset(reader.ext_mft_length + 0x2000)
              entry = parse_adsp_manifest(reader, entry_name, add_module_entries)
              reader.set_offset(cur_off)
          cse_mft.add_comp(entry)
  
          nb_index += 1
  
      return cse_mft
  
  def parse_css_manifest(css_mft, reader, limit):
      """ Parses CSS manifest from sof binary
      """
      ver, = struct.unpack('I', reader.get_data(0, 4))
      if ver == 4:
          reader.info('CSS Manifest type 4')
          return parse_css_manifest_4(css_mft, reader, limit)
  
      reader.error('CSS Manifest NOT found or NOT recognized!')
      sys.exit(1)
  
  def parse_css_manifest_4(css_mft, reader, size_limit):
      """ Parses CSS manifest type 4 from sof binary
      """
  
      # CSS Header
      hdr = Component('css_mft_hdr', 'Header', reader.get_offset())
      css_mft.add_comp(hdr)
  
      hdr.add_a(Auint('type', reader.read_dw()))
      header_len_dw = reader.read_dw()
      hdr.add_a(Auint('header_len_dw', header_len_dw))
      hdr.add_a(Auint('header_version', reader.read_dw()))
      hdr.add_a(Auint('reserved0', reader.read_dw(), 'red'))
      hdr.add_a(Ahex('mod_vendor', reader.read_dw()))
      hdr.add_a(Adate('date', hex(reader.read_dw())))
      size = reader.read_dw()
      hdr.add_a(Auint('size', size))
      hdr.add_a(Astring('header_id', reader.read_bytes(4).decode()))
      hdr.add_a(Auint('padding', reader.read_dw()))
      hdr.add_a(Aversion('fw_version', reader.read_w(), reader.read_w(),
                         reader.read_w(), reader.read_w()))
      hdr.add_a(Auint('svn', reader.read_dw()))
      reader.read_bytes(18*4)
      modulus_size = reader.read_dw()
      hdr.add_a(Adec('modulus_size', modulus_size))
      exponent_size = reader.read_dw()
      hdr.add_a(Adec('exponent_size', exponent_size))
      modulus = reader.read_bytes(modulus_size * 4)
      hdr.add_a(Amodulus('modulus', modulus, KNOWN_KEYS.get(modulus, 'Other')))
      hdr.add_a(Abytes('exponent', reader.read_bytes(exponent_size * 4)))
      hdr.add_a(Abytes('signature', reader.read_bytes(modulus_size * 4)))
  
      # Move right after the header
      reader.set_offset(css_mft.file_offset + header_len_dw*4)
  
      # Anything packed here is
      #   either an 'Extension' beginning with
      #     dw0 - extension type
      #     dw1 - extension length (in bytes)
      #   that could be parsed if extension type is recognized
      #
      #   or series of 0xffffffff that should be skipped
      ext_idx = 0
      while reader.get_offset() < size_limit:
          ext_type = reader.read_dw()
          if ext_type == 0xffffffff:
              continue
          reader.set_offset(reader.get_offset() - 4)
          css_mft.add_comp(parse_mft_extension(reader, ext_idx))
          ext_idx += 1
  
      return css_mft
  
  def parse_mft_extension(reader, ext_id):
      """ Parses mft extension from sof binary
      """
      ext_type = reader.read_dw()
      ext_len = reader.read_dw()
      if ext_type == 15:
          begin_off = reader.get_offset()
          ext = PlatFwAuthExtension(ext_id, reader.get_offset()-8)
          ext.add_a(Astring('name', reader.read_bytes(4).decode()))
          ext.add_a(Auint('vcn', reader.read_dw()))
          ext.add_a(Abytes('bitmap', reader.read_bytes(16), 'red'))
          ext.add_a(Auint('svn', reader.read_dw()))
          read_len = reader.get_offset() - begin_off
          reader.ff_data(ext_len - read_len)
      elif ext_type == 17:
          ext = AdspMetadataFileExt(ext_id, reader.get_offset()-8)
          ext.add_a(Auint('adsp_imr_type', reader.read_dw(), 'red'))
          # skip reserved part
          reader.read_bytes(16)
          reader.read_dw()
          reader.read_dw()
          #
          ext.add_a(Auint('version', reader.read_dw()))
          ext.add_a(Abytes('sha_hash', reader.read_bytes(32)))
          ext.add_a(Auint('base_offset', reader.read_dw()))
          ext.add_a(Auint('limit_offset', reader.read_dw()))
          ext.add_a(Abytes('attributes', reader.read_bytes(16)))
      else:
          ext = MftExtension(ext_id, 'Other Extension', reader.get_offset()-8)
          reader.ff_data(ext_len-8)
      ext.add_a(Auint('type', ext_type))
      ext.add_a(Auint('length', ext_len))
      return ext
  
  def parse_adsp_manifest_hdr(reader):
      """ Parses ADSP manifest hader from sof binary
      """
      # Verify signature
      try:
          sig = reader.read_bytes(4).decode()
      except UnicodeDecodeError:
          print('\n' + reader.offset_to_string() + \
                '\terror: Failed to decode signature, wrong position?')
          sys.exit(1)
      if sig != '$AM1':
          reader.error('ADSP Manifest NOT found!', -4)
          sys.exit(1)
      reader.info('ADSP Manifest (' + sig + ')', -4)
  
      hdr = Component('adsp_mft_hdr', 'ADSP Manifest Header',
                      reader.get_offset() -4)
      hdr.add_a(Astring('sig', sig))
  
      hdr.add_a(Auint('size', reader.read_dw()))
      hdr.add_a(Astring('name', chararr_to_string(reader.read_bytes(8), 8)))
      hdr.add_a(Auint('preload', reader.read_dw()))
      hdr.add_a(Auint('fw_image_flags', reader.read_dw()))
      hdr.add_a(Auint('feature_mask', reader.read_dw()))
      hdr.add_a(Aversion('build_version', reader.read_w(), reader.read_w(),
                         reader.read_w(), reader.read_w()))
  
      hdr.add_a(Adec('num_module_entries', reader.read_dw()))
      hdr.add_a(Ahex('hw_buf_base_addr', reader.read_dw()))
      hdr.add_a(Auint('hw_buf_length', reader.read_dw()))
      hdr.add_a(Ahex('load_offset', reader.read_dw()))
  
      return hdr
  
  def parse_adsp_manifest_mod_entry(index, reader):
      """ Parses ADSP manifest module entry from sof binary
      """
      # Verify Mod Entry signature
      try:
          sig = reader.read_bytes(4).decode()
      except UnicodeDecodeError:
          print(reader.offset_to_string() + \
                '\terror: Failed to decode ModuleEntry signature')
          sys.exit(1)
      if sig != '$AME':
          reader.error('ModuleEntry signature NOT found!')
          sys.exit(1)
      reader.info('Module Entry signature found (' + sig + ')', -4)
  
      mod = Component('mod_entry_'+repr(index), 'Module Entry',
                      reader.get_offset() -4)
      mod.add_a(Astring('sig', sig))
  
      mod.add_a(Astring('mod_name',
                        chararr_to_string(reader.read_bytes(8), 8)))
      mod.add_a(Astring('uuid', reader.read_uuid()))
      me_type = reader.read_dw()
      mod.add_a(Astring('type',
                        hex(me_type) + ' ' + mod_type_to_string(me_type)))
      mod.add_a(Abytes('hash', reader.read_bytes(32)))
      mod.add_a(Ahex('entry_point', reader.read_dw()))
      mod.add_a(Adec('cfg_offset', reader.read_w()))
      mod.add_a(Adec('cfg_count', reader.read_w()))
      mod.add_a(Auint('affinity_mask', reader.read_dw()))
      mod.add_a(Adec('instance_max_count', reader.read_w()))
      mod.add_a(Auint('instance_stack_size', reader.read_w()))
      for i in range(0, 3):
          seg_flags = reader.read_dw()
          mod.add_a(Astring('seg_'+repr(i)+' flags',
                            hex(seg_flags) + ' ' + seg_flags_to_string(seg_flags)))
          mod.add_a(Ahex('seg_'+repr(i)+' v_base_addr', reader.read_dw()))
          mod.add_a(Ahex('seg_'+repr(i)+' file_offset', reader.read_dw()))
  
      return mod
  
  def parse_adsp_manifest(reader, name, add_module_entries):
      """ Parses ADSP manifest from sof binary
      """
      adsp_mft = AdspManifest(name, reader.get_offset())
      adsp_mft.add_comp(parse_adsp_manifest_hdr(reader))
      num_module_entries = adsp_mft.cdir['adsp_mft_hdr'].adir['num_module_entries'].val
      for i in range(0, num_module_entries):
          mod_entry = parse_adsp_manifest_mod_entry(i, reader)
          if add_module_entries:
              adsp_mft.add_comp(mod_entry)
  
      return adsp_mft
  
  def parse_fw_bin(path, verbose, add_module_entries):
      """ Parses sof binary
      """
      reader = BinReader(path, verbose)
  
      parsed_bin = FwBin()
      parsed_bin.add_a(Astring('file_name', reader.file_name))
      parsed_bin.add_a(Auint('file_size', reader.file_size))
      parsed_bin.add_comp(parse_extended_manifest(reader))
      parsed_bin.add_comp(parse_cse_manifest(reader, add_module_entries))
  
      if verbose:
          print('Parsing finished')
      return parsed_bin
  
  class BinReader():
      """ sof binary reader
      """
      def __init__(self, path, verbose):
          self.verbose = verbose
          if self.verbose:
              print('Reading SOF ri image ' + path)
          self.file_name = path
          # read the content
          self.data = open(path, 'rb').read()
          self.file_size = len(self.data)
          if self.verbose:
              print('File size ' + uint_to_string(self.file_size, True))
          self.cur_offset = 0
  
      def get_offset(self):
          """ Retrieve the offset, the reader is at
          """
          return self.cur_offset
  
      def ff_data(self, delta_offset):
          """ Forwards the read pointer by specified number of bytes
          """
          self.cur_offset += delta_offset
  
      def set_offset(self, offset):
          """ Set current reader offset
          """
          old_offset = self.cur_offset
          self.cur_offset = offset
          return old_offset
  
      def get_data(self, beg, length):
          """ Retrieves the data from beg to beg+length.
              This one is good to peek the data w/o advancing the read pointer
          """
          return self.data[self.cur_offset +beg : self.cur_offset +beg +length]
  
      def read_bytes(self, count):
          """ Reads the specified number of bytes from the stream
          """
          bts = self.get_data(0, count)
          self.ff_data(count)
          return bts
  
      def read_dw(self):
          """ Reads a dword from the stream
          """
          dword, = struct.unpack('I', self.get_data(0, 4))
          self.ff_data(4)
          return dword
  
      def read_w(self):
          """ Reads a word from the stream
          """
          word, = struct.unpack('H', self.get_data(0, 2))
          self.ff_data(2)
          return word
  
      def read_b(self):
          """ Reads a byte from the stream
          """
          byte, = struct.unpack('B', self.get_data(0, 1))
          self.ff_data(1)
          return byte
  
      def read_uuid(self):
          """ Reads a UUID from the stream and returns as string
          """
          out = '{:08x}'.format(self.read_dw())
          out += '-'+'{:04x}'.format(self.read_w())
          out += '-'+'{:04x}'.format(self.read_w())
          out += '-'+'{:02x}'.format(self.read_b()) + \
                 '{:02x}'.format(self.read_b()) + '-'
          for _ in range(0, 6):
              out += '{:02x}'.format(self.read_b())
          return out
  
      def offset_to_string(self, delta=0):
          """ Retrieves readable representation of the current offset value
          """
          return uint_to_string(self.cur_offset+delta)
  
      def info(self, loginfo, off_delta=0, verb_info=True):
          """ Prints 'info' log to the output, respects verbose mode
          """
          if verb_info and not self.verbose:
              return
          print(self.offset_to_string(off_delta) + '\t' + loginfo)
  
      def error(self, logerror, off_delta=0):
          """ Prints 'error' log to the output
          """
          print(self.offset_to_string(off_delta) + '\terror: ' + logerror)
  
  # Data Model
  
  class Attribute():
      """ Attribute: base class with global formatting options
      """
      no_colors = False
      full_bytes = True
  
  class Auint(Attribute):
      """ Attribute : unsigned integer
      """
      def __init__(self, name, val, color='none'):
          self.name = name
          self.val = val
          self.color = color
  
      def __str__(self):
          if Attribute.no_colors:
              return uint_to_string(self.val)
          return '{}{}{}'.format(change_color(self.color),
                                 uint_to_string(self.val),
                                 change_color('none'))
  
  class Ahex(Attribute):
      """ Attribute : unsigned integer printed as hex
      """
      def __init__(self, name, val, color='none'):
          self.name = name
          self.val = val
          self.color = color
  
      def __str__(self):
          if Attribute.no_colors:
              return hex(self.val)
          return '{}{}{}'.format(change_color(self.color), hex(self.val),
                                 change_color('none'))
  
  class Adec(Attribute):
      """ Attribute: integer printed as dec
      """
      def __init__(self, name, val):
          self.name = name
          self.val = val
  
      def __str__(self):
          return repr(self.val)
  
  class Abytes(Attribute):
      """ Attribute: array of bytes
      """
      def __init__(self, name, val, color='none'):
          self.name = name
          self.val = val
          self.color = color
  
      def __str__(self):
          length = len(self.val)
          if Attribute.no_colors:
              out = ''
          else:
              out = '{}'.format(change_color(self.color))
          if Attribute.full_bytes or length <= 16:
              out += ' '.join(['{:02x}'.format(b) for b in self.val])
          else:
              out += ' '.join('{:02x}'.format(b) for b in self.val[:8])
              out += ' ... '
              out += ' '.join('{:02x}'.format(b) for b in self.val[length-8:length])
          if not Attribute.no_colors:
              out += '{}'.format(change_color('none'))
          return out
  
  class Adate(Attribute):
      """ Attribute: Date in BCD format
      """
      def __init__(self, name, val):
          self.name = name
          self.val = val
  
      def __str__(self):
          return date_to_string(self.val)
  
  class Astring(Attribute):
      """ Attribute: String
      """
      def __init__(self, name, val):
          self.name = name
          self.val = val
  
      def __str__(self):
          return self.val
  
  class Aversion(Attribute):
      """ Attribute: version
      """
      def __init__(self, name, major, minor, hotfix, build):
          self.name = name
          self.val = '{:d}.{:d}.{:d}.{:d}'.format(major, minor, hotfix, build)
  
      def __str__(self):
          return self.val
  
  class Amodulus(Abytes):
      """ Attribute: modulus from RSA public key
      """
      def __init__(self, name, val, val_type):
          super().__init__(name, val)
          self.val_type = val_type
  
      def __str__(self):
          out = super().__str__()
          if not Attribute.full_bytes:
              if Attribute.no_colors:
                  out += ' ({})'.format(self.val_type)
              else:
                  out += ' {}({}){}'.format(change_color('red'), self.val_type,
                                            change_color('none'))
          return out
  
  class Component():
      """ A component of sof binary
      """
      def __init__(self, uid, name, file_offset):
          self.uid = uid
          self.name = name
          self.file_offset = file_offset
          self.attribs = []
          self.adir = {}
          self.max_attr_name_len = 0
          self.components = []
          self.cdir = {}
  
      def add_a(self, attrib):
          """ Adds an attribute
          """
          self.max_attr_name_len = max(self.max_attr_name_len,
                                       len(attrib.name))
          self.attribs.append(attrib)
          self.adir[attrib.name] = attrib
  
      def add_comp(self, comp):
          """ Adds a nested component
          """
          self.components.append(comp)
          self.cdir[comp.uid] = comp
  
      def get_comp(self, comp_uid):
          """ Retrieves a nested component by id
          """
          for comp in self.components:
              if comp.uid == comp_uid:
                  return comp
          return None
  
      def dump_info(self, pref):
          """ Prints out the content (name, all attributes, and nested comps)
          """
          print(pref + self.name)
          for attrib in self.attribs:
              print("{:}  {:<{:}} {:}".format(pref, attrib.name,
                                              self.max_attr_name_len, attrib))
          self.dump_comp_info(pref)
  
      def dump_attrib_info(self, pref, attr_name):
          """ Prints out a single attribute
          """
          attrib = self.adir[attr_name]
          print("{:}  {:<{:}} {:}".format(pref, attrib.name,
                                          self.max_attr_name_len, attrib))
  
      def dump_comp_info(self, pref, name_filter=''):
          """ Prints out all nested components (filtered by name set to 'filter')
          """
          for comp in self.components:
              if comp.name == name_filter:
                  continue
              print()
              comp.dump_info(pref + '  ')
  
  class ExtendedManifestAE1(Component):
      """ Extended manifest
      """
      def __init__(self):
          super(ExtendedManifestAE1, self).__init__('ext_mft',
                                                 'Extended Manifest', 0)
  
      def dump_info(self, pref):
          hdr = self.cdir['ext_mft_hdr']
          if hdr.adir['length'].val == 0:
              return
          out = '{}{}'.format(pref, self.name)
          out += ' ver {}'.format(hdr.adir['ver'])
          out += ' entries {}'.format(hdr.adir['entries'])
          print(out)
          self.dump_comp_info(pref, name_filter='Header')
  
  class ExtendedManifestXMan(Component):
      """ Extended manifest
      """
      def __init__(self):
          super(ExtendedManifestXMan, self).__init__('ext_mft',
                                                 'Extended Manifest', 0)
  
      def dump_info(self, pref):
          hdr = self.cdir['ext_mft_hdr']
          if hdr.adir['length'].val == 0:
              return
          out = '{}{}'.format(pref, self.name)
          out += ' ver {}'.format(hdr.adir['ver'])
          out += ' length {}'.format(hdr.adir['length'].val)
          print(out)
          self.dump_comp_info(pref, name_filter='Header')
  
  class CseManifest(Component):
      """ CSE Manifest
      """
      def __init__(self, offset):
          super(CseManifest, self).__init__('cse_mft', 'CSE Manifest', offset)
  
      def dump_info(self, pref):
          hdr = self.cdir['cse_mft_hdr']
          print('{}{} ver {} checksum {} partition name {}'.
                format(pref,
                       self.name, hdr.adir['header_version'],
                       hdr.adir['checksum'], hdr.adir['partition_name']))
          self.dump_comp_info(pref, name_filter='Header')
  
  class CssManifest(Component):
      """ CSS Manifest
      """
      def __init__(self, name, offset):
          super(CssManifest, self).__init__('css_mft', name, offset)
  
      def dump_info(self, pref):
          hdr = self.cdir['css_mft_hdr']
          out = '{}{} (CSS Manifest)'.format(pref, self.name)
          out += ' type {}'.format(hdr.adir['type'])
          out += ' ver {}'.format(hdr.adir['header_version'])
          out += ' date {}'.format(hdr.adir['date'])
          print(out)
          print('{}  Rsvd0 {}'.
                format(pref, hdr.adir['reserved0']))
          print('{}  Modulus size (dwords) {}'.
                format(pref, hdr.adir['modulus_size']))
          print('{}    {}'.format(pref, hdr.adir['modulus']))
          print('{}  Exponent size (dwords) {}'.
                format(pref,
                       hdr.adir['exponent_size']))
          print('{}    {}'.format(pref, hdr.adir['exponent']))
          print('{}  Signature'.format(pref))
          print('{}    {}'.format(pref, hdr.adir['signature']))
          # super().dump_info(pref)
          self.dump_comp_info(pref, name_filter='Header')
  
  class MftExtension(Component):
      """ Manifest Extension
      """
      def __init__(self, ext_id, name, offset):
          super(MftExtension, self).__init__('mft_ext'+repr(ext_id), name,
                                             offset)
  
      def dump_info(self, pref):
          print('{}{} type {} length {}'.
                format(pref, self.name,
                       self.adir['type'], self.adir['length']))
  
  class PlatFwAuthExtension(MftExtension):
      """ Platform FW Auth Extension
      """
      def __init__(self, ext_id, offset):
          super(PlatFwAuthExtension,
                self).__init__(ext_id, 'Plat Fw Auth Extension', offset)
  
      def dump_info(self, pref):
          out = '{}{}'.format(pref, self.name)
          out += ' name {}'.format(self.adir['name'])
          out += ' vcn {}'.format(self.adir['vcn'])
          out += ' bitmap {}'.format(self.adir['bitmap'])
          out += ' svn {}'.format(self.adir['svn'])
          print(out)
  
  class AdspMetadataFileExt(MftExtension):
      """ ADSP Metadata File Extension
      """
      def __init__(self, ext_id, offset):
          super(AdspMetadataFileExt,
                self).__init__(ext_id, 'ADSP Metadata File Extension',
                               offset)
  
      def dump_info(self, pref):
          out = '{}{}'.format(pref, self.name)
          out += ' ver {}'.format(self.adir['version'])
          out += ' base offset {}'.format(self.adir['base_offset'])
          out += ' limit offset {}'.format(self.adir['limit_offset'])
          print(out)
          print('{}  IMR type {}'.format(pref, self.adir['adsp_imr_type']))
          print('{}  Attributes'.format(pref))
          print('{}    {}'.format(pref, self.adir['attributes']))
  
  class AdspManifest(Component):
      """ ADSP Manifest
      """
      def __init__(self, name, offset):
          super(AdspManifest, self).__init__('adsp_mft', name, offset)
  
      def dump_info(self, pref):
          hdr = self.cdir['adsp_mft_hdr']
          out = '{}{} (ADSP Manifest)'.format(pref, self.name)
          out += ' name {}'.format(hdr.adir['name'])
          out += ' build ver {}'.format(hdr.adir['build_version'])
          out += ' feature mask {}'.format(hdr.adir['feature_mask'])
          out += ' image flags {}'.format(hdr.adir['fw_image_flags'])
          print(out)
          print('{}  HW buffers base address {} length {}'.
                format(pref,
                       hdr.adir['hw_buf_base_addr'],
                       hdr.adir['hw_buf_length']))
          print('{}  Load offset {}'.format(pref,
                                            hdr.adir['load_offset']))
          self.dump_comp_info(pref, 'ADSP Manifest Header')
  
  class FwBin(Component):
      """ Parsed sof binary
      """
      def __init__(self):
          super(FwBin, self).__init__('bin', 'SOF Binary', 0)
  
      def dump_info(self, pref):
          """ Print out the content
          """
          print('SOF Binary {} size {}'.format(
              self.adir['file_name'], self.adir['file_size']))
          self.dump_comp_info(pref)
  
  def main(args):
      """ main function
      """
      Attribute.no_colors = args.no_colors
      Attribute.full_bytes = args.full_bytes
  
      fw_bin = parse_fw_bin(args.sof_ri_path, args.verbose, not args.headers)
      fw_bin.dump_info('')
  
  if __name__ == "__main__":
      ARGS = parse_params()
      main(ARGS)
  