# CHIPSEC: Platform Security Assessment Framework
# Copyright (c) 2010-2022, Intel Corporation
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; Version 2.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
#
# Contact information:
# chipsec@intel.com
#

"""
Common include file for modules

"""

import os
import json
from enum import Enum
from chipsec.library.defines import bit, is_set
from chipsec.library.file import get_main_dir
from chipsec.library.logger import logger
from hashlib import sha256
from typing import Dict


class ReturnCode:
    class status(Enum):
        SUCCESS = [0x0000000000000000, 'Test module completed successfully']
        LOCKS = [bit(31), 'Locks are not set']
        MITIGATION = [bit(30), 'Does not support mitigation']
        CONFIGURATION = [bit(29), 'Configuration not valid']
        PROTECTION = [bit(28), 'Protection not supported/enabled']
        ACCESS_RW = [bit(27), 'Read or write access issues']
        RESTORE = [bit(26), 'Cannot restore binary/value']
        POTENTIALLY_VULNERABLE = [bit(25), 'Found potential vulnerabilities']
        MISMATCH = [bit(24), 'Data does not match expected value']
        REGISTER_NOT_DEFINED = [bit(20), 'Register not defined in Configuration']
        ALL_FFS = [bit(19), 'Read returned all 0xFFs']
        ALL_00S = [bit(18), 'Read returned all 0x00s']
        DEVICE_DISABLED = [bit(17), 'Device is disabled']
        FEATURE_DISABLED = [bit(16), 'Feature is disabled']
        PARSE_ERROR = [bit(15), 'Issue parsing the data']
        UNDEFINED_RANGES = [bit(14), 'Memory ranges are not defined']
        TIMEOUT = [bit(13), 'Operation timed out']
        VERIFY = [bit(12), 'Manual verification/further testing recommended']
        UNSUPPORTED_FEATURE = [bit(11), 'Feature not supported']
        UNSUPPORTED_OPTION = [bit(10), 'Option not supported']
        DEBUG_FEATURE = [bit(9), 'A debug feature is enabled or an unexpected debug state was discovered on this platform']
        NOT_APPLICABLE = [bit(8), 'Skipping module since it is not supported']
        ARCHIVED = [bit(1), 'Archived module, to run use tag ARCHIVED']
        INFORMATION = [bit(0), 'For your information']
        INVALID = [0xFFFFFFFFFFFFFFFF, 'Error running the test']

    def __init__(self, cs):
        self.id = 0x0
        self.url = ''
        self._result = 0x00000000
        self._return_code = self.status.SUCCESS.value[0]
        self._message = ''
        self.logger = logger()
        self.cs = cs

    def setStatusBit(self, status) -> None:
        if is_set(self._result, status.value[0]) is not True:
            self._result ^= status.value[0]
            self._message += ' / ' + status.value[1]

    def setResultBits(self) -> None:
        self._return_code ^= self._result << 32

    def setTestID(self) -> None:
        self._return_code ^= self.id << 4

    def printLogOutput(self) -> None:
        if self._result == self.status.SUCCESS.value[0]:
            self.logger.log_good(f'RC 0x{self._return_code:016x}: {self.status.SUCCESS.value[1]}')
        else:
            self.logger.log_important(f"For next steps: {self.url}")
            self.logger.log_important(f"RC 0x{self._return_code:016x}: {self._message}")

    def buildReturnCode(self, print_output: bool) -> None:
        self.setResultBits()
        self.setTestID()
        if print_output: self.printLogOutput()

    def resetReturnCodeValues(self) -> None:
        self.id = 0x0
        self._result = 0x00000000
        self._return_code = self.status.SUCCESS.value[0]

    def getReturnCode(self, result: int, print_output: bool = True) -> int:
        if self.cs.using_return_codes:
            self.buildReturnCode(print_output)
        else:
            self._return_code = result
        ret_value = self._return_code
        self.resetReturnCodeValues()
        return ret_value


def get_module_ids_dictionary() -> Dict[str, str]:
    with open(os.path.join(get_main_dir(), 'chipsec', 'library', 'module_ids.json'), 'r') as module_ids_file:
        module_ids = json.loads(module_ids_file.read())
    return module_ids


def generate_hash_id(className: str) -> int:
    generated_id = sha256(className.encode("utf-8")).hexdigest()[:7]
    return int(generated_id, 16)

# -------------------------------------------------------
# Legacy results
# -------------------------------------------------------


class ModuleResult(Enum):
    FAILED = 0
    PASSED = 1
    WARNING = 2
    DEPRECATED = 4
    INFORMATION = 5
    NOTAPPLICABLE = 6
    ARCHIVED = 7
    ERROR = -1


result_priority = {
    ModuleResult.PASSED: 0,
    ModuleResult.NOTAPPLICABLE: 0,
    ModuleResult.DEPRECATED: 0,
    ModuleResult.ARCHIVED: 0,
    ModuleResult.INFORMATION: 1,
    ModuleResult.WARNING: 2,
    ModuleResult.FAILED: 3,
    ModuleResult.ERROR: 4
}

ModuleResultName = {
    ModuleResult.FAILED: 'Failed',
    ModuleResult.PASSED: 'Passed',
    ModuleResult.WARNING: 'Warning',
    ModuleResult.DEPRECATED: 'Deprecated',
    ModuleResult.INFORMATION: 'Information',
    ModuleResult.ERROR: 'Error',
    ModuleResult.NOTAPPLICABLE: 'NotApplicable',
    ModuleResult.ARCHIVED: 'Archived',
}


def max_result_priority(previous_result: ModuleResult, current_result: ModuleResult) -> ModuleResult:
    '''Accepts two results and returns either current_result (if equal) or the max of the two'''
    return previous_result if result_priority[previous_result] > result_priority[current_result] else current_result


def getModuleResultName(res, using_return_codes) -> str:
    if using_return_codes:
        result_mask = 0xFFFFFFFF00000000
        status = [ReturnCode.status.SUCCESS.value[0], ReturnCode.status.INFORMATION.value[0], ReturnCode.status.NOT_APPLICABLE.value[0]]
        if ((res & result_mask) >> 32) in status:
            resultName = 'Passed'
        elif ((res & result_mask) >> 32) == ReturnCode.status.ARCHIVED.value[0]:
            resultName = 'Archived'
        else:
            resultName = 'Failed'
        return resultName
    return ModuleResultName[res] if res in ModuleResultName else ModuleResultName[ModuleResult.ERROR]
# -------------------------------------------------------
