# ===============================================================================
#  Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
#  All Rights Reserved.
#  Confidential and Proprietary - Qualcomm Technologies, Inc.
# ===============================================================================

import os
import posixpath
import SCons.Script
import json

# Extra f-strings are used for alignment/readability in this file
# pylint: disable=f-string-without-interpolation

# constants
OUTFILE_RECORD_FNAME = "signed_images.json"

# globals
# maps sign-id to a list of corresponding SCons nodes for unsigned MBNs
# to be included in a QTI MISC image
_QTI_MISC_VOUCH_FOR_IMAGES = {}


def exists(env):
    """Required SCons method for confirming module existence"""
    return env.Detect('sectools_glue')


def generate(env):
    """Modifies the environment of any SConscripts loading this toolchain"""
    # Load required toolchains
    if "SECTOOLS_DIR" in env:
        env.Tool('sectools_builder', toolpath=['${SECTOOLS_DIR}'])

    # Parse any signing arguments
    # We cannot ensure CASS validation until GetSignserverUrl is available,
    # but this at least makes the arguments accessible
    env.AddCommandlineArgument(('SIGNTYPE', 'SIGNTYPE', ''))
    env.AddCommandlineArgument(('SIGNSERVER', 'SIGNSERVER', ''))
    env.AddCommandlineArgument(('SIGNPORT', 'SIGNPORT', ''))

    # Add common env methods
    env.AddMethod(use_v2_signer, "UseV2Signer")
    env.AddMethod(get_sha256_dir, "GetSha256Dir")
    env.AddMethod(get_unsigned_dir, "GetUnsignedDir")
    env.AddMethod(get_signserver_url, "GetSignServerUrl")

    # Add legacy sectools v1 compatibility methods
    if "SectoolGetUnsignedInstallPath" not in env:
        env.AddMethod(get_unsigned_install_path, "SectoolGetUnsignedInstallPath")

    # Validate CASS signing arguments
    if env.get('SIGNTYPE') == 'CASS':
        if not env.get('SIGNSERVER'):
            raise ValueError(
                "Error: For SIGNTYPE CASS SIGNSERVER must be specified as "
                "\"SIGNSERVER = <hostname> \" ")

        os.environ['SECTOOLS_SIGNER_HOST'] = env.GetSignServerUrl()
        if env.get('SIGNPORT'):
            os.environ['SECTOOLS_SIGNER_PORT'] = env.get('SIGNPORT')

    # Add remaining env methods based on supported signer
    if env.UseV2Signer():
        env.AddMethod(get_cass_capability_v2, "GetCASSCapability_v2")
        env.AddMethod(get_signer_params_v2, "GetSignerParams_v2")
        env.AddMethod(generate_unsigned_v2, "GenerateUnsigned_v2")
        env.AddMethod(sign_for_qti_v2, "QTIExclusiveSign_v2")
        env.AddMethod(sign_for_oem_v2, "OEMExclusiveSign_v2")
        env.AddMethod(sha256_hash_seg_supported_chipsets, "SHA256HashSegSupportedChipsets")
        env.AddMethod(double_sign_v2, "DoubleSign_v2")
        env.AddMethod(get_profile_v2, "GetSecurityProfile_v2")
        env.AddMethod(add_to_qti_misc, "AddToQTIMisc")
        env.AddMethod(generate_qti_misc, "GenerateQTIMisc")
    else:
        env.AddMethod(get_default_config_v1, "GetDefaultConfig_v1")
        env.AddMethod(generate_unsigned, "GenerateUnsigned")
        env.AddMethod(sign_for_qti, "QTIExclusiveSign")
        env.AddMethod(sign_for_oem, "OEMExclusiveSign")
        env.AddMethod(double_sign, "DoubleSign")

def get_compatible_profiles(env,profiles):
    """returns a list of compatible profiles
    """
    compatible_chipsets_map = {'hamoa': ['purwa'],
                               'pakala': ['bonito', 'eliza'],
                               'pinnacles':['kobuk']
                               }
    compatible_profiles=[]

    # Some compatible chipsets may still require the profile of each individual
    # chipset (eg waipio/palima). Add each compatible chipset's profile.
    if env['CHIPSET'] in ["waipio","netrani"]:
        assert profiles is not None, \
        "Legacy logic failed to define a security_profile"  
        compatible_chipsets = ['palima']
        security_profile=profiles

        for chipset in compatible_chipsets:
            compatible_profiles.append(
                security_profile.replace("${CHIPSET}", chipset))

    elif env['CHIPSET'] in ["hamoa","pinnacles"]: 
        compatible_chipsets=compatible_chipsets_map[env['CHIPSET']]

        for chipset in compatible_chipsets:
            chipset_env=env.Clone()
            chipset_env['CHIPSET']=chipset
            profile = get_unified_profile_v2(chipset_env)
            compatible_profiles.append(chipset_env.subst(profile))

    elif env['CHIPSET'] == "pakala" and env.StandaloneSdk():
        compatible_chipsets = ['bonito', 'eliza']
        for chipset in compatible_chipsets:
            chipset_env = env.Clone()
            chipset_env['CHIPSET'] = chipset
            profile = get_unified_profile_v2(chipset_env)
            compatible_profiles.append(chipset_env.subst(profile))

    return compatible_profiles


# Common methods (shared between v1 & v2)
def use_v2_signer(env):
    """Returns True if the current chipset (as determined from the environment)
       should use the sectools v2 signer, else False for v1 signer."""
    return True


def sha256_hash_seg_supported_chipsets(env):
    """Returns True if sha256_hash_seg_supported_chipsets contains current chipset"""
    sha256_hash_seg_supported_chipsets = ['lemans','monaco']
    if env['CHIPSET'] in sha256_hash_seg_supported_chipsets:
        return True
    else:
        return False


def get_unsigned_dir(env):
    """Returns the directory for unsigned binaries, accounting for the SDK"""
    # In the standalone SDK environment, unsigned MBN binaries for TAs should
    # be generated in a chipset-independent location governed by
    # MBN_ROOT_UNSIGNED. If MBN_ROOT_UNSIGNED is not defined or this is not an
    # SDK environment, fall back to using the MBN_ROOT location.
    if env.StandaloneSdk() and env.get('MBN_ROOT_UNSIGNED') is not None:
        unsigned_root = env['MBN_ROOT_UNSIGNED']
    else:
        unsigned_root = env['MBN_ROOT']

    return os.path.join(unsigned_root, "unsigned")


def get_unsigned_install_path(env,
                              install_base_dir,
                              subfolder=None,
                              featureflags=None,
                              disable_featureflags=None,
                              policy_file=None):
    """Wraps get_unsigned_dir to ensure compatibility with legacy sectools v1
       SectoolGetUnsignedInstallPath calls."""
    return get_unsigned_dir(env)


def get_sha256_dir(env):
    """Returns the directory for SHA256 binaries"""
    if env.SHA256HashSegSupportedChipsets():
        sha256_dir_path = os.path.join(env['MBN_ROOT'],"sha256_mbns")
        try:
            os.makedirs(sha256_dir_path, exist_ok = True)
            print("Directory sha256_mbns created successfully")
        except OSError as error:
            print("Directory sha256_mbns can not be created")
    return os.path.join("${MBN_ROOT}", "sha256_mbns")


def get_signserver_url(env):
    """Reads the signing server from env["SIGNSERVER"] and transforms the value
       into a valid signing url, prepending a proper http* prefix if required.
       Returns the updated signserver url"""
    signserver = env.get("SIGNSERVER")
    if signserver is None:
        raise ValueError("SIGNSERVER must be defined in the environment")

    if not signserver.lower().startswith("http"):
        signserver = f"http://{signserver}"

    return signserver


# v2 signer methods
def get_cass_capability_v2(env):
    """Returns the appropriate CASS capability value to be passed to
       --cass-capability for the current chipset."""

    # HOYA architecture uses qtee_test_v4_qtee_release
    if env["CHIPSET"] in ['lemans','monaco','kodiak']:
        cass_capability = "qtee_test_v4_qtee_release"
    # Hash Segment v2.0 is needed only for legacy wildcat targets. Default for all new ones is v3.0
    elif env["CHIPSET"] in ['waipio', 'netrani','kailua', 'hamoa', 'lassen', 'palima', 'purwa']:
        cass_capability = "srot_test_v6_com0.0_ent2.0_srot_release"
    else:
        cass_capability = "srot_test_v6_com0.0_ent3.0_srot_release"

    return cass_capability


def get_signer_params_v2(env):
    """Assembles and returns appropriate signing server parameters for a
       secimage invocation based on SIGNSERVER/SIGNPORT in the current env."""
    signserver = env.get("SIGNSERVER")
    if not signserver:
        return "--signing-mode TEST"

    signport = env.get("SIGNPORT")
    if not signport:
        raise ValueError("Must provide a valid SIGNPORT")

    cass_capability = env.GetCASSCapability_v2()
    signserver_url = env.GetSignServerUrl()
    qti_signer_params = [
        f"--signing-mode QTI-REMOTE",
        f"--cass-capability {cass_capability}",
        f"--qti-remote-signing-server-port {signport}",
        f"--qti-remote-signing-server-url {signserver_url}",
    ]

    return " ".join(qti_signer_params)


def generate_unsigned_v2(env, input_elf, output_elf_name, image_id,
                         secondary_software_id=None, oem_id=None,
                         oem_product_id=None, security_profile=None,
                         qti=False, ensure_no_metadata=False):
    """Generates an unsigned MBN 'output_elf_name' from 'input_elf' using the
       other provided signing parameters. Returns a SCons node representing the
       unsigned MBN. If 'ensure_no_metadata' is True, rejects any additional
       parameters that may generate OEM or QTI metadata in the resulting
       unsigned binary."""
    if security_profile is None:
        security_profile = env.GetSecurityProfile_v2(env, image_id)

    platform_independent = True
    if secondary_software_id is not None:
        if ensure_no_metadata:
            raise ValueError(
                f"'secondary_software_id' conflicts with platform binding")
        platform_independent = False

    if oem_id is not None:
        if ensure_no_metadata:
            raise ValueError(
                f"'oem_id' would generate metadata for {output_elf_name}")
        platform_independent = False

    if oem_product_id is not None:
        if ensure_no_metadata:
            raise ValueError(
                f"'oem_product_id' would generate metadata for {output_elf_name}")
        platform_independent = False

    sectools = env["SECTOOLS"]
    output_elf_path = os.path.join(env.GetUnsignedDir(), output_elf_name)
    secure_image_command = [
        f"{sectools} secure-image $SOURCE",
        f"--outfile $TARGET",
        f"--security-profile {security_profile}",
        f"--image-id {image_id}",
        (f"--secondary-software-id {secondary_software_id}"
         if secondary_software_id is not None else ""),
        f"--platform-binding INDEPENDENT" if platform_independent else "",
        f"--hash",
        f"--qti" if qti else "",
        f"--oem-id {oem_id}" if oem_id is not None else "",
        (f"--oem-product-id {oem_product_id}"
         if oem_product_id is not None else ""),
    ]
    unsigned_mbn = env.Command(
        output_elf_path, input_elf, " ".join(secure_image_command))

    return unsigned_mbn


# sign_for_qti_v2 is added specifically to enable sectools v2 signing for
# the Lemans target, which is based on the Hoya architecture.
# QTI signing currently required only for:
# * Lemans xbl_sec
def sign_for_qti_v2(env, input_elf, output_elf_name, image_id,
                    secondary_software_id=None, security_profile=None):
    """Generates a QTI-signed MBN 'output_elf_name' from 'input_elf' using the
       other provided signing parameters. Returns a SCons node representing the
       QTI-signed MBN."""
    if security_profile is None:
        security_profile = env.GetSecurityProfile_v2(image_id)

    unsigned_mbn = env.GenerateUnsigned_v2(
        input_elf, output_elf_name, image_id=image_id,
        security_profile=security_profile, qti=True,
        ensure_no_metadata=True)

    sectools = env["SECTOOLS"]
    output_elf_path = os.path.join("${MBN_ROOT}", output_elf_name)
    qti_signer_params = env.GetSignerParams_v2()
    secure_image_command = [
        f"{sectools} secure-image $SOURCE",
        f"--outfile $TARGET",
        f"--security-profile {security_profile}",
        f"--image-id {image_id}",
        (f"--secondary-software-id {secondary_software_id}"
         if secondary_software_id is not None else ""),
        f"--sign",
        f"--qti {qti_signer_params}",
    ]
    qti_signed_mbn = env.Command(
        output_elf_path, unsigned_mbn, " ".join(secure_image_command))

    # Unsigned binaries were not originally returned from v2 signing functions.
    # They are added to the list rather than returned first for legacy support.
    return env.Flatten(qti_signed_mbn + unsigned_mbn)


def should_create_compressed(env, security_profile, image_id):
    """Determine if we should create compressed binaries"""

    if image_id in ["TZ-APP-OEM", "TZ-APP-QTI"]:
        if not env.TACompressionSupported():
            print("TA compression is not supported for ", env['CHIPSET'])
            return False
        if env.get('TA_PROP_CONFIG'):
            if True == env[env['TA_PROP_CONFIG']]['compress_ta_image']:
                return True

    if env['CHIPSET'] not in ['hamoa', 'glymur']:
        return False

    if image_id in ["TZ-DEVCFG", "TZ", "QHEE"]:
        return True

    return False


def sign_for_oem_v2(env, input_elf, output_elf_name, image_id,
                    secondary_software_id=None, oem_id=None,
                    oem_product_id=None, root_key=None,
                    root_certificate=None, signing_mode=None,
                    platform_binding=None, security_profile=None,
                    root_certificate_count=None, root_certificate_index=None):
    """Generates an OEM-signed MBN 'output_elf_name' from 'input_elf' using the
       other provided signing parameters. Returns a SCons node representing the
       OEM-signed MBN."""
    if security_profile is None:
        security_profile = env.GetSecurityProfile_v2(image_id)

    if root_key:
        root_key = env.RealPath(root_key)

    if root_certificate:
        root_certificate = env.RealPath(root_certificate)

    if signing_mode is None:
        signing_mode = "TEST"

    unsigned_mbn = env.GenerateUnsigned_v2(
        input_elf, output_elf_name, image_id=image_id,
        security_profile=security_profile, ensure_no_metadata=True)

    sectools = env['SECTOOLS']
    output_elf_path = os.path.join("${MBN_ROOT}", output_elf_name)
    secure_image_command = [
        f"{sectools} secure-image $SOURCE",
        f"--outfile {output_elf_path}",
        f"--outfile-record $BUILD_ROOT/{OUTFILE_RECORD_FNAME}",
        f"--security-profile {security_profile}",
        f"--image-id {image_id}",
        (f"--secondary-software-id {secondary_software_id}"
         if secondary_software_id is not None else ""),
        (f"--root-certificate-count {root_certificate_count}"
         if root_certificate_count is not None else ""),
        (f"--root-certificate-index {root_certificate_index}"
         if root_certificate_index is not None else ""),
        f"--sign",
        f"--platform-binding {platform_binding}" if platform_binding else "",
        f"--signing-mode", signing_mode,
        f"--root-certificate {root_certificate}" if root_certificate else "",
        f"--root-key {root_key}" if root_key else "",
        f"--oem-id {oem_id}" if oem_id is not None else "",
        (f"--oem-product-id {oem_product_id}"
         if oem_product_id is not None else ""),
    ]

    output_elf_paths = [output_elf_path]
    if should_create_compressed(env, security_profile, image_id):
        output_elf_compressed_path = os.path.join("${MBN_ROOT}", "compressed", output_elf_name)
        output_elf_paths.append(output_elf_compressed_path)
        secure_image_command.extend([f"--compress", f"--compressed-outfile {output_elf_compressed_path}"])

    oem_signed_mbns = env.Command(
        output_elf_paths, unsigned_mbn, " ".join(secure_image_command))

    # Unsigned binaries were not originally returned from v2 signing functions.
    # They are added to the list rather than returned first for legacy support.
    return oem_signed_mbns + unsigned_mbn


def double_sign_v2(env, input_elf, output_elf_name, image_id,
                   secondary_software_id=None, security_profile=None):
    """Generates a double-signed MBN 'output_elf_name' from 'input_elf' using
       the other provided signing parameters. Returns a SCons node representing
       the double-signed MBN and double-signed MBN for SHA256."""
    if security_profile is None:
        security_profile = env.GetSecurityProfile_v2(image_id)

    unsigned_mbn = env.GenerateUnsigned_v2(
        input_elf, output_elf_name, image_id=image_id,
        security_profile=security_profile, qti=True,
        ensure_no_metadata=True)

    sectools = env['SECTOOLS']
    output_elf_path = os.path.join("${MBN_ROOT}", output_elf_name)
    qti_signer_params = env.GetSignerParams_v2()

    if os.name != 'nt':
        secure_image_command = [
            f"{sectools} secure-image $SOURCE",
            f"--outfile {output_elf_path}",
            f"--security-profile {security_profile}",
            f"--image-id {image_id}",
            (f"--secondary-software-id {secondary_software_id}"
             if secondary_software_id is not None else ""),
            f"--sign",
            f"--qti {qti_signer_params} &&",
            f"{sectools} secure-image {output_elf_path}",
            f"--outfile {output_elf_path}",
            f"--outfile-record $BUILD_ROOT/{OUTFILE_RECORD_FNAME}",
            f"--security-profile {security_profile}",
            f"--image-id {image_id}",
            (f"--secondary-software-id {secondary_software_id}"
             if secondary_software_id is not None else ""),
            f"--sign --signing-mode TEST",
            ]
    else:
        single_sign_output_elf_name = output_elf_name.split('.')[0] + "_qti_signed.mbn"
        single_sign_output_elf_path = os.path.join("${MBN_ROOT}", single_sign_output_elf_name)
        single_sign_secure_image_command = [
            f"{sectools} secure-image $SOURCE",
            f"--outfile {single_sign_output_elf_path}",
            f"--security-profile {security_profile}",
            f"--image-id {image_id}",
            (f"--secondary-software-id {secondary_software_id}"
             if secondary_software_id is not None else ""),
            f"--sign",
            f"--qti {qti_signer_params}",
            ]
    
        single_signed_mbn = env.Command(
            single_sign_output_elf_path, unsigned_mbn, " ".join(single_sign_secure_image_command))
    
        double_sign_secure_image_command = [
            f"{sectools} secure-image {single_sign_output_elf_path}",
            f"--outfile {output_elf_path}",
            f"--outfile-record $BUILD_ROOT/{OUTFILE_RECORD_FNAME}",
            f"--security-profile {security_profile}",
            f"--image-id {image_id}",
            (f"--secondary-software-id {secondary_software_id}"
             if secondary_software_id is not None else ""),
            f"--sign --signing-mode TEST",
            ]

    output_elf_paths = [output_elf_path]
    if should_create_compressed(env, security_profile, image_id):
        output_elf_compressed_path = os.path.join("${MBN_ROOT}", "compressed", output_elf_name)
        output_elf_paths.append(output_elf_compressed_path)
        if os.name != 'nt':
            secure_image_command.extend([f"--compress", f"--compressed-outfile {output_elf_compressed_path}"])
        else:
            double_sign_secure_image_command.extend([f"--compress", f"--compressed-outfile {output_elf_compressed_path}"])

    if os.name != 'nt':
        double_signed_mbns = env.Command(
            output_elf_paths, unsigned_mbn, " ".join(secure_image_command))
    else:
        double_signed_mbns = env.Command(
            output_elf_paths, single_signed_mbn, " ".join(double_sign_secure_image_command))

    # Unsigned binaries were not originally returned from v2 signing functions.
    # They are added to the list rather than returned first for legacy support.
    mbn_list = double_signed_mbns + unsigned_mbn

    if env.SHA256HashSegSupportedChipsets():
        output_elf_sha256_path = os.path.join(env.GetSha256Dir(), output_elf_name)
        secure_sha256_image_command = [
        f"{sectools} secure-image $SOURCE",
        f"--outfile $TARGET",
        f"--security-profile {security_profile}",
        f"--image-id {image_id}",
        f"--segment-hash-algorithm SHA256",
        (f"--secondary-software-id {secondary_software_id}"
         if secondary_software_id is not None else ""),
        f"--sign",
        f"--qti {qti_signer_params} &&",
        f"{sectools} secure-image $TARGET",
        f"--outfile $TARGET",
        f"--outfile-record $BUILD_ROOT/{OUTFILE_RECORD_FNAME}",
        f"--security-profile {security_profile}",
        f"--image-id {image_id}",
        (f"--secondary-software-id {secondary_software_id}"
         if secondary_software_id is not None else ""),
        f"--sign --signing-mode TEST",
        ]
        double_signed_sha256 = env.Command(
            output_elf_sha256_path, unsigned_mbn, " ".join(secure_sha256_image_command))
        mbn_list.append(double_signed_sha256)

    # for mbn in signed_mbn:
    #     mbn_path = mbn.srcnode().abspath
    return env.Flatten(mbn_list)


def get_profile_v2(env, image_id):
    """Calls 'get_profile_legacy_v2' for chipsets that don't support unified
       security profiles. Returns the unified profile for others."""
    _legacy_v2_chipsets = [
        'aurora',
        'fillmore',
        'halliday',
        'hamoa',
        'kailua',
        'kuno',
        'lanai',
        'lassen',
        'lemans',
        'netrani',
        'palima',
        'pinnacles',
        'puna',
        'tofino',
        'waipio',
        'wales',
    ]

    chipset = env["CHIPSET"]

    if chipset in _legacy_v2_chipsets:
        profiles = get_legacy_profile_v2(env, image_id)
    else:
        profiles = get_unified_profile_v2(env)

    #profiles=env.subst(profiles)

    compatible_profiles = get_compatible_profiles(env, profiles)

    profiles=[profiles] +compatible_profiles
    

    # Validate that all returned profiles exist
    # If not, fail with a clear error
    for profile in profiles:
        profile = env.subst(profile)
        if not os.path.isfile(profile):
            raise ValueError(
                f"Expected {chipset} security profile does not exist: "
                f"{profile}")

    # Some chipsets may return multiple security profiles (e.g. Waipio/Palima).
    # The caller expects these to be returned a single ' '-separated string.
    return " ".join(profiles)


def get_unified_profile_v2(env):
    """Identifies and returns a single unified security profile for the current
       chipset(s)."""
    _tz_profile_root = "${BUILD_ROOT}/ssg"
    _sdk_profile_root = "${SDK_ROOT}/scripts"

    profile_root = _tz_profile_root
    if env.StandaloneSdk():
        profile_root = _sdk_profile_root

    chipset_profile = posixpath.join(
        profile_root, "${CHIPSET}_security_profile.xml")
    if not os.path.isfile(env.subst(chipset_profile)):
        # fall back to the stubs dummy profile
        chipset_profile = posixpath.join(
            profile_root, "stubs_security_profile.xml")

    profiles=chipset_profile

    # caller expects a single profile
    return profiles


def get_legacy_profile_v2(env, image_id):
    """Identifies & returns a list of valid security profile XMLs for the given
       'image_id'."""
    _tz_profile_root = "${BUILD_ROOT}/ssg"
    _sdk_profile_root = "${SDK_ROOT}/scripts"

    # Command line example to identify all image ids in a security profile:
    # sectools secure-image --security-profile <path to profile>
    # --available-image-ids
    security_profile = None
    if image_id == 'ADSP' and env['CHIPSET'] == 'lemans':
         # This image is validated via BOOT
         if env.StandaloneSdk():
             security_profile = posixpath.join(
                 _sdk_profile_root,
                 "${CHIPSET}_boot_security_profile.xml")
         else:
             security_profile = posixpath.join(
                 _tz_profile_root, "imports",
                 "${CHIPSET}_boot_security_profile.xml")
    elif image_id in ['ABL', 'ADSP', 'APPS-CPU-GUEST-OS', 'CAMERA-FW', 'CDSP',
                    'CMNLIB', 'CPUSYS-VM', 'EVA-FW', 'GPU-MICRO-CODE',
                    'IPA-FW', 'IPA-MICRO-CODE', 'IP-PROTECTOR', 'MCP-OEM',
                    'MLVM', 'NPU-FW', 'OEM-VM', 'QUPV3', 'SLPI-SSC-SENSORS-FW',
                    'TZ-APP-OEM', 'TZ-APP-OEM-BACKWARDS-COMPATIBLE',
                    'TZ-APP-QTI', 'TZ-TEE', 'UEFIFV', 'VENUS-FW']:
        # The image is validated via TZ
        if env.StandaloneSdk():
            security_profile = posixpath.join(
                _sdk_profile_root,
                "${CHIPSET}_tz_security_profile.xml")
        else:
            security_profile = posixpath.join(
                _tz_profile_root,
                "${CHIPSET}_tz_security_profile.xml")
    elif env['CHIPSET'] in ['lemans', 'monaco', 'kodiak']:
        # The image is validated via BOOT
        if env.StandaloneSdk():
            security_profile = posixpath.join(
                _sdk_profile_root,
                "${CHIPSET}_boot_security_profile.xml")
        else:
            security_profile = posixpath.join(
                _tz_profile_root, "imports",
                "${CHIPSET}_boot_security_profile.xml")
    else:
        # The image is validated via TME
        if env.StandaloneSdk():
            security_profile = posixpath.join(
                _sdk_profile_root,
                "${CHIPSET}_tme_security_profile.xml")
        else:
            security_profile = posixpath.join(
                _tz_profile_root, "imports",
                "${CHIPSET}_tme_security_profile.xml")


    return security_profile


def add_to_qti_misc(env, unsigned_mbn, image_id):
    """Adds an 'unsigned_mbn' & its corresponding 'image_id' to the list of
       binaries to be combined into the QTI MISC image when GenerateQTIMisc
       is called later."""
    unsigned_images = _QTI_MISC_VOUCH_FOR_IMAGES.get(image_id, [])
    if unsigned_mbn not in unsigned_images:
        unsigned_images.append(unsigned_mbn)
        _QTI_MISC_VOUCH_FOR_IMAGES[image_id] = unsigned_images


def generate_qti_misc(env):
    """Generates the QTI MISC image from all inputs added via AddToQTIMisc"""
    assert "MBN_ROOT" in env, "MBN_ROOT must be defined in env"

    security_profile = env.GetSecurityProfile_v2("QTI-MISC")

    # Generate matched lists of mbn paths & their sign ids
    # Note that SCons input nodes are tracked separately. Passing them into
    # env.Command will enforce proper build dependencies.
    input_nodes = []
    mbn_paths = []
    image_ids = []
    for image_id, mbn_list in sorted(list(_QTI_MISC_VOUCH_FOR_IMAGES.items())):
        for unsigned_mbn in sorted(mbn_list):
            input_nodes.append(unsigned_mbn)
            mbn_path = unsigned_mbn.srcnode().abspath
            mbn_paths.append(mbn_path)
            image_ids.append(image_id)

    if not input_nodes:
        # no inputs; skip generation
        return []

    sectools = env['SECTOOLS']
    output_elf_path = os.path.join("${MBN_ROOT}", "multi_image_qti.mbn")
    vouch_for_image_paths = " ".join(mbn_paths)
    vouch_for_image_ids = " ".join(image_ids)
    qti_signer_params = env.GetSignerParams_v2()
    secure_image_command = [
        f"{sectools} secure-image",
        f"--outfile $TARGET",
        f"--security-profile {security_profile}",
        f"--vouch-for {vouch_for_image_paths}",
        f"--image-id {vouch_for_image_ids}",
        f"--sign",
        f"--qti {qti_signer_params}",
    ]
    qti_misc = env.Command(
        output_elf_path, input_nodes, " ".join(secure_image_command))

    return qti_misc


# v1 signer methods
def get_default_config_v1(env):
    """Returns the path to the default sectools v1 signing config, as resolved
       from ${SECIMAGE_CONFIG} in the environment."""
    default_config = env.subst('${SECIMAGE_CONFIG}')
    if default_config == 'secimage.xml':
        default_config = env.SectoolConfigV1()
    elif default_config == 'secimagev2.xml':
        default_config = env.SectoolConfigV2()
    elif default_config == 'secimagev3.xml':
        default_config = env.SectoolConfigV3()
    elif default_config == 'secimage_ecc.xml':
        default_config = env.SectoolConfigECC()
    elif default_config == 'secimage_eccv3.xml':
        default_config = env.SectoolConfigECCV3()
    else:
        raise ValueError(
            '{} is not a valid secimage*.xml file'.format(default_config))

    config_path = os.path.join(os.path.dirname(default_config),
                               '${SECIMAGE_CONFIG}')
    return config_path


def generate_unsigned(env, input_elf, output_elf_name, sign_id, app_id=None,
                      config=None, qti=False):
    """Generates unsigned MBN 'output_elf_name' from 'input_elf' and the other
       provided signing parameters. Returns a SCons node representing the new
       MBN."""
    if config is None:
        config = '${SECTOOLS_DIR}/config/integration/${SECIMAGE_CONFIG}'
        # In the standalone SDK env, we build the path to the config file from
        # the default config path, rather than config/integration
        if env.StandaloneSdk():
            config = env.GetDefaultConfig_v1()

    # Check if the TA currently being compiled has already been built
    # for another chipset. If it is chipset-independent and already built,
    # just return the previously built TA MBN.
    app_built_flag = env.get('APP_NAME', '') + '_BUILT'
    unsigned_mbn_built_flag = app_built_flag + '_UNSIGNED_MBN'
    if (SCons.Script.ARGUMENTS.get(app_built_flag) and
            SCons.Script.ARGUMENTS.get(unsigned_mbn_built_flag)):
        unsigned_mbn = SCons.Script.ARGUMENTS[unsigned_mbn_built_flag]
        return unsigned_mbn

    # Set the appropriate sectools env key and remove any conflicting keys
    env['USES_SEC_POLICY_DEFAULT_UNSIGN'] = True
    if 'USES_SEC_POLICY_MULTIPLE_DEFAULT_SIGN' in env:
        del env['USES_SEC_POLICY_MULTIPLE_DEFAULT_SIGN']

    unsigned_dir = env.GetUnsignedDir()
    unsigned_mbn = env.SectoolBuilder(
        target_base_dir='${SHORT_BUILDPATH}',
        source=input_elf,
        sign_id=sign_id,
        app_id=app_id,
        config=config,
        soc_hw_version=env.get("SOC_HW_VERSION"),
        soc_vers=env.get("SOC_VERS"),
        sectools_install_base_dir=unsigned_dir,
        install_file_name=output_elf_name,
        qti_sign=qti,
    )

    # set the built flag
    SCons.Script.ARGUMENTS[unsigned_mbn_built_flag] = unsigned_mbn

    # clear the unsign flag for other operations
    del env['USES_SEC_POLICY_DEFAULT_UNSIGN']
    return unsigned_mbn


def sign_for_qti(env, input_elf, output_elf_name, sign_id, app_id=None,
                 config=None):
    """Generates a QTI-signed MBN 'output_elf_name' from 'input_elf' and the
       other provided signing parameters. Returns a SCons node representing the
       new MBN."""
    if config is None:
        config = '${SECTOOLS_DIR}/config/integration/${SECIMAGE_CONFIG_QTI}'

    unsigned_mbn = env.GenerateUnsigned(input_elf, output_elf_name, sign_id,
                                        app_id, config, qti=True)

    env['USES_SEC_POLICY_DEFAULT_SIGN'] = True
    qti_signed_mbn = env.SectoolBuilder(
        target_base_dir='${SHORT_BUILDPATH}',
        source=unsigned_mbn,
        sign_id=sign_id,
        app_id=app_id,
        max_num_root_certs=env.get("MAX_NUM_ROOT_CERTS"),
        signer='qti_remote' if env.get('SIGNTYPE') == 'CASS' else 'local_v2',
        qti_sign=True,
        sectools_install_base_dir='${MBN_ROOT}',
        config=config,
        soc_hw_version=env.get("SOC_HW_VERSION"),
        soc_vers=env.get("SOC_VERS"),
        install_file_name=output_elf_name,
    )

    # clear the sign flag for other operations
    del env['USES_SEC_POLICY_DEFAULT_SIGN']
    return env.Flatten(unsigned_mbn + qti_signed_mbn)


def sign_for_oem(env, input_elf, output_elf_name, sign_id, app_id=None,
                 config=None):
    """Generates an OEM-signed MBN 'output_elf_name' from 'input_elf' and the
       other provided signing parameters. Returns a SCons node representing the
       new MBN."""
    if config is None:
        config = '${SECTOOLS_DIR}/config/integration/${SECIMAGE_CONFIG}'

    unsigned_mbn = env.GenerateUnsigned(input_elf, output_elf_name, sign_id,
                                        app_id, config)

    # Create the signed binaries if chipset value is passed,
    # otherwise return the unsigned binaries.
    if env['CHIPSET']:
      env['USES_SEC_POLICY_MULTIPLE_DEFAULT_SIGN'] = True
      oem_signed_mbn = env.SectoolBuilder(
        target_base_dir='${SHORT_BUILDPATH}',
        source=unsigned_mbn,
        sign_id=sign_id,
        app_id=app_id,
        max_num_root_certs=env.get("MAX_NUM_ROOT_CERTS"),
        sectools_install_base_dir='${MBN_ROOT}',
        config=config,
        soc_hw_version=env.get("SOC_HW_VERSION"),
        soc_vers=env.get("SOC_VERS"),
        install_file_name=output_elf_name,
      )

      return env.Flatten(unsigned_mbn + oem_signed_mbn)
    else:
      return env.Flatten(unsigned_mbn)


def double_sign(env, input_elf, output_elf_name, sign_id, app_id=None,
                config=None, config_qti=None):
    """Generates a double-signed MBN 'output_elf_name' from 'input_elf' and the
       other provided signing parameters. Returns a SCons node representing the
       new MBN."""
    if config is None:
        config = '${SECTOOLS_DIR}/config/integration/${SECIMAGE_CONFIG}'

    if config_qti is None:
        config_qti = \
            '${SECTOOLS_DIR}/config/integration/${SECIMAGE_CONFIG_QTI}'

    unsigned_mbn = env.GenerateUnsigned(input_elf, output_elf_name, sign_id,
                                        config=config)

    # Create the signed binaries if chipset value is passed,
    # otherwise return the unsigned binaries.
    if env['CHIPSET']:
      env['USES_SEC_POLICY_DEFAULT_SIGN'] = True
      qti_signed_mbn = env.SectoolBuilder(
        target_base_dir='${SHORT_BUILDPATH}',
        source=unsigned_mbn,
        sign_id=sign_id,
        app_id=app_id,
        max_num_root_certs=env.get("MAX_NUM_ROOT_CERTS"),
        signer='qti_remote' if env.get('SIGNTYPE') == 'CASS' else 'local_v2',
        qti_sign=True,
        config=config_qti,
        soc_hw_version=env.get("SOC_HW_VERSION"),
        soc_vers=env.get("SOC_VERS"),
      )

      # Clear the default sign flag for the next operation
      del env['USES_SEC_POLICY_DEFAULT_SIGN']

      env['USES_SEC_POLICY_MULTIPLE_DEFAULT_SIGN'] = True
      double_signed_mbn = env.SectoolBuilder(
        target_base_dir='${SHORT_BUILDPATH}',
        source=qti_signed_mbn,
        sign_id=sign_id,
        app_id=app_id,
        max_num_root_certs=env.get("MAX_NUM_ROOT_CERTS"),
        sectools_install_base_dir='${MBN_ROOT}',
        config=config,
        soc_hw_version=env.get("SOC_HW_VERSION"),
        soc_vers=env.get("SOC_VERS"),
        install_file_name=output_elf_name,
      )

      return env.Flatten(unsigned_mbn + qti_signed_mbn + double_signed_mbn)
    else:
      return env.Flatten(unsigned_mbn)
