-- Copyright (c) 2026 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.

-- Description: CPLD 固件安全自检处理

local log = require 'mc.logging'
local ctx = require 'mc.context'
local client = require 'general_hardware.client'
local cmn = require 'common'
local utils = require 'mc.utils'
local vos = require 'utils.vos'
local file_sec = require 'utils.file'
local cfg = require 'unit_manager.class.logic_fw.upgrade.fw_cfgs'
local drivers = require 'unit_manager.class.logic_fw.upgrade.drivers_api'
local chip = require 'unit_manager.class.logic_fw.cpld_chip'
local defs = require 'unit_manager.class.logic_fw.comm_defs'
local skynet = require 'skynet'
local singleton = require 'mc.singleton'

local cpld_security_test = {}
cpld_security_test.__index = cpld_security_test

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

-- 需要处理的 FirmwareType 列表
local CPLD_FIRMWARE_TYPES<const> = {
    EXUCpld = true -- 先只做EXU
}

local CPLD_SECURITY_TEST_TYPE<const> = 'Security'
local CPLD_SECURITY_FINISH<const> = 'security_finish'
local MAX_CPLD_UPDATE_FILE_NUM<const> = 32 -- 8家厂商，4片为例

-- 解析 HPM 包中的 update.cfg 文件
-- @param hpm_path: HPM 包路径
-- @return cfg_list: update.cfg 文件中的配置列表
function cpld_security_test:parse_hpm_update_cfg(hpm_path)
    if not hpm_path or hpm_path == '' then
        log:error('[CPLD_SECURITY] hpm_path is nil or empty')
        return {}, nil
    end

    if not vos.get_file_accessible(hpm_path) then
        log:error('[CPLD_SECURITY] hpm_path not accessible: %s', tostring(hpm_path))
        return {}, nil
    end

    -- 1. 解析 HPM 包，获取 FirmwareDirectory
    local package_info = cmn.get_package_info(hpm_path)
    if not package_info or not package_info.FirmwareDirectory then
        log:error('[CPLD_SECURITY] get_package_info failed or FirmwareDirectory missing, hpm_path=%s', tostring(hpm_path))
        return {}, nil
    end

    local dir_path = package_info.FirmwareDirectory
    local ret = file_sec.check_shell_special_character_s(dir_path)
    if ret ~= 0 then
        log:error('[CPLD_SECURITY] FirmwareDirectory invalid, err=%s, dir=%s', tostring(ret), tostring(dir_path))
        return {}, nil
    end

    -- 2. 解析 update.cfg，找到固件包文件名
    local cfg_path = dir_path .. 'update.cfg'
    local upg_cfg_list = cfg:get_cfg_list(cfg_path)
    if not upg_cfg_list or not next(upg_cfg_list) then
        log:error('[CPLD_SECURITY] get_cfg_list failed, cfg_path=%s', tostring(cfg_path))
        return {}, nil
    end
    return upg_cfg_list, dir_path
end

-- 执行验证操作（不包含锁和路由切换），参考 process.lua:52-89 的 load_cpld_single 实现
-- @param logic_fw: CPLD 逻辑固件对象
-- @param verify_file: 验证文件路径
-- @return true: 验证成功, false: 验证失败
local function jtag_verify_svf_internal(logic_fw, verify_file)
    if not logic_fw or not verify_file or verify_file == '' then
        log:error('[CPLD_SECURITY] invalid logic_fw or verify_file, verify_file = %s', tostring(verify_file))
        return false
    end
    if not vos.get_file_accessible(verify_file) then
        log:error('[CPLD_SECURITY] verify file not found, file: %s', verify_file)
        return false
    end
    local file_len = vos.get_file_length(verify_file)
    if file_len <= 0 then
        log:error('[CPLD_SECURITY] verify file len invalid, file: %s, len: %s', verify_file, tostring(file_len))
        return false
    end

    local file_name_len = #verify_file
    local bs = require 'mc.bitstring'
    local upg_info = {
        mode = 0,         -- CPLD_UPGRADE_LOAD_MODE = 0
        product_id = 0,
        board_id = 0,
        pcb = 0x43,       -- 与升级流程保持一致
        component_id = 0, -- V3 采用 CSR 配置链路，cid 默认填 0
        file_type = 'svf',
        filename_len = file_name_len,
        filename = verify_file
    }

    local CPLD_UPG_FILE_DESC = string.format(
        '<<mode:4/unit:8, product_id:4/little-unit:8, ' ..
        'board_id:4/little-unit:8, ' ..
        'pcb:4/little-unit:8, component_id:4/little-unit:8, ' ..
        'file_type:3/string, filename_len:4/little-unit:8, filename:%d/binary>>',
        file_name_len)

    local info = bs.new(CPLD_UPG_FILE_DESC):pack(upg_info)

    -- 通过 JTAG 写入 verify.svf
    local write_ok, err = pcall(function()
        return logic_fw.update_chip.Write(logic_fw.update_chip, ctx.new(), 0, info)
    end)

    if not write_ok then
        log:error('[CPLD_SECURITY] verify svf write failed: %s', tostring(err))
        return false
    end

    log:notice('[CPLD_SECURITY] verify svf write success, fw_id: %s, file: %s', tostring(logic_fw.id), verify_file)
    return true
end

local function process_cpld_verify_with_chip_id(logic_fw, cpld_info, hpm_file_path, cpld_chip_list, hpm_cfg)
    local ok, error = pcall(logic_fw.chip_info.SetBypassMode, logic_fw.chip_info, ctx.new(), true)
    if not ok then
        log:error('[CPLD_SECURITY] set bypass mode failed: %s', tostring(error))
        return false
    end
    local chip_id
    local file_id
    for _, chip_index in pairs(cpld_chip_list) do
        chip_id = cpld_info[string.format('device%d_id', chip_index)]
        if not chip_id then
            log:error('[CPLD_SECURITY] chip_id not found, chip_index=%s', tostring(chip_index))
            goto continue
        end

        file_id = chip:get_cpld_upgrade_file_id(chip_id, hpm_cfg.chip_num, chip_index)
        if file_id == 0 then
            log:error('[CPLD_SECURITY] get_cpld_upgrade_file_id failed, chip_id=0x%x, chip_index=%s',
                chip_id, tostring(chip_index))
            goto continue
        end
        log:notice('[CPLD_SECURITY] chip_id: 0x%x, chip_index: %s, file_id: %s', chip_id, tostring(chip_index), tostring(file_id))
        drivers:set_upg_cpld_chip(logic_fw.chip_info, chip_index - 1)
        local verify_file = string.format('%sverify%02d.svf', hpm_file_path, file_id)
        if not jtag_verify_svf_internal(logic_fw, verify_file) then
            log:error('[CPLD_SECURITY] verify failed for chip_index=%s, file=%s', tostring(chip_index), verify_file)
            return false
        end
        skynet.sleep(20)
        ::continue::
   end
   return true
end

local function process_all_cpld_verify(logic_fw, hpm_file_path)
    local ok, error = pcall(logic_fw.chip_info.SetBypassMode, logic_fw.chip_info, ctx.new(), true)
    if not ok then
        log:error('[CPLD_SECURITY] set bypass mode failed: %s', tostring(error))
        return false
    end
    for i = 1, MAX_CPLD_UPDATE_FILE_NUM do
        local verify_file = string.format('%sverify%02d.svf', hpm_file_path, i)
        if not jtag_verify_svf_internal(logic_fw, verify_file) then
            log:error('[CPLD_SECURITY] verify failed for file_id=%s, file=%s', tostring(i), verify_file)
            return false
        end
        skynet.sleep(20)
    end
    return true
end

-- 获取多厂商升级CPLD列表
-- 参考 fw_upgrade.lua:33-54 的 check_hot_upgrade 实现
-- @param hpm_update_cfg: HPM 包中的 update.cfg 配置项
-- @return upgrade_list: CPLD 升级列表
-- @return is_hot_upgrade: 是否是热升级（true/false）
local function get_need_upgrade_cpld_list(hpm_update_cfg)
    -- 检测是否存在ColdValidList且不为空
    if hpm_update_cfg.cold_valid_list and #hpm_update_cfg.cold_valid_list ~= 0 then
        local upgrade_list = hpm_update_cfg.cold_valid_list
        log:notice('[CPLD_SECURITY] cpld cold upgrade, check chip list = %s', cmn.join(upgrade_list, ", "))
        return upgrade_list
    end

    -- 检测是否存在HotValidList且不为空
    if hpm_update_cfg.hot_valid_list and #hpm_update_cfg.hot_valid_list ~= 0 then
        local upgrade_list = hpm_update_cfg.hot_valid_list
        log:notice('[CPLD_SECURITY] cpld hot upgrade, check chip list = %s', cmn.join(upgrade_list, ", "))
        return upgrade_list
    end

    -- 如果两者都为空，生成默认的升级列表（1 到 chip_num）
    if hpm_update_cfg.chip_num and hpm_update_cfg.chip_num > 0 then
        local upgrade_list = {}
        for i = 1, hpm_update_cfg.chip_num do
            table.insert(upgrade_list, i)
        end
        log:notice('[CPLD_SECURITY] all chip upgrade, check chip list = %s', cmn.join(upgrade_list, ", "))
        return upgrade_list
    end
    log:error('[CPLD_SECURITY] hpm_update_cfg invalid, no valid check chip list found')
    return {}
end

-- 多厂商场景下的验证处理
-- 参考 process.lua:180-194 的 load_cpld_multi_supplier_mode 实现
-- @param logic_fw: CPLD 逻辑固件对象
-- @param hpm_cfg: HPM 配置项
-- @param hpm_file_path: HPM 包解压后的目录或文件路径
-- @return true: 验证成功, false: 验证失败
local function multi_supplier_process_verify(logic_fw, hpm_cfg, hpm_file_path)
    local cpld_chip_list = get_need_upgrade_cpld_list(hpm_cfg)
    local cpld_info = {}
    local ok = drivers:get_cpld_device_info(logic_fw.chip_info, cpld_info)
    if ok == defs.RET.ERR then
        log:notice('[CPLD_SECURITY] get cpld device info failed')
        return false
    end
    if (chip:check_has_pango(cpld_info) and hpm_cfg.chip_num > 1) or
        (hpm_cfg.supplier_mode == defs.CPLD_SUPPLIER_MODE.MIXED) then
        return process_cpld_verify_with_chip_id(logic_fw, cpld_info, hpm_file_path, cpld_chip_list, hpm_cfg);
    else
        return process_all_cpld_verify(logic_fw, hpm_file_path)
    end
end

local function process_verify(hpm_cfg, logic_fw, hpm_file_path)
    if hpm_cfg.supplier_mode <= defs.CPLD_SUPPLIER_MODE.SINGLE then
        local verify_file = hpm_file_path .. 'verify.svf'
        return jtag_verify_svf_internal(logic_fw, verify_file)
    else
        return multi_supplier_process_verify(logic_fw, hpm_cfg, hpm_file_path)
    end
end

-- @param logic_fw: CPLD 逻辑固件对象
-- @param verify_file: 验证文件路径
-- @return true: 验证成功, false: 验证失败
local function retry_lock_chip_to_process_verify(hpm_cfg, logic_fw, hpm_file_path)
    local lock_time_out = 3
    local ok, ret_code
    while lock_time_out > 0 do
        ok, ret_code = logic_fw:update_chip_lock(ctx.new(), 600)
        if ok and ret_code == 0 then
            -- 执行验证操作
            local verify_ret = process_verify(hpm_cfg, logic_fw, hpm_file_path)
            -- 硬件总线解锁
            ok, ret_code = logic_fw:update_chip_unlock(ctx.new())
            if not ok or ret_code ~= 0 then
                log:error("[CPLD_SECURITY] set lock status to 0 failed, ret code:%s", ret_code)
            end
            if verify_ret then
                return true
            else
                log:debug("[CPLD_SECURITY] verify failed, retry...")
            end
        else
            log:debug("[CPLD_SECURITY] get chip lock failed")
        end
        lock_time_out = lock_time_out - 1
        skynet.sleep(100)
    end
    log:error("[CPLD_SECURITY] verify failed because can not get chip lock or verify failed")
    return false
end

-- 根据hpm_update_cfg_list过滤logic_fw_list确定哪些CPLD需要进行安全自检
-- logic_fw_list可能有多份配置文件，需要遍历进行过滤确定哪些CPLD需要进行安全自检
-- @param system_id: SystemId
-- @param component_id: ComponentID
-- @param component_id_ex: ComponentIDEx
-- @return cpld_fw_list: 需要进行安全自检的CPLD逻辑固件对象列表
function cpld_security_test:filter_cpld_fw_list_for_verify(system_id, component_id, component_id_ex, hpm_cfg)
    if not self.logic_fw_list or type(self.logic_fw_list) ~= 'table' then
        log:error('[CPLD_SECURITY] logic_fw_list invalid')
        return nil
    end

    local update_obj_list = {}
    for _, fw in pairs(self.logic_fw_list) do
        if (system_id == defs.ALL_SYSTEM_ID or system_id == fw.system_id)
            and hpm_cfg:check_fw_uid_exist(fw) then
            log:notice('[CPLD_SECURITY] match logic_fw, system_id=%s, comp_id=%s, comp_id_ex=%s, name=%s',
                tostring(system_id), tostring(component_id), tostring(component_id_ex), tostring(fw.name))
            table.insert(update_obj_list, fw)
        end
    end
    return update_obj_list
end

-- FirmwareRestoreCheckSignal 回调， 多个CPLD固件会多次触发此回调
-- 入参约定：SystemId, FirmwareType, ComponentID, ComponentIDEx, AdditionalOptions(UpdateService.Object)
function cpld_security_test:on_firmware_restore_check(ctx, system_id, firmware_type, component_id, component_id_ex, additional_options)
    log:notice('[CPLD_SECURITY] receive FirmwareRestoreCheckSignal, system_id=%s, firmware_type=%s, cid=%s, cid_ex=%s',
        tostring(system_id), tostring(firmware_type), tostring(component_id), tostring(component_id_ex))
    if not CPLD_FIRMWARE_TYPES[firmware_type] then
        log:notice('[CPLD_SECURITY] ignore non-CPLD firmware type: %s', tostring(firmware_type))
        return
    end

    if additional_options.restore_type ~= CPLD_SECURITY_TEST_TYPE then
        log:notice('[CPLD_SECURITY] ignore non-CPLD security test type: %s', tostring(additional_options.restore_type))
        return
    end
    -- 回复收到自检信号
    client:FirmwareRestoreFirmwareRestoreFirmwareRestoreCheckReply(ctx, defs.RET.OK, system_id, firmware_type, component_id, component_id_ex, additional_options)
    local hpm_update_cfg_list, hpm_dir_path = self:parse_hpm_update_cfg(additional_options.hpm_path)
    if not hpm_dir_path then
        log:error('[CPLD_SECURITY] parse hpm update cfg failed, hpm_path=%s', tostring(additional_options.hpm_path))
        additional_options[CPLD_SECURITY_FINISH] = 'true'
        client:FirmwareRestoreFirmwareRestoreFirmwareRestoreCheckReply(ctx, defs.RET.ERR, system_id, firmware_type, component_id, component_id_ex, additional_options)
        return
    end
    local reply_res = defs.RET.OK
    for _, hpm_cfg in pairs(hpm_update_cfg_list) do
        local fw_data_path = hpm_dir_path .. hpm_cfg.name
        utils.secure_tar_unzip(fw_data_path, hpm_dir_path, 0x6400000, 1024)
        local cpld_fw_obj_list = self:filter_cpld_fw_list_for_verify(system_id, component_id, component_id_ex, hpm_cfg)
        if #cpld_fw_obj_list == 0 then
            log:error('[CPLD_SECURITY] no logic_fw matched, system_id=%s, comp_id=%s, comp_id_ex=%s, hpm_cfg_name=%s',
                tostring(system_id), tostring(component_id), tostring(component_id_ex), tostring(hpm_cfg.name))
            goto continue
        end
        -- 根据hpm_cfg.name还有hpm_dir_path确定verify.svf文件路径
        for _, cpld_fw_obj in pairs(cpld_fw_obj_list) do
            -- hpm包的路径：/dev/shm/upgrade/valid/随机数/
            log:notice('[CPLD_SECURITY] start verify for cpld_fw_obj, fw_id=%s, fw_name=%s, hpm_cfg_name=%s, hpm_dir_path=%s',
                tostring(cpld_fw_obj.id), tostring(cpld_fw_obj.name or ''), tostring(hpm_cfg.name), tostring(hpm_dir_path))
            cpld_fw_obj:switch_to_firmware_route()
            local verify_ok = retry_lock_chip_to_process_verify(hpm_cfg, cpld_fw_obj, hpm_dir_path)
            if verify_ok then
                log:security('[CPLD_SECURITY] verify success for cpld_fw_obj, fw_id=%s, fw_name=%s',
                    tostring(cpld_fw_obj.id), tostring(cpld_fw_obj.name or ''))
            else
                reply_res = defs.RET.ERR
                log:security('[CPLD_SECURITY] verify failed for cpld_fw_obj, fw_id=%s, fw_name=%s',
                    tostring(cpld_fw_obj.id), tostring(cpld_fw_obj.name or ''))
            end
            cpld_fw_obj:switch_to_default_route()
        end
        ::continue::
    end
    if hpm_dir_path then
        local ok, err = pcall(function()
            utils.remove_file(hpm_dir_path)
        end)
        if not ok then
            log:error('[CPLD_SECURITY] remove hpm dir failed, hpm_dir_path=%s, err=%s', tostring(hpm_dir_path), tostring(err))
        end
    end
    additional_options[CPLD_SECURITY_FINISH] = 'true'
    client:FirmwareRestoreFirmwareRestoreFirmwareRestoreCheckReply(ctx, reply_res, system_id, firmware_type, component_id, component_id_ex, additional_options)
    log:notice('[CPLD_SECURITY] firmware restore check processing completed, reply_res=%s', tostring(reply_res))
end

-- 初始化：订阅 FirmwareRestoreCheckSignal
-- @param bus: dbus 句柄（预留，目前未使用）
-- @param db:  数据库句柄（预留，目前未使用）
-- @param logic_fw_list: unit_manager.logic_fw 表
function cpld_security_test:init(bus, db, logic_fw_list)
    if self.inited then
        return
    end
    self.inited = true
    self.logic_fw_list = logic_fw_list -- 配置CSR中用于CPLD升级相关的配置

    -- 订阅 FirmwareRestoreCheckSignal 信号
    client:SubscribeFirmwareRestoreFirmwareRestoreFirmwareRestoreCheckSignal(function(...)
        log:notice('[CPLD_SECURITY_TEST] receive FirmwareRestoreCheckSignal, start to process cpld security test')
        self:on_firmware_restore_check(...)
    end)
    log:notice('[CPLD_SECURITY_TEST] Init cpld security test success')
end
return singleton(cpld_security_test)