-- 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 bs = require 'mc.bitstring'
local json = require 'cjson'
local test_common = require 'test_common.utils'
local log = require 'mc.logging'
require 'skynet.manager'
local utils = require 'mc.utils'
local sd_bus = require 'sd_bus'
local mdb_syspowerctrl = require 'stub_service.PowerControl'
require 'ipmi'
local test_bios_fw_info = require 'test_bios_fw_info'
local lu = require 'luaunit'
local test_wr_bios = require 'test_write_bios'
local test_wr_bdf = require 'test_write_bdf'
local test_bios_mode = require 'test_bios_mode'
local test_ipmi_option = require 'test_ipmi_option'
local test_smbios = require 'test_smbios'
local test_bios_conf = require 'test_bios_conf'
local test_bios_ipmi = require 'test_bios_ipmi'
local test_poweron_delay = require 'test_poweron_delay'
local test_bios_certificate = require 'test_bios_certificate'
local mdb = require 'mc.mdb'
local base_messages = require 'messages.base'
local custom_messages = require 'messages.custom'
local test_bios_common = require 'test_bios_common'
local BIOS_PATH = '/bmc/kepler/Systems/1/Bios'
local BIOS_INTERFACE = 'bmc.kepler.Systems.Bios'
local BIOS_OPTION_PATH = '/bmc/kepler/Systems/1/BootOptions'
local BIOS_OPTION_INTERFACE = 'bmc.kepler.Systems.BootOptions'
local absolute_test_data_dir = os.getenv('PROJECT_DIR') .. '/test/integration/.test_temp_data/'
skynet.setenv('KSF_PATH', absolute_test_data_dir)
skynet.setenv('KSF_BAK_PATH', absolute_test_data_dir)
skynet.setenv('KSF_DEFAULT_PATH', absolute_test_data_dir)
skynet.setenv('PROG_APP_PATH', absolute_test_data_dir .. 'apps')
skynet.setenv('PROG_CSR_PATH', absolute_test_data_dir .. 'csr')
skynet.setenv('PAM_TALLY_LOG_DIR', absolute_test_data_dir .. 'tmp/tallylog')
skynet.setenv('PAM_FAILLOCK_DIR', absolute_test_data_dir .. 'tmp/data/trust')

require 'bios.json_types.BootOptions'
require 'bios.json_types.Bios'
-- 准备测试专用路径
local function prepare_test_data()
    log:info('- prepare test data')
    local test_data_dir = skynet.getenv('TEST_DATA_DIR')
    local bios_data_dir = skynet.getenv('BIOS_DATA_DIR')

    local regist_file_path = os.getenv('ROOT_DIR') .. '/test_data/apps/Bios/registry.json'
    os.execute('mkdir -p ' .. test_data_dir)
    os.execute('mkdir -p ' .. bios_data_dir)
    os.execute('mkdir -p /data/opt/pme/conf/bios')
    os.execute('mkdir -p ' .. test_data_dir .. '/apps/ipmi_core/mds')
    os.execute('cp ' .. regist_file_path .. ' /opt/')
    utils.copy_file('test/integration/data/passwd', test_data_dir .. '/passwd')
    utils.copy_file('test/integration/data/shadow', test_data_dir .. '/shadow')
    utils.copy_file('test/integration/data/group', test_data_dir .. '/group')
    utils.copy_file('test/integration/data/datatocheck_default.dat',
        test_data_dir .. '/datatocheck_default.dat')
    utils.copy_file('temp/opt/bmc/apps/ipmi_core/mds/schema.json',
        test_data_dir .. '/apps/ipmi_core/mds/schema.json')

    local dir_list = {'apps/bios/mds', 'apps/hwdiscovery/mds', 'apps/fructrl/mds', 'csr', 'data', 'usr/lib64'}
    for _, path in pairs(dir_list) do
        os.execute('mkdir -p ' .. test_data_dir .. '/' .. path)
    end
    utils.copy_file('test/integration/test_data/14100513_BCU_01.csr',
        test_data_dir .. '/csr/14100513_BCU_01.sr')
    utils.copy_file('test/integration/test_data/14100513_EXU_01.csr',
        test_data_dir .. '/csr/14100513_EXU_01.sr')
    utils.copy_file('test/integration/test_data/root.csr', test_data_dir .. '/csr/root.sr')
    os.execute('tar -xzvf temp/test_data/apps/hwproxy/mockdata.tar.gz -C ' .. test_data_dir .. 'data')
    utils.copy_file('mds/schema.json', test_data_dir .. '/apps/bios/mds/schema.json')
    utils.copy_file('temp/opt/bmc/apps/hwdiscovery/mds/schema.json',
        test_data_dir .. '/apps/hwdiscovery/mds/schema.json')
    utils.copy_file('temp/opt/bmc/apps/fructrl/mds/schema.json',
        test_data_dir .. '/apps/fructrl/mds/schema.json')
    utils.copy_file('temp/usr/lib64/mock/libsoc_adapter_it.so',
        test_data_dir .. '/usr/lib64/libsoc_adapter.so')

    local tallylog_dir = skynet.getenv('PAM_TALLY_LOG_DIR')
    os.execute('mkdir -p ' .. tallylog_dir)
    local pamfaillock_dir = skynet.getenv('PAM_FAILLOCK_DIR')
    os.execute('mkdir -p ' .. pamfaillock_dir)
    local temp_dir = os.getenv('PROJECT_DIR') .. '/temp'
    os.execute(string.format("find %s -name client.lua|xargs sed -i -e 's/local MAX_RETRY_TIMES<const> ="..
        " 10/local MAX_RETRY_TIMES<const> = 1/g'", temp_dir))
end

-- 删除测试专用路径
local function clear_test_data(exit_test)
    log:info('- clear test data')
    local test_data_dir = utils.realpath('.') .. skynet.getenv('TEST_DATA_DIR')
    local bios_data_dir = utils.realpath('.') .. skynet.getenv('BIOS_DATA_DIR')
    if exit_test then
        skynet.timeout(0, function()
            skynet.sleep(20)
            skynet.abort()
            utils.remove_file(test_data_dir)
            utils.remove_file(bios_data_dir)
        end)
    else
        utils.remove_file(test_data_dir)
        utils.remove_file(bios_data_dir)
    end
end

-- 使用sdbus接口读取树上Processor对象属性
local function get_bios_property(bus, name, system_id, prop_name)
    return bus:call('bmc.kepler.bios', '/bmc/kepler/Systems/' .. system_id .. '/' .. name,
        'org.freedesktop.DBus.Properties', 'Get', 'ss', 'bmc.kepler.Systems.' .. name, prop_name)
        :value()
end

local function get_bios_version_property(bus, system_id, prop_name)
    return bus:call('bmc.kepler.bios', '/bmc/kepler/Systems/' .. system_id .. '/Bios',
        'org.freedesktop.DBus.Properties', 'Get', 'ss', 'bmc.kepler.Systems.Bios', prop_name)
        :value()
end

local function send_ipmi(data)
    return test_bios_common.send_ipmi(data)
end

local BIOS_SET_VERSION_RSP = '<<ManufactureId:3/unit:8>>'
local function test_set_version(bus)
    log:info('================ test_set_version start================')
    -- 发送将version设为 1.86 的ipmi命令
    local ok, rsp = send_ipmi('0x30 0x92 0xDB 0x07 0x00 0x02 0x31 0x2E 0x38 0x36')
    assert(ok, 'call ipmi msg failed')
    if ok then
        log:info('send ipmi success ')
        local data = bs.new(BIOS_SET_VERSION_RSP):unpack(rsp)
        assert(data, 'version: data get failed')
        local version = get_bios_version_property(bus, 1, 'Version')
        assert(version == '1.86', 'update tree failed')
        log:info('rsp manid= 0x%06x   version = %s', data.ManufactureId, version)
    end

    -- 发送version为空的ipmi命令
    ok = send_ipmi('0x30 0x92 0xDB 0x07 0x00 0x02')
    assert(not ok)

    -- 发送厂商id不合规范的ipmi命令
    ok = send_ipmi('0x30 0x92 0xDB 0x00 0x00 0x00 0x02 0x39')
    assert(not ok)
    log:info('================ test_set_version finish================')
end

local function test_boot_order(bus)
    log:info('---------------- test set_boot_order[1] start ----------------')
    local boot_order = json.encode({
        BootTypeOrder0 = 'HardDiskDrive',
        BootTypeOrder1 = 'DVDROMDrive',
        BootTypeOrder2 = 'PXE',
        BootTypeOrder3 = 'Others'
    })
    local obj = mdb.get_object(bus, BIOS_OPTION_PATH, BIOS_OPTION_INTERFACE)
    local resp = obj:SetBootOrder_PACKED(require'mc.context'.new(), boot_order)
    log:info('OutCode = ' .. resp.OutCode)
    log:info('ErrName = ' .. resp.ErrName)
    log:info('ErrValue = ' .. resp.ErrValue)
    log:info('---------------- test set_boot_order[2] start ----------------')
    boot_order = json.encode({
        BootTypeOrder0 = 'HardDiskDrive',
        BootTypeOrder1 = 'DVDROMDrive',
        BootTypeOrder2 = 'PXE'
    })
    local _, resp = pcall(function()
        return obj:SetBootOrder_PACKED(require'mc.context'.new(), boot_order)
    end)
    lu.assertEquals(resp.name, custom_messages.WrongArrayLengthMessage.Name)
    log:info('---------------- test set_start_option[1] start ----------------')
    resp = obj:SetStartOption_PACKED(require'mc.context'.new(), "Cd")
    log:info('Result = ' .. resp.Result)
    lu.assertEquals(resp.Result, 0)
    log:info('---------------- test set_start_option[2] start ----------------')
    local _, resp = pcall(function()
        return obj:SetStartOption_PACKED(require'mc.context'.new(), "partion")
    end)
    lu.assertEquals(resp.name, base_messages.PropertyValueNotInListMessage.Name)
end

local function test_bios_fw(bus)
    log:info('================ test bios_fw start ================')

    test_bios_fw_info.test_fw_info_register(bus)

    local path = '/bmc/kepler/UpdateService'
    local interface = 'bmc.kepler.UpdateService'
    log:info('start prepare')
    bus:signal(path, interface, 'UpgradePrepareSignal', 'is', 1, 'Bios')
    log:info('start finish')
    bus:signal(path, interface, 'UpgradeFinishSignal', 'is', 1, 'Bios')

    skynet.sleep(10)
    test_bios_fw_info.test_fw_info_register(bus) -- 测试bios版本信息是否有改变

    log:info('================ test bios_fw complete ================')
end

local function test_bios_get_currentvalue(bus)
    log:info('================ test bios_get_currentvalue start ================')
    -- 硬编码实现 currentvalue.json文件  存放在在指定目录下
    local file_data = {
        ['BootTypeOrder0'] = 'DVDROMDrive',
        ['BootTypeOrder1'] = 'HardDiskDrive',
        ['BootTypeOrder2'] = 'PXE',
        ['BootTypeOrder3'] = 'Others'
    }
    local current_path = get_bios_property(bus, 'Bios', 1, 'CurrentValueFileName')
    local f = io.open(current_path, 'w+')
    local file_encode_data = json.encode(file_data)
    f:write(file_encode_data)
    f:close()

    -- 执行rpc方法
    local key = 'BootTypeOrder0'
    local obj = mdb.get_object(bus, BIOS_PATH, BIOS_INTERFACE)
    local resp = obj:GetCurrentValueSetting_PACKED(require'mc.context'.new(), key)
    local data = resp:unpack()
    lu.assertEquals(data, 'DVDROMDrive')

    key = ''
    local _, resp = pcall(function()
        return obj:GetCurrentValueSetting_PACKED(require'mc.context'.new(), key)
    end)
    lu.assertEquals(resp.name, base_messages.InternalErrorMessage.Name)

    key = '123456'
    local _, resp = pcall(function()
        return obj:GetCurrentValueSetting_PACKED(require'mc.context'.new(), key)
    end)
    lu.assertEquals(resp.name, base_messages.InternalErrorMessage.Name)
    log:info('================ test bios_get_currentvalue complete ================')
end

local WRITE_SMBIOS_DATA_RSP = '<<ManufactureId:3/unit:8, RequiredOffset/string>>'
local function test_read_file_from_bmc()
    log:info('================ test read file from bmc[prepare][silkconfig.json] ================')
    local ok = send_ipmi('0x30 0x92 0xdb 0x07 0x00 0x0a 0x00 0x2a 0x00')
    lu.assertEquals(ok, true)
    log:info('================ test read file from bmc[read][silkconfig.json] ================')
    local ok, rsp = send_ipmi('0x30 0x92 0xdb 0x07 0x00 0x0a 0x00 0x2a 0x01 0x40 0x00 0x00 0x00 0x1')
    lu.assertEquals(ok, true)
    if ok then
        local data = bs.new(WRITE_SMBIOS_DATA_RSP):unpack(rsp)
        lu.assertEquals(data.ManufactureId, 0x7DB)
    end
    log:info('================ test read file from bmc[finish][silkconfig.json] ================')
    ok, rsp = send_ipmi('0x30 0x92 0xdb 0x07 0x00 0x0a 0x00 0x2a 0x03')
    lu.assertEquals(ok, true)
    if ok then
        local data = bs.new(WRITE_SMBIOS_DATA_RSP):unpack(rsp)
        lu.assertEquals(data.ManufactureId, 0x7DB)
    end
end

local function test_bios_get_file_changed()
    log:info('================ test bios get file changed[silkconfig][1] ================')
    local ok, rsp = send_ipmi('0x30 0x92 0xdb 0x07 0x00 0x0c 0x00 0x0a')
    lu.assertEquals(ok, true)
    if ok then
        lu.assertEquals(string.sub(rsp, -2, -2):byte(), 0x01)
        lu.assertEquals(string.sub(rsp, -1, -1):byte(), 0x00)
    end

    log:info('================ test bios get file changed[silkconfig][2] ================')
    ok = send_ipmi('0x30 0x92 0xdb 0x06 0x00 0x00 0x0c 0x00 0x2a')
    lu.assertIsTrue(ok ~= true)

    log:info('================ test bios get file changed[silkconfig][2] ================')
    ok = send_ipmi('0x30 0x92 0xdb 0x07 0x00 0x0c 0x01 0x2a')
    lu.assertIsTrue(ok ~= true)
end

local function test_bios()
    log:notice('================ test bios start ================')
    local bus = sd_bus.open_user(true)
    test_bios_common.set_bus(bus)
    test_bios_certificate.test_boot_import_certificate(bus)
    test_bios_certificate.test_crl_import_certificate(bus)
    test_bios_certificate.test_secure_boot_import_certificate(bus)
    test_bios_certificate.test_boot_reset_certificate(bus)
    test_boot_order(bus)
    test_set_version(bus)
    test_smbios.test_smbios_all(bus)
    test_bios_fw(bus)
    test_bios_mode.test_mode(bus)
    test_bios_get_currentvalue(bus)
    test_read_file_from_bmc()
    test_wr_bios.test_send_bios_data()
    test_wr_bdf.test_send_bdf(bus)
    test_bios_get_file_changed()
    test_ipmi_option.test_set_option()
    test_bios_conf.test_conf(bus)
    test_bios_ipmi.test_ipmi()
    test_bios_certificate.test_set_cert_assertion()
    test_poweron_delay.test_get_info()
    skynet.call('bios', 'lua', 'exit')
    log:notice('================ test bios complete ================')
end

local function mock_hwproxy_service()
    local hwproxy = sd_bus.open_user(true)
    local system_id = 1
    hwproxy:request_name('bmc.kepler.hwmon')
    hwproxy:start()

    local intf = mdb_syspowerctrl.ComputerSystemPowerControlAccessor.new(system_id)
    intf:register(hwproxy)
    return intf
end

skynet.start(function()
    clear_test_data() -- 防止之前数据残留
    prepare_test_data()
    test_common.dbus_launch()
    skynet.uniqueservice('sd_bus')
    skynet.uniqueservice('persistence/service/main')

    local intf = mock_hwproxy_service()
    skynet.uniqueservice('maca/service/main')
    skynet.uniqueservice('ipmi_core/service/main')
    skynet.uniqueservice('key_mgmt/service/main')
    skynet.uniqueservice('firmware_mgmt/service/main')
    skynet.uniqueservice('pcie_device/service/main')
    skynet.uniqueservice('fructrl/service/main')
    skynet.uniqueservice('main')
    skynet.uniqueservice('hwproxy/service/main')
    skynet.uniqueservice('hwdiscovery/service/main')
    skynet.sleep(1500)
    skynet.fork(function()
        local ok, err = pcall(test_bios, intf)
        clear_test_data(true)
        if not ok then
            error(err)
        end
    end)
end)
