-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at:
--         http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

local skynet = require 'skynet'
local singleton = require 'mc.singleton'
local log = require 'mc.logging'
local module_object = require 'security_module.security_object'
local ipmi = require 'ipmi'
local comp_code = ipmi.types.Cc
local ipmi_util = require 'ipmi_util'
local bs = require 'mc.bitstring'
local msg = require 'general_hardware.ipmi.ipmi_message'
local initiator = require 'mc.initiator'
local json = require 'cjson'

local E_FAILED <const> = -1

local first_flag = true
local security_module = {}
security_module.__index = security_module

function security_module.new()
    return setmetatable({}, security_module)
end

function security_module:init(db)
    self.db = db
    skynet.fork_once(function()
        for i = 0, 90 do
            -- 启动阶段90s内加载到安全模块对象，则认为上电前就在位，不记录插入日志
            if self.module_obj then
                first_flag = false
                return
            end
            skynet.sleep(100)
        end
        -- 启动后90s仍然没加载到安全模块对象，则不认为在位，下次插入需要记录日志
        first_flag = false
    end)
end

function security_module:on_add_object(class_name, object, position)
    local switch = {
        ['SecurityModule'] = function ()
            self.module_obj = module_object.new(object, position)
            if self.cache_data then -- 自发现加载前已有缓存数据
                self.module_obj:update_properties(self.cache_data)
                self.cache_data = nil  -- 清空缓存
            end
            if not first_flag then
                log:operation(initiator.new('N/A', 'N/A', '127.0.0.1'), 'NULL',
                    '%s plug in successfully', string.gsub(object['Protocol'],'`',''))
            end
            first_flag = false
        end
    }

    if switch[class_name] then
        switch[class_name]()
        log:notice('[security_module] Add object, class: %s, position: %s', class_name, position)
    end
end

function security_module:on_delete_object(class_name, object, position)
    local switch = {
        ['SecurityModule'] = function ()
            log:operation(initiator.new('N/A', 'N/A', '127.0.0.1'), 'NULL',
                '%s plug out successfully', string.gsub(object['Protocol'],'`',''))
            self.module_obj = nil
            self.cache_data = nil
        end
    }

    if switch[class_name] then
        switch[class_name]()
        log:notice('[security_module] Delete object, class: %s, position: %s', class_name, position)
    end
end

local get_trusted_info_from_cmd = bs.new('<<ProtocolLen:8, Protocol:ProtocolLen/string, ProtocolVerLen:8,' ..
    'ProtocolVer:ProtocolVerLen/string, FirmwareVerLen:8, FirmwareVer:FirmwareVerLen/string, SelfTestResult:8,' ..
    'ManufacturerLen:8, Manufacturer:ManufacturerLen/string>>')

local function decode_request(tail)
    local req_info = {}
    local length = #tail
    if length < 6 then -- 参数长度异常
        log:error("[general_hardware]decode_request: command length is error.")
        return E_FAILED
    end

    local trusted_module_info = get_trusted_info_from_cmd:unpack(tail)
    req_info.Protocol = trusted_module_info.Protocol
    req_info.ProtocolVersion = (trusted_module_info.ProtocolVer ~= '') and trusted_module_info.ProtocolVer or 'N/A'
    req_info.FirmwareVersion = (trusted_module_info.FirmwareVer ~= '') and trusted_module_info.FirmwareVer or 'N/A'
    req_info.Health = trusted_module_info.SelfTestResult
    req_info.Manufacturer = (trusted_module_info.Manufacturer ~= '') and trusted_module_info.Manufacturer or 'N/A'
    return req_info
end

function security_module:report_trusted_module_info(req)
    local manu_id = ipmi_util.get_manu_id()
    if ipmi_util.judge_manu_id_valid(req) == E_FAILED then
        return msg.ReportTrustedModuleInfoRsp.new(comp_code.InvalidCommand, manu_id)
    end

    local trusted_module_info = decode_request(req.TrustedModuleInfo)
    log:notice('[security_module] Get trusted module info: %s', json.encode(trusted_module_info))
    if trusted_module_info == E_FAILED then
        return msg.ReportTrustedModuleInfoRsp.new(comp_code.CommandNotAvailable, manu_id)
    end

    log:notice('get trusted module info: %s', json.encode(trusted_module_info))
    if not self.module_obj then
        self.cache_data = trusted_module_info
    else
        self.module_obj:update_properties(trusted_module_info)
    end
    return msg.ReportTrustedModuleInfoRsp.new(comp_code.Success, manu_id)
end

function security_module:get_security_module_info(req)
    local manu_id = ipmi_util.get_manu_id()
    if ipmi_util.judge_manu_id_valid(req) == E_FAILED then
        return msg.GetSecurityModuleInfoRsp.new(comp_code.InvalidCommand, manu_id, 0, '')
    end

    local presence = 0
    local protocol_len = 3
    local protocol = 'N/A'
    local protocol_ver_len = 3
    local protocol_ver = 'N/A'
    local firmware_ver_len = 3
    local firmware_ver = 'N/A'
    local manufacturer_len = 3
    local manufacturer = 'N/A'
    local self_test_result = 255
    if self.module_obj then
        presence = 1
        protocol = self.module_obj:get_protocol()
        protocol_len = #protocol
        protocol_ver = self.module_obj:get_protocol_version()
        protocol_ver_len = #protocol_ver
        firmware_ver = self.module_obj:get_firmware_version()
        firmware_ver_len = #firmware_ver
        manufacturer = self.module_obj:get_manufacturer()
        manufacturer_len = #manufacturer
        self_test_result = self.module_obj:get_health()
    end
    local security_module_info = string.char(protocol_len) .. protocol .. string.char(protocol_ver_len) ..
        protocol_ver .. string.char(firmware_ver_len) .. firmware_ver .. string.char(self_test_result) ..
        string.char(manufacturer_len) .. manufacturer
    return msg.GetSecurityModuleInfoRsp.new(comp_code.Success, manu_id, presence, security_module_info)
end

return singleton(security_module)