# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
# All rights reserved.
# Confidential and Proprietary - Qualcomm Technologies, Inc.

import os
from SecureAppMetadata import _kDefaultPrivilegeIds
import SCons
'''
===========================================================================

FUNCTION get_cmnlib_exported_api_list(env, cmnlib, is_cmnlib_file):

DESCRIPTION
  This function performs following operations:
  1. Checks whether source is file or folder.
  2. Check whether file/ folder exists or not.
  3. If is_cmnlib_file is true:
      Read the dynamic symbol table from passed library file.
     If is_cmnlib_file is false:
      Parse all the code files in passed cmnlib folder and get
      a list of exposed API names.

Parameters
  1. Scons environment variable.
  2. Path to:
    a) libcmnlib.so(in case of SDK or deployed SSG environment).
    b) Source folder of commonlib directory(in case of SSG environment).
  3. Describes whether first parameter is file/ folder.
  4. While extracting exposed API's from source files in commonlib folder,
     API names are present after a certain keyword. ex:
     a) CMNLIB_EXPORT
     b) UC_PUBLIC_API
     This parameter holds one of these values if a folder is passed as first
     parameter.

RETURN VALUE
  List of all the exposed API's.

===========================================================================
'''
def get_cmnlib_exported_api_list(env, src_cmnlib, is_cmnlib_file, export_keyword=None):
  cmnlib_api_list = []
  # Check for existence of source file.
  if is_cmnlib_file:
    if not os.path.isfile(src_cmnlib):
      print(("Error: Unable to locate source file: " + src_cmnlib))
      exit(1)
    # Read the dynamic symbol table from passed libcmnlib.so file.
    # For Offtarget environment, the latest available LLVM is used from
    # /usr/lib/llvm-*/bin
    # Otherwise, LLVM used from
    # /pkg/qct/software/llvm/release/arm/<version>.
    # Using corresponding readelf version to read dynamic symbols from libcmnlib.so.
    # ->llvm-readelf* is used to support both:
    # 1. llvm-readelf in linux environment.
    # 2. llvm-readelf.exe in windows environment.
    if env.OfftargetTesting():
      try:
        libcmnlib_elf_data = os.popen(os.path.join(env.GetOfftargetLLVM(), "llvm-readelf*") + " --dyn-symbols " + src_cmnlib).read()
      except Exception as e:
        print("\nError: Please check if readelf command is available in system or not.")
        exit(1)
    else:
      try:
        libcmnlib_elf_data = os.popen(os.path.join(env.subst(env.get('LLVMROOT')), "bin", "llvm-readelf") + " --dyn-syms " + src_cmnlib).read()
      except Exception as e:
        print("\nError: Please check if readelf command is available in system or not.")
        exit(1)
    libcmnlib_elf_data = libcmnlib_elf_data.split("\n")
    # From the dynamic symbol table of the library file, fetch the list of API's.
    for line in libcmnlib_elf_data:
      line = line.lstrip().rstrip()
      line_list = str(line).split(" ")
      if "FUNC" in line_list:
        cmnlib_api_list.append(line_list[-1])
  else:
    # Check for existence of source folder.
    if os.path.exists(src_cmnlib):
      # Scan all files inside source folders and extract all the exposed API's.
      for r,d,f in os.walk(src_cmnlib):
        for file in f:
          # CPP file -> prxy_new.cpp. This is implemented in cmnlib for C++ support.
          if file.endswith('.c') or file.endswith('.cpp'):
            cur_file = os.path.join(r, file)
            # Extract out all locations where export_keyword is present.
            cur_file_index = 0
            cur_file_data = open(cur_file,"r").read()
            # Keep looping if export_keyword is present in file at a location ahead
            # of cur_file_index. 'cur_file_index' is the location till where the
            # current file has been read.
            while(cur_file_data.find(export_keyword,cur_file_index) != -1):
              cur_file_index = cur_file_data.find(export_keyword,cur_file_index)
              cur_file_index += len(export_keyword)
              '''
              Code is assumed to be as follows:
              export_keyword <return_type> <api_name>(arguments)
              Searching for 1st paranthesis ahead of location of export_keyword.
              Then back search for first space/newline character.
              Between these 2 locations lies the API name.
              '''
              cur_file_api_name_end_idx = cur_file_data.find("(",cur_file_index)
              # No further API names are present in the file.
              if cur_file_api_name_end_idx == -1:
                break
              # In some files inside commonlib source folder. code is present like:
              # CMNLIB_EXPORT <ret_type> CMNLIB_PREFIX(<api_name>).
              # In this case, cur_file_index is the begining of the API name, since, API
              # name is present after '('. Now, search for ')' and that is end of API name.
              # This change is required while collecting exposed API's from commonlib folder.
              if export_keyword == "CMNLIB_EXPORT":
                # CMNLIB_PREFIX keyword is used to add certain keyword before the commonlib
                # exposed API's. This is appended in offtarget environment but not in ontarget
                # environment. So, adding api name with and without prefix for both ontarget
                # and offtarget environments.
                if "CMNLIB_PREFIX" in cur_file_data[cur_file_index:cur_file_api_name_end_idx]:
                  cur_file_index = cur_file_api_name_end_idx
                  cur_file_api_name_end_idx = cur_file_data.find(")",cur_file_index)
                  cur_file_api_name = cur_file_data[cur_file_index : cur_file_api_name_end_idx]
                  cur_file_api_name = cur_file_api_name.rstrip().lstrip(" *(")
                  cmnlib_api_list.append(cur_file_api_name)
                  cmnlib_api_list.append("__wrap_"+cur_file_api_name)
                  continue
              # Back search for first space/newline character before '('.
              # Between (' ' or '\n') and '(' lies the API name.
              # Check whatever (' ' or '\n') comes first. while backsearching.
              cur_file_api_name_beg_idx = cur_file_data[cur_file_index : cur_file_api_name_end_idx-1].rfind(" ") + cur_file_index + 1
              cur_file_api_name_beg_idx1 = cur_file_data[cur_file_index : cur_file_api_name_end_idx-1].rfind("\n") + cur_file_index + 1
              cur_file_api_name_beg_idx = max(cur_file_api_name_beg_idx,cur_file_api_name_beg_idx1)
              # Extract out the API name between the 2 locations.
              cur_file_api_name = cur_file_data[cur_file_api_name_beg_idx : cur_file_api_name_end_idx]
              # Strip out extra characters from API name.
              cur_file_api_name = cur_file_api_name.rstrip().lstrip(" *")
              cmnlib_api_list.append(cur_file_api_name)
  cmnlib_api_list = set(cmnlib_api_list)
  unq_cmnlib_api_list = []
  for api in cmnlib_api_list:
    # While reading dynamic symbols during offtarget , '@' gets appended
    # at the end of API_names. Following strip is to remove it.
    api = api.rstrip('@')
    unq_cmnlib_api_list.append(api)
  return unq_cmnlib_api_list

'''
===========================================================================

FUNCTION query_supported_krnl_svc(env, service, default_svc_only=False):

DESCRIPTION
  This function performs following operations:
  1. Reads all the default services from the system.
  2. Checks if required service is part of default privileges.
  3. If service not found in default services, check whether 
     "default_svc_only" is set to true or not. This flag implies
     whether service passed is to be checked only in default
     services or not. If its true, return false.
  4. If default_svc_only is false, check the current build env for
     standalone TA/ SSG.
  5. Generates path to idl folder of the current build environment.
  6. Scans the folder and subfolders to find all C*.idl files.
  7. Reads all such files and find out all such strings which have
     C<string>_uid = <some_number>.
  8. Match the required service with the string read from the idl
     files.

Parameters
  1. Scons environment variable.
  2. Required kernel service for the TA.
  3. Flag to check whether passed service is to be checked only in
     default services list.

RETURN VALUE
  True/ False -> Based on whether required service is present in either (default
  or idl files) or not.

===========================================================================
'''
def query_supported_krnl_svc(env, service, default_svc_only=False):
  # Check whether service is part of default system priveleges.
  if service in _kDefaultPrivilegeIds:
    return True
  # Check if default_svc_only is set to true. This implies service
  # is to be checked only in default services and it is not present.
  if default_svc_only:
    print(("Error: " + service + " service is not supported in system as default service."))
    return False
  # Path to IDL folder to scan all the available services.
  idl_folder = ""
  # In case current build environment is standalone TA build environment.
  if env.StandaloneSdk():
    if SCons.Script.ARGUMENTS.get('sdk_version') and SCons.Script.ARGUMENTS.get('sdk_version') not in ["TZ.XF.5.0", "latest"]:
      if ('C'+service+'_UID') in env['MINKIDL']:
        return True
      else:
        print(("Error: " + service + " service is not supported by the system."))
        return False
    # Fetch path to current SDK folder's IDL folder.
    idl_folder = os.path.join(env.get('SDK_ROOT').rstrip(), 'inc', 'idl')
  else:
    cmnlib_scons_file = os.path.join(env.get('BUILD_ROOT').rstrip(), 'ssg', 'securemsm', 'trustzone', 'qsapps', 'libs', 'cmnlib', 'build','SConscript')
    # Check if current evironment is SSG build environment.
    if os.path.isfile(cmnlib_scons_file):
      # Fetch path to SSG component IDL folder.
      idl_folder = os.path.join(env.get('BUILD_ROOT').rstrip(), 'ssg', 'securemsm', 'trustzone', 'qsee', 'idl')
    # Check if current evironment is deployed SSG build environment.
    else:
      # Fetch path to deployed SSG component IDL folder.
      idl_folder = os.path.join(env.get('INC_ROOT').rstrip(), 'ssg', 'securemsm', 'trustzone', 'qsee', 'idl')
  # Check for existence of current idl folder.
  if not os.path.exists(idl_folder):
    print(("Error: Unable to locate IDL files location: " + idl_folder))
    exit(1)
  # Store all kerenel services present inside idl folder to mink_uid.
  krnl_svc = []
  # Extract all the kernel services in the IDL folder.
  for r,d,f in os.walk(idl_folder):
    for file in f:
      if file.endswith('.idl') and file.startswith("C"):
        cur_file=os.path.join(r, file)
        all_lines=open(cur_file,"r").readlines()
        uid=""
        string=""
        for line in all_lines:
          if "_UID" in line and "=" in line:
            line_list = ((line.rstrip('\n')).split(';')[0]).split('=')
            uid=" "
            if "0x" in line_list[-1]:
              uid=hex(int(line_list[-1], 16))
            else:
              uid=hex(int(str(line_list[-1])))
            string=(line_list[0].rstrip(' ')).split(' ')[-1]
            krnl_svc.append(string)
  # Check if queried service is supported by system or not.
  if ('C'+service+'_UID') in krnl_svc:
    return True
  else:
    print(("Error: " + service + " service is not supported by the system."))
    return False

'''
===========================================================================

FUNCTION query_supported_cmnlib_api(env,cmnlib_api):

DESCRIPTION
  This function performs following operations:
  1. Check the current build env -> standalone TA/ SSG.
  2. Finds one of the following case:
    a) If standalone env, gets path to libcmnlib.so file of the current SDK.
    b) If SSG env, generates path to commonlib and uclib folder of SSG component.
    c) If deployed SSG env, generates path to respective libcmnlib.so file.
  3. Collect all the exposed API names based on cases for point 2:
    a) Calls api to parse the libcmnlib.so file and fetch all the API's exposed.
    b) Calls api to read and parse all the files present inside this folder and
       extract exported API names.
  4. Check whether required cmnlib_api is present in the list of exposed API's or not.

Parameters
  1. Scons environment variable.
  2. API to be checked in current commonlib exposed API's.

RETURN VALUE
  True/ False -> Based on whether the cmnlib_api has been exposed by
  commonlib or not.

===========================================================================
'''
def query_supported_cmnlib_api(env, cmnlib_api):
  # List to store all the exported commonlib API's.
  cmnlib_api_list = []
  # Check if current evironment is standalone TA environment.
  if env.StandaloneSdk():
    if SCons.Script.ARGUMENTS.get('sdk_version') and SCons.Script.ARGUMENTS.get('sdk_version') not in ["TZ.XF.5.0", "latest"]:
      if cmnlib_api in env['CMNLIB_SUPPORTED_APIS']:
        return True
      else:
        print(("Error: " + cmnlib_api + " API is not supported by commonlib library."))
        return False
    # Generate path to libcmnlib.so file
    sdk_libcmnlib_file = os.path.join(env.get('SDK_ROOT').rstrip(), 'libs', env.get('APP_EXEC_MODE'), 'libcmnlib.so')
    if 'ARM_AARCH' in env and env['ARM_AARCH'] == 'armv9' and env['APP_EXEC_MODE'] not in ['x86-32', 'x86-64', 'aarch32']:
      sdk_libcmnlib_file = os.path.join(env.get('SDK_ROOT').rstrip(), 'libs', 'armv9', env.get('APP_EXEC_MODE'), 'libcmnlib.so')
    cmnlib_api_list = get_cmnlib_exported_api_list(env, sdk_libcmnlib_file, True)
  else:
    cmnlib_scons_file = os.path.join(env.get('BUILD_ROOT').rstrip(), 'ssg', 'securemsm', 'trustzone', 'qsapps', 'libs', 'cmnlib', 'build','SConscript')
    # Check if current evironment is SSG build environment.
    if os.path.isfile(cmnlib_scons_file):
      # Fetch path to commonlib source folder of SSG component.
      ssg_cmnlib_src_folder = os.path.join(env.get('BUILD_ROOT').rstrip(), 'ssg', 'securemsm', 'trustzone', 'qsapps', 'libs', 'cmnlib')
      cmnlib_api_list = get_cmnlib_exported_api_list(env, ssg_cmnlib_src_folder, False, "CMNLIB_EXPORT")
      # Fetch path to another commonlib source folder of SSG component.
      ssg_cmnlib_src_folder = os.path.join(env.get('BUILD_ROOT').rstrip(), 'ssg', 'securemsm', 'trustzone', 'qsapps', 'libs', 'common')
      cmnlib_api_list += get_cmnlib_exported_api_list(env, ssg_cmnlib_src_folder, False, "CMNLIB_EXPORT")
      # Fetch path to ucliblib source folder.
      uclib_src_folder = os.path.join(env.get('BUILD_ROOT').rstrip(), 'uclib')
      cmnlib_api_list += get_cmnlib_exported_api_list(env, uclib_src_folder, False, "UC_PUBLIC_API")
    # Check if current evironment is deployed SSG build environment.
    else:
      # Generate path to libcmnlib.so file
      sdk_libcmnlib_file = os.path.join(env.get('INC_ROOT').rstrip(), 'ssg', 'bsp', 'trustzone', 'qsapps', 'commonlib', 'build', env.get('SHORT_BUILDPATH'), env.get('PROC'), 'libcmnlib.so')
      if 'ARM_AARCH' in env and env['ARM_AARCH'] == 'armv9' and env['APP_EXEC_MODE'] not in ['x86-32', 'x86-64', 'aarch32']:
        sdk_libcmnlib_file = os.path.join(env.get('INC_ROOT').rstrip(), 'ssg', 'bsp', 'trustzone', 'qsapps', 'commonlib', 'build', env.get('SHORT_BUILDPATH'), 'armv9', env.get('PROC'), 'libcmnlib.so') 
      cmnlib_api_list = get_cmnlib_exported_api_list(env, sdk_libcmnlib_file, True)
  # Verify whether TA passed API is exposed by commonlib or not.
  if cmnlib_api in cmnlib_api_list:
    return True
  else:
    print(("Error: " + cmnlib_api + " API is not supported by commonlib library."))
    return False
