-- 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 singleton = require 'mc.singleton'
local log = require 'mc.logging'
local mdb = require 'mc.mdb'
local skynet = require 'skynet'
local org_freedesktop_dbus = require 'sd_bus.org_freedesktop_dbus'
local msg = require 'general_hardware.ipmi.ipmi_message'
local ipmi = require 'ipmi'
local cc = ipmi.types.Cc
local log_collector = require 'log_collector'
local client = require 'general_hardware.client'
local ctx = require 'mc.context'
local initiator = require 'mc.initiator'
local utils = require 'dpu_service.utils'
local utils_core = require 'utils.core'
local mc_utils = require 'mc.utils'
local file_sec = require 'utils.file'
local vos = require 'utils.vos'
local custom_messages = require 'messages.custom'
local dpu_upgrade_object = require 'dpu_service.dpu_upgrade_object'
local cmn = require 'common'
local dpu_enums = require 'dpu_service.dpu_enums'
local upgrade_subject = require 'upgrade_subject.upgrade_subject'
local skynet_queue = require 'skynet.queue'

local E_OK = 0 -- 函数执行成功返回0
local E_FAILED = nil -- 函数执行失败返回nil
local MANUFACTURE_ID<const> = 0x0007db
local TLV_OS_STATUS<const> = 6
local TLV_EP_STATUS<const> = 2
local CONFIG_BMC<const> = 0
local CONFIG_OS<const> = 1
local IPV6_LENGTH<const> = 39
local SET_IPV6_MAX_TIMES<const> = 5
local SET_IPV4_MAX_TIMES<const> = 10

local MANAGER_ID<const> = 1
local ETH_IPV4_PATH<const> = '/bmc/kepler/Managers/' .. tostring(MANAGER_ID) ..
                                 '/EthernetInterfaces/Ipv4'
local ETH_IPV6_PATH<const> = '/bmc/kepler/Managers/' .. tostring(MANAGER_ID) ..
                                 '/EthernetInterfaces/Ipv6'
local ETH_IPV4_INTF<const> = 'bmc.kepler.Managers.EthernetInterfaces.Ipv4'
local ETH_PATH<const> = '/bmc/kepler/Managers/' .. tostring(MANAGER_ID) .. '/EthernetInterfaces'
local ETH_INTF<const> = 'bmc.kepler.Managers.EthernetInterfaces'
local PCIECARD_INTF <const> = 'bmc.kepler.Systems.PCIeDevices.PCIeCard'
local LIST_END<const> = 0 -- 0表示没有后续数据
local DEV_SHM_TMP_DIR<const> = "/dev/shm/tmp"
local TMP_DIR_PATTERN<const> = "^/tmp/"
local TMP_HTTPS_CERT_PUB_FILE<const> = "/dev/shm/tmp/publick_ca.crt"
local MAX_HTTPS_CERT_FILE_LEN<const> = 100 * 1024 -- 证书最大大小为100k
local ADMINISTRATOR_USER_UID<const> = 502
local ADMINISTRATOR_GID<const> = 204
local LLDP_ENABLE<const> = 1
local LLDP_DISABLE<const> = 0
local LLDP_STATUS_TYPE<const> = 5
local COMPUTING_BOARD_PORT1<const> = 4
local COMPUTING_BOARD_PORT2<const> = 5
local COMPUTING_BOARD_PORT_OFFSET<const> = 4
local PUBLIC_KEY_URI_TYPE = 'URI'
local PUBLIC_KEY_TEXT_TYPE = 'text'
local SOL_SWITCH_TYPE = {
    U0_SPU  = {num = 1, name = 'U0 SPU UART'},
    U0_MPU  = {num = 2, name = 'U0 MPU UART'},
    MCU     = {num = 3, name = 'MCU UART'},
    _1860   = {num = 4, name = 'GE UART'},
    U1_SPU  = {num = 5, name = 'U1 SPU UART'},
    U1_MPU  = {num = 6, name = 'U1 MPU UART'}
}

local BIOS_LOG_LEVEL = {
    [1] = 'SIMPLE',
    [2] = 'FULL'
}

local BIOS_LOG_TYPE = {
    [1] = 'MRC',
    [2] = 'UEFI'
}

local POWER_STATE_TO_STR = {
    [0] = 'Off',
    [1] = 'On',
    [5] = 'Graceful Off'
}

local dpu_service = {}
dpu_service.__index = dpu_service

function dpu_service.new()
    return setmetatable({
        dpu_objects = {}, sr_upgrade_list = {}, dpu_objects_with_model = {}, has_collected = false,
        dpu_detection_list = {}, hotplug = nil
        }, dpu_service)
end

function dpu_service:set_sys_time(dpu_obj)
    local ret = dpu_obj:set_sys_time(os.date('!*t', os.time()))
    if not ret then
        return E_FAILED
    end
    return E_OK
end

-- 获取vlanId
local function get_eth_vlan(bus)
    local ok, rsp = pcall(mdb.get_object, bus, ETH_PATH, ETH_INTF)
    if not ok then
        log:error('[DPU] get EthernetInterfaces object fail, error: %s', rsp)
        return nil
    end
    return rsp['VLANId']
end

-- 获取ip, mask
local function get_eth_ipv4(bus)
    local ok, rsp = pcall(mdb.get_object, bus, ETH_IPV4_PATH, ETH_IPV4_INTF)
    if not ok then
        log:error('[DPU] get Ipv4 object fail, error: %s', rsp)
        return nil, nil
    end
    return rsp['IpAddr'], rsp['SubnetMask']
end

-- Ipv6获取ip, PrefixLength
local function get_eth_ipv6(bus)
    local ipv6_obj = client:GetIpv6Objects()[ETH_IPV6_PATH]
    if not ipv6_obj then
        log:error('[DPU] get Ipv6 object fail.')
        return nil, nil
    end

    return ipv6_obj.IpAddr, ipv6_obj.PrefixLength
end

local function set_sdi_ipv4(bus, dpu_obj)
    skynet.fork(function()
        local ipv4, mask, vlan
        local repeat_times = 0
        while true do
            ipv4, mask = get_eth_ipv4(bus)
            vlan = get_eth_vlan(bus)
            if ipv4 and mask and vlan and dpu_obj:set_sdi_ipv4(CONFIG_BMC, ipv4, mask, vlan) then
                log:notice('[DPU] set SDI card ipv4: %s %s %s', ipv4, mask, vlan)
                break
            end
            skynet.sleep(200) -- 2s 重试1次
            repeat_times = repeat_times + 1
            if repeat_times > SET_IPV4_MAX_TIMES then
                log:debug('[DPU] Number of set SDI card ipv4 is more than %s times.', SET_IPV4_MAX_TIMES)
                break
            end
        end
    end)
    client:OnIpv4PropertiesChanged(function(values, _, interface)
        log:info('[DPU] PropertiesChanged on %s', interface)
        skynet.sleep(200) -- 等待2s，等待资源树属性变更完成
        local ipv4, mask = get_eth_ipv4(bus)
        local vlan = get_eth_vlan(bus)
        ipv4 = values['IpAddr'] and values['IpAddr']:value() or ipv4
        mask = values['SubnetMask'] and values['SubnetMask']:value() or mask
        if dpu_obj:set_sdi_ipv4(CONFIG_BMC, ipv4, mask, vlan) then
            log:notice('[DPU] set SDI card ipv4: %s %s %s', ipv4, mask, vlan)
        end
    end)
end

local function fix_byte(data)
    local parts = {}
    pcall(string.gsub(data, '[^:]+', function(val)
        val = tonumber(val, 16)
        table.insert(parts, string.format("%04x", val))
    end))
    return parts
end

-- 预处理ipv6地址：2001:0db8:85a3::8a2e:0370:7334 --> 2001:0db8:85a3:0000:0000:8a2e:0370:7334
local function pre_process_ipv6(data)
    if data == nil then
        return data
    end
    local res = ''
    local first, last = string.find(data, '::')
    if not first then
        res = data
    else
        local first_part = string.sub(data, 1, first - 1)
        local second_part = string.sub(data, last + 1)
        local first_t = fix_byte(first_part)
        local second_t = fix_byte(second_part)

        local num = 8 - #second_t - #first_t
        while num > 0 do
            table.insert(first_t, '0000')
            num = num - 1
        end

        res = table.concat(first_t, ':')

        if next(second_t) ~= nil then
            res = res .. ':' .. table.concat(second_t, ':')
        end
    end

    if #res == IPV6_LENGTH then
        return res
    else
        local parts = fix_byte(res)
        return table.concat(parts, ':')
    end
end

local function set_sdi_ipv6(bus, dpu_obj)
    skynet.fork(function()
        local ipv6, prefix_length, vlan
        local repeat_times = 0
        while true do
            ipv6, prefix_length = get_eth_ipv6(bus)
            vlan = get_eth_vlan(bus)
            ipv6 = pre_process_ipv6(ipv6)
            if ipv6 and prefix_length and vlan and dpu_obj:set_sdi_ipv6(CONFIG_BMC, ipv6, prefix_length, vlan) then
                log:notice('[DPU] set SDI card ipv6: %s %s %s', ipv6, prefix_length, vlan)
                break
            end
            skynet.sleep(200) -- 2s 重试1次
            repeat_times = repeat_times + 1
            if repeat_times > SET_IPV6_MAX_TIMES then
                log:debug('[DPU] Number of set SDI card ipv6 is more than %s times.', SET_IPV6_MAX_TIMES)
                break
            end
        end
    end)
    client:OnIpv6PropertiesChanged(function(values, _, _)
        skynet.sleep(200) -- 等待2s，等待资源树属性变更完成
        local ipv6, prefix_length = get_eth_ipv6(bus)
        local vlan = get_eth_vlan(bus)
        ipv6 = values['IpAddr'] and values['IpAddr']:value() or ipv6
        ipv6 = pre_process_ipv6(ipv6)
        prefix_length = values['PrefixLength'] and values['PrefixLength']:value() or prefix_length
        if dpu_obj:set_sdi_ipv6(CONFIG_BMC, ipv6, prefix_length, vlan) then
            log:notice('[DPU] set SDI card ipv6: %s %s %s', ipv6, prefix_length, vlan)
        end
    end)
end

local function set_sdi_ip(bus, dpu_obj)
    if not dpu_obj._signal then
        dpu_obj._signal = {}
    end
    set_sdi_ipv4(bus, dpu_obj)
    --set_sdi_ipv6
    set_sdi_ipv6(bus, dpu_obj)
    client:OnEthernetInterfacesPropertiesChanged(function(values, _, _)
        skynet.sleep(200) -- 等待2s，等待资源树属性变更完成
        local ipv4, mask = get_eth_ipv4(bus)
        local ipv6, prefix_length = get_eth_ipv6(bus)
        ipv6 = pre_process_ipv6(ipv6)
        local vlan = get_eth_vlan(bus)
        vlan = values['VLANId'] and values['VLANId']:value() or vlan
        if dpu_obj:set_sdi_ipv4(CONFIG_BMC, ipv4, mask, vlan) then
            log:notice('[DPU] set SDI card ipv4: %s %s %s', ipv4, mask, vlan)
        end
        if dpu_obj:set_sdi_ipv6(CONFIG_BMC, ipv6, prefix_length, vlan) then
            log:notice('[DPU] set SDI card ipv6: %s %s %s', ipv6, prefix_length, vlan)
        end
    end)
end

function dpu_service:update_sdi_slot(dpu_obj)
    skynet.fork_loop({count = 0}, function()
        while true do
            if dpu_obj:set_sdi_slot(dpu_obj.pciecard['SlotID']) then
                dpu_obj:update_pciecard_nodeid()
                log:info('[DPU] set SDI card SlotID: %s', dpu_obj.pciecard['SlotID'])
                break
            end
            skynet.sleep(200) -- 2s 重试1次
        end
        self:sort_dpu_objects_by_slot_id()
    end)
    dpu_obj._signal[#dpu_obj._signal + 1] = dpu_obj.pciecard.property_changed:on(function(name,
        value)
        log:info('[DPU] PropertiesChanged on %s', name)
        if name == 'SlotID' then
            dpu_obj:set_sdi_slot(value)
            dpu_obj:update_pciecard_nodeid()
            self:sort_dpu_objects_by_slot_id()
        end
    end)
end

function dpu_service:send_host_os_status(dpu_obj)
    -- MCU不支持会出现卡死的情况，需要pcall包一下
    return pcall(function ()
        local objects = client:GetSystemsFruCtrlObjects()
        if not objects then
            log:error('send host os status failed,fructrl obj is nil')
            return
        end
        local _, obj = next(objects)
        local status = (obj and obj.PowerState == 'ON') and 1 or 0
        dpu_obj:set_sdi_host_os_status(status)
    end)
end

function dpu_service:update_info_task_internal(dpu_obj)
    dpu_obj:fetch_capability()
    dpu_obj:fetch_mcu_fw_ver()
    dpu_obj:register_mcu_firmware_info(self.bus)
    dpu_obj:register_cpld_firmware_info(self.bus)
    dpu_obj:register_vrd_firmware_info(self.bus)
    dpu_obj:get_cpld_manufacture_id()
    dpu_obj:get_mcu_manufacture_id()
    dpu_obj:get_pcb_id()
    dpu_obj:get_extend_card_presence()
    dpu_obj:fetch_mcu_reset_time()
end

function dpu_service:update_info_every_thirty_second(dpu_obj, position)
    skynet.fork_loop({count = 0}, function()
        while true do
            if not self.dpu_objects or not self.dpu_objects[position] then
                return
            end
            dpu_obj:update_fw_ver() -- 查询sdi固件版本
            dpu_obj:update_mcu_log() -- 与硬件对齐每30s查询是否需要更新日志
            skynet.sleep(3000) -- 周期30s
        end
    end)
end

function dpu_service:update_sdi_property(bus, dpu_obj)
    dpu_obj:check_heartbeat_loss()
    skynet.sleep(50)
    dpu_obj:fetch_mac_address()
    skynet.sleep(50)
    dpu_obj:fetch_sdi_ip()
    skynet.sleep(50)
    dpu_obj:fetch_power()
    skynet.sleep(50)
    dpu_obj:fetch_boot_option()
    skynet.sleep(50)
    dpu_obj:fetch_pxe_option()
    skynet.sleep(50)
    dpu_obj:fetch_dpu_uuid()
    skynet.sleep(50)
    dpu_obj:fetch_m2_presence()
    skynet.sleep(50)
    dpu_obj:fetch_os_status()
    skynet.sleep(50)
    dpu_obj:fetch_bios_status()
    skynet.sleep(50)
    dpu_obj:fetch_mpu_busy_status()
    skynet.sleep(50)
    dpu_obj:fetch_secure_boot()
    skynet.sleep(50)
    dpu_obj:fetch_LLDP_status()
    skynet.sleep(50)
    dpu_obj:establish_serial_record_connection(self.bus) -- 启动建立串口录音连接任务
end

function dpu_service:fetch_dpu_hardware_info(dpu_obj)
    local fetch_version_suc
    local fetch_cpu_arch_suc
    local fetch_cpu_cores_suc
    local fetch_disk_size_suc
    local fetch_mem_size_suc
    for _ = 1, 5 do
        skynet.sleep(4500)
        if not fetch_version_suc then
            fetch_version_suc = dpu_obj:fetch_bios_version() and true or false
        end
        if not fetch_cpu_arch_suc then
            fetch_cpu_arch_suc = dpu_obj:fetch_cpu_arch() and true or false
        end
        if not fetch_cpu_cores_suc then
            fetch_cpu_cores_suc = dpu_obj:fetch_cpu_cores() and true or false
        end
        if not fetch_disk_size_suc then
            fetch_disk_size_suc = dpu_obj:fetch_disk_size() and true or false
        end
        if not fetch_mem_size_suc then
            fetch_mem_size_suc = dpu_obj:fetch_mem_size() and true or false
        end
        if fetch_mem_size_suc and fetch_disk_size_suc and
            fetch_cpu_cores_suc and fetch_cpu_arch_suc and fetch_version_suc then
            break
        end
    end
end

function dpu_service:get_dpu_power_status(dpu_obj, position)
    skynet.sleep(5500) -- 延时55s获取dpu电源状态, 避免事件不上报
    while true do
        if not self.dpu_objects or not self.dpu_objects[position] then
            return
        end
        dpu_obj:fetch_power_status()
        skynet.sleep(200) -- 周期2s
    end
end

-- 此函数需要重构，放到dpu_object里面，并且应该在获取能力码之后才能做set/get操作
function dpu_service:update_info_task(dpu_obj, position)
    skynet.sleep(200) -- 等待hwproxy
    self:update_info_task_internal(dpu_obj)

    -- 启动设置系统时间
    skynet.fork(function()
        while self:set_sys_time(dpu_obj) ~= E_OK do
            log:debug('[DPU] set system time fail, try again')
            skynet.sleep(500) -- 5s 重试1次
        end
        log:info('[DPU] set system time success')
    end)

    set_sdi_ip(self.bus, dpu_obj)
    self:update_sdi_slot(dpu_obj)
    -- sdi加载后，发送一次host os的状态给MCU，避免还未加载时Fructrl已经发生变化
    self:send_host_os_status(dpu_obj)

    skynet.fork_loop({count = 0}, function()
        self:get_dpu_power_status(dpu_obj, position)
    end)

    skynet.fork_loop({count = 0}, function()
        while true do
            if not self.dpu_objects or not self.dpu_objects[position] then
                return
            end
            self:update_sdi_property(self.bus, dpu_obj)
            skynet.sleep(30 * 100) -- 周期30s
        end
    end)

    skynet.fork_loop({count = 0}, function()
        while true do
            if not self.dpu_objects or not self.dpu_objects[position] then
                return
            end
            dpu_obj:fetch_device_temperature()
            dpu_obj:update_error_code_by_std_smbus(self.bus)
            skynet.sleep(200) -- 周期2s
        end
    end)
    -- 电子标签
    skynet.fork(function()
        --[[
            UBNIC卡装备有写电子标签的TU，不能使用MCU的电子标签，
            如果使用，会将Eeprom的电子标签覆盖
        ]]
        if string.match(dpu_obj.pciecard.DeviceName, '.*QT100n.*') then
            return
        end
        dpu_obj:get_sdi_elabel(function(...)
            dpu_obj:set_frudata(dpu_obj.ref_dpu_frudata, ...)
        end)
    end)
    skynet.fork(function()
        self:fetch_dpu_hardware_info(dpu_obj)
    end)
    self:update_info_every_thirty_second(dpu_obj, position)
end

function dpu_service:get_uart_presence(default_smc)
    if next(self.dpu_detection_list) then
        return self.dpu_detection_list[1].UartPresence
    end
    local ok, res = pcall(function()
        return default_smc:Read(ctx.get_context_or_default(), 0x800c900, 1):byte()
    end)
    if ok then
        return res
    end
    log:debug('get uart presence failed, error is %s', res)
    return nil
end

function dpu_service:monitor_serial_connection(dpu_obj)
    -- 如果CSR配置IgnoreSerialCableAlarm，就不用告警。
    if dpu_obj.inner.IgnoreSerialCableAlarm then
        log:notice('No need to create sdi serial monitor')
        return
    end

    if self.monitor_serial_task then
        return
    end
    self.monitor_serial_task = true
    local retry = 3
    local ok, default_smc

    -- 不存在SmartNICDetection对象时，串口线缆在位信号需要通过扩展板的smc获取
    while not next(self.dpu_detection_list) and retry >= 0 do
        -- 由于无法跨SR引用，通过直接获取的方式获取扩展板smc chip对象
        ok, default_smc = pcall(mdb.get_object, self.bus, '/bmc/kepler/Chip/Smc/Smc_ExpBoardSMC_0101',
            'bmc.kepler.Chip.BlockIO')
        if ok then
            break
        end
        retry = retry - 1
        skynet.sleep(500)
    end

    if not next(self.dpu_detection_list) and (not ok or not default_smc) then
        log:error('Unable to create sdi serial monitor. Cannot get uart presence smc')
        self.monitor_serial_task = false
        return
    end

    local uart_presence
    while true do
        uart_presence = self:get_uart_presence(default_smc)
        dpu_obj:general_serial_disconnect_event(self.bus, uart_presence)
        if not self.dpu_objects or not next(self.dpu_objects) then
            self.monitor_serial_task = false
            skynet.sleep(500)
            dpu_obj:clear_serial_disconnect_event(self.bus)
            return
        end
        skynet.sleep(500)
    end
end

local function get_fw_version(firmware_type, dpu, cpld_id)
    local fw_version = {
        ['Mcu'] = dpu.dpucard['MCUVersion'],
        ['DPUCpld'] = dpu.dpucard['MultiLogicVersion'][string.format("CPLD%s", cpld_id)],
        ['DPUVrd'] = dpu.dpucard['VrdVersion']
    }

    return fw_version[firmware_type]
end

local function in_uid(uids, uid)
    -- SDI升级包无uid不支持升级
    if uids and #uids == 0 or not uid then
        log:error("[DPU] no uids in upgrade.cfg")
        return false
    end

    for _, v in pairs(uids) do
        if v == uid then
            return true
        end
    end

    return false
end

function dpu_service:is_valid_firmware(firmware_type, cfg, dpu, cpld_id)
    if not in_uid(cfg.uids, dpu.uid) then
        log:notice('[DPU] invalid uid: %s', dpu.uid)
        return false
    end

    if cfg.component_id ~= utils.FIRMWARE_TYPE_COMPONENT_ID[firmware_type] then
        return false
    end

    local cur_manufacture_id = {
        ['Mcu'] = dpu.mcu_manufacture_id,
        ['DPUCpld'] = dpu.cpld_manufacture_ids[cpld_id],
        ['DPUVrd'] = 0 -- VRD只有一个厂商，当前固定为0
    }
    -- Byte0代表厂商，与当前环境的DPU厂商进行比较
    local manufacture_id = cfg.component_idex & 0xFF
    if manufacture_id ~= cur_manufacture_id[firmware_type] then
        log:notice('[DPU] manufacture_id: %s, cur_manufacture_id: %s',
            manufacture_id, cur_manufacture_id[firmware_type])
        return false
    end

    return true
end

function dpu_service:is_dpu_mcu(firmware_type, cfg_path)
    if not utils.FIRMWARE_TYPE_COMPONENT_ID[firmware_type] then
        log:debug("This is not a dpu mcu, firmware type not match")
        return false
    end

    if not utils.get_firmware_uid(cfg_path, 'Firmware1', 'UID') then
        log:debug("This is not a dpu mcu, can not get UID")
        return false
    end
    return true
end

local function get_vme_file(dir)
    local files = utils_core.dir(dir)
    for _, file in pairs(files) do
        if file:match('.vme$') then
            return file
        end
    end
    return nil
end

-- @brief: process the received UpgradePrepareSignal emitted from firmware_mgmt
function dpu_service:on_upgrade_prepare(_, system_id, firmware_type, cfg_path, _, parameters)
    if parameters and parameters.UpgradeMode and parameters.UpgradeMode == "Parallel" and
        self.upgrade_state[firmware_type] then
        log:debug("[dpu_service] firmware %s is upgrading, add to upgrade queue", firmware_type)
        client:UpdateServiceUpdateServicePrepareReply(ctx.new(), system_id, firmware_type, '',
            utils.DBUS_CALL_CODE.UPGRADING, parameters)
        return
    end
    self.upgrade_state[firmware_type] = true

    log:notice("[dpu_service] prepare_upgrade, firmware_type:%s", firmware_type)
    local cpld_id = 1
    if firmware_type == 'DPUCpld' then
        local file_path = string.match(cfg_path, '/.+/') .. 'Firmware1'
        if file_sec.check_real_path_s(file_path) ~= E_OK then
            log:error("[dpu_service] file %s not exist", file_path)
            self.upgrade_state[firmware_type] = false
            return
        end
        local dir = utils.uncompress_firmware(file_path)
        if not utils_core.is_dir(dir) then
            log:error("[dpu_service] uncompress firmware failed")
            self.upgrade_state[firmware_type] = false
            return
        end
        local file_name = get_vme_file(dir)
        if not file_name then
            log:error("[dpu_service] firmware package incorrect")
            self.upgrade_state[firmware_type] = false
            return
        end
        cpld_id = utils.check_CPLD_upgrade_name(file_name)
        log:notice("[dpu_service] the current upgrade is the CPLD%s", cpld_id)
    end
    if not self:is_dpu_mcu(firmware_type, cfg_path) then
        self.upgrade_state[firmware_type] = false
        return
    end

    local ok, rsp = client:PFileFileChown(ctx.new(), nil, cfg_path, 104, 104)  --设置配置文件属主为secbox
    if not ok then
        log:error('[DPU] chown cfg file failed, error %s', rsp)
        self.upgrade_state[firmware_type] = false
        client:UpdateServiceUpdateServicePrepareReplyparameters(
            ctx.new(), system_id, firmware_type, '', utils.DBUS_CALL_CODE.RET_ERR, parameters)
        return
    end
    self.upgradable_cfgs = {}
    local cfgs = utils.get_cfgs(cfg_path)
    for _, dpu in pairs(self.dpu_objects) do
        dpu.firmware_name = nil
        for k, cfg in pairs(cfgs) do
            if self:is_valid_firmware(firmware_type, cfg, dpu, cpld_id) then
                log:notice('[DPU] add firmware config, component_id: %s, component_idex: %s',
                    cfg.component_id, cfg.component_idex)
                self.upgradable_cfgs[k] = cfg
                dpu.firmware_name = cfg.name
            end
        end
    end

    if next(self.upgradable_cfgs) == nil then
        log:notice("upgradable cfgs empty")
        self.upgrade_state[firmware_type] = false
        client:UpdateServiceUpdateServicePrepareReply(ctx.new(), system_id, firmware_type, '',
            utils.DBUS_CALL_CODE.RET_ERR, parameters)
        return
    end

    local _, dpu = next(self.dpu_objects)
    local cur_fw_version = get_fw_version(firmware_type, dpu, cpld_id)
    -- Notify the firmware_mgmt app an available dpu version
    client:UpdateServiceUpdateServicePrepareReply(ctx.new(), system_id, firmware_type, cur_fw_version,
        utils.DBUS_CALL_CODE.RET_OK, parameters)

    log:notice('[DPU] prepare DPU upgrade end, firmware type: %s, current version: %s', firmware_type, cur_fw_version)
end

local function get_fw_path(firmware_type, dpu, dir)
    local fw_path = ''

    local fw_raw_table = {
        ['Mcu'] = dpu.mcu_fw_raw,
        ['DPUCpld'] = dpu.cpld_fw_raw,
        ['DPUVrd'] = dpu.vrd_fw_raw
    }

    local fw_raw = fw_raw_table[firmware_type]
    if not fw_raw then
        return fw_path, nil
    end

    for k, v in pairs(fw_raw) do
        fw_path = dir .. v
        if vos.get_file_accessible(fw_path) then
            return fw_path, v
        end
    end

    return '', nil
end

function dpu_service:upgrade_task(system_id, firmware_type, file_path, firmware_name, parameters)
    log:notice("[DPU] upgrade task, firmware_type:%s", firmware_type)

    local is_success = false
    local dir = utils.uncompress_firmware(file_path)
    local dpu_index = 1
    for _, dpu in pairs(self.dpu_objects) do
        -- 升级包中可能存在多个固件，取适合当前dpu卡的固件
        if firmware_name ~= dpu.firmware_name then
            log:error("upgrade skip dpu firmware name: %s, firmware_name: %s", dpu.firmware_name, firmware_name)
            goto continue
        end

        local fw_path, file_name = get_fw_path(firmware_type, dpu, dir)
        local cpld_id = utils.check_CPLD_upgrade_name(file_name)
        local upgrade_object = dpu_upgrade_object.new(dpu)
        if vos.get_file_accessible(fw_path) then
            local ok, status = pcall(function()
                return upgrade_object:upgrade(
                    fw_path, dpu_index, self.dpu_count, system_id, firmware_type, parameters, cpld_id)
            end)
            if not ok or not status then
                log:error("[DPU] %s upgrade fail, status: %s", dpu.uid, status)
                return false
            end
            is_success = true
            dpu_index = dpu_index + 1
        end
        ::continue::
    end

    return is_success
end

-- @brief: handle the UpgradeProcessSignal
function dpu_service:on_upgrade_process(_, system_id, firmware_type, file_path, parameters)
    if next(self.upgradable_cfgs) == nil then
        self.upgrade_state[firmware_type] = false
        return
    end

    log:notice('[DPU Service] receive process signal, firmware_type: %s', firmware_type)
    local ok, rsp = client:PFileFileChown(ctx.new(), nil, file_path, 104, 104)
    if not ok then
        log:error('[DPU Service] chown file failed, error %s', rsp)
        self.upgrade_state[firmware_type] = false
        client:UpdateServiceUpdateServiceProcessReply(ctx.new(), system_id, firmware_type, utils.DBUS_CALL_CODE.RET_ERR,
            parameters)
        return
    end

    local name = string.match(file_path, '[^/]+$')
    if not self.upgradable_cfgs[name] then
        log:error('[DPU Service] match firmware config fail, name: %s', name)
        self.upgrade_state[firmware_type] = false
        client:UpdateServiceUpdateServiceProcessReply(ctx.get_context_or_default(), system_id, firmware_type,
            utils.DBUS_CALL_CODE.RET_ERR, parameters)
        return
    end

    dpu_enums.DPU_UPGRADING = 1
    local ok, ret = pcall(function()
        return self:upgrade_task(system_id, firmware_type, file_path, name, parameters)
    end)
    dpu_enums.DPU_UPGRADING = 0
    if not ok or not ret then
        log:error('[DPU Service] upgrade task failed, error: %s', ret)
        self.upgrade_state[firmware_type] = false
        client:UpdateServiceUpdateServiceProcessReply(ctx.get_context_or_default(), system_id, firmware_type,
            utils.DBUS_CALL_CODE.RET_ERR, parameters)
        return
    end

    client:UpdateServiceUpdateServiceProcessReply(ctx.get_context_or_default(), system_id, firmware_type,
        utils.DBUS_CALL_CODE.RET_OK, parameters)
end

-- @brief: handle the UpgradeFinishSignal
function dpu_service:on_upgrade_finish(_, system_id, firmware_type, parameters)
    if next(self.upgradable_cfgs) == nil then
        return
    end

    log:notice('[DPU] finish DPU upgrade, firmware type: %s', firmware_type)
    self.upgrade_state[firmware_type] = false
    client:UpdateServiceUpdateServiceFinishReply(ctx.new(), system_id, firmware_type,
        utils.DBUS_CALL_CODE.RET_OK, parameters)
end

function dpu_service:add_object(dpu_obj, position)
    skynet.fork(function()
        self:update_info_task(dpu_obj, position)
        self:monitor_serial_connection(dpu_obj)
    end)
end

function dpu_service:sort_dpu_objects_by_slot_id()
    --根据dpu_obj的slot_id对table进行排列
    table.sort(self.ordered_dpu_position_objects, function (a, b)
        return self.dpu_objects[a].pciecard['SlotID'] < self.dpu_objects[b].pciecard['SlotID']
    end)
end

function dpu_service:on_add_default_dpu_object(dpu_object, object, position)
    local dpu_obj = dpu_object.new(object, position)
    self.dpu_objects[position] = dpu_obj
    self.ordered_dpu_position_objects[#self.ordered_dpu_position_objects + 1] = position
    self:sort_dpu_objects_by_slot_id()
    self.dpu_count = self.dpu_count + 1
    self:add_object(dpu_obj, position)
    dpu_obj:update_property_task()
    dpu_obj:add_prop_before_change_callback()
    if self.sr_upgrade_list[position] then
        dpu_obj:set_dpu_type(self.sr_upgrade_list[position].Type)
    end
    if self.hotplug then
        dpu_obj:set_hotplug(self.hotplug)
    end
end

local function dpu_plug_log_operation_log(info, type)
    if type ~= 'add' and type ~= 'delete' then
        log:error('dpu_plug_log_operation_log failed, type (%s) invalid', type)
        return
    end

    local description = string.format('vid: 0x%04x, did: 0x%04x, sub_vid: 0x%04x, sub_did: 0x%04x', info.vid,
        info.did, info.sub_vid, info.sub_did)

    if info.sn ~= '' and info.sn ~= 'N/A' then
        description = description .. string.format(', SN: %s', info.sn)
    elseif info.pn  ~= '' and info.pn  ~= 'N/A' then
        description = description .. string.format(', PN: %s', info.pn)
    end

    log:operation(initiator.new('N/A', 'N/A', '127.0.0.1'), 'general_hardware', 'The PCIe card%d(%s) %s successfully',
        info.slot_id, description, type == 'add' and 'plug in' or 'plug out')
end

function dpu_service:dpu_plug_out_record(obj_info, position)
    if not obj_info then
        log:error('dpu plug out record failed. dpu_obj (%s) is nil.', position)
        return
    end

    if obj_info.slot_id == 0 then
        log:notice('dpu plug out record failed, slot_id not ready')
        return
    end

    self.db:delete(self.db.DPUCardMapping):where({ SlotID = obj_info.slot_id }):exec()
    dpu_plug_log_operation_log(obj_info, 'delete')
end

local function dpu_plug_in_record_task(obj, db)
    local obj_info = {
        slot_id = obj[PCIECARD_INTF].SlotID,
        vid = obj[PCIECARD_INTF].VendorID,
        did = obj[PCIECARD_INTF].DeviceID,
        sub_did = obj[PCIECARD_INTF].SubDeviceID,
        sub_vid = obj[PCIECARD_INTF].SubVendorID,
        sn = obj[PCIECARD_INTF].SerialNumber,
        pn = obj[PCIECARD_INTF].PartNumber
    }

    -- 对象数据已有时，不重复记录日志
    local db_obj = db:select(db.DPUCardMapping):where({
        SlotID = obj_info.slot_id, VendorID = obj_info.vid,  DeviceID = obj_info.did, SubDeviceID = obj_info.sub_did,
        SubVendorID = obj_info.sub_vid}):first()

    if db_obj then
        log:notice(
            'already record dpu plug in log, slot_id: %d, vid: 0x%04x, did: 0x%04x, sub_vid: 0x%04x, sub_did: 0x%04x',
            obj_info.slot_id, obj_info.vid, obj_info.did, obj_info.sub_vid, obj_info.sub_did)
        return
    end

    db.DPUCardMapping({
        SlotID = obj_info.slot_id,
        VendorID = obj_info.vid,
        DeviceID = obj_info.did,
        SubDeviceID = obj_info.sub_did,
        SubVendorID = obj_info.sub_vid
        }):save()

    dpu_plug_log_operation_log(obj_info, 'add')
end

function dpu_service:dpu_plug_in_record(obj, position)
    if not obj then
        log:error('dpu plug in record failed. dpu_obj (%s) is nil.', position)
        return
    end

    -- 监听slot_id变化
    -- slot_id同步完成才记录日志
    local has_recorded = false
    obj[PCIECARD_INTF].property_changed:on(function(name, value)
            if has_recorded or name ~= 'SlotID' then
                return
            end

            if value ~= 0 then
                has_recorded = true
                dpu_plug_in_record_task(obj, self.db)
            end
        end
    )

    if not has_recorded and obj[PCIECARD_INTF].SlotID ~= 0 then
        has_recorded = true
        dpu_plug_in_record_task(obj, self.db)
        return
    end
end

local plug_record = {
    ['DPUCard'] = {
        add = dpu_service.dpu_plug_in_record,
        delete = dpu_service.dpu_plug_out_record
    }
}

-- DPU的Model与带外管理文件的对应关系，未匹配时使用默认的dpu_object文件
local hardware_config_model<const> = {
    ['HyperCard4.0'] = 'hypercard_object',
    ['BF2'] = 'bluefield_object',
    ['BF3'] = 'bluefield_object'
}
function dpu_service:on_add_object(class_name, object, position)
    -- 记录卡插入操作日志
    if plug_record[class_name] then
        skynet.fork_once(function ()
            self.queue(function(service, obj, pos)
                plug_record[class_name]['add'](service, obj, pos)
            end, self, object, position)
        end)
    end
    local switch = {
        ['DPUCard'] = function ()
            local ok
            local dpu_object
            local model = hardware_config_model[object.Model]
            if not model then
                dpu_object = require 'dpu_service.dpu_object'
                self:on_add_default_dpu_object(dpu_object, object, position)
                return
            end
            ok, dpu_object = pcall(require, string.format('dpu_service.%s', model))
            if not ok or not dpu_object then
                log:error('No specific dpu_object file for this DPUCard(Model: %s)', object.Model)
                return
            end
            local dpu_obj = dpu_object.new(object, position)
            dpu_obj:start()
            self.dpu_objects_with_model[position] = dpu_obj
            if self.hotplug then
                dpu_obj:set_hotplug(self.hotplug)
            end
        end,
        ['SRUpgrade'] = function ()
            self.sr_upgrade_list[position] = object
            if self.dpu_objects[position] then
                self.dpu_objects[position]:set_dpu_type(self.sr_upgrade_list[position].Type)
            end
        end,
        ['SmartNICDetection'] = function ()
            self.dpu_detection_list[#self.dpu_detection_list + 1] = object
        end,
        ['DPUCardHotplug'] = function ()
            self.hotplug = object
            for _, dpu_obj in pairs(self.dpu_objects) do
                dpu_obj:set_hotplug(object)
            end
        end
    }
    if switch[class_name] then
        switch[class_name]()
        log:info('[DPU] add object, class: %s, position: %s',class_name, position)
    end
end

function dpu_service:on_delete_object(class_name, object, position)
    -- 记录卡拔出操作日志
    if plug_record[class_name] then
        local obj_info = {
            slot_id = object[PCIECARD_INTF].SlotID,
            vid = object[PCIECARD_INTF].VendorID,
            did = object[PCIECARD_INTF].DeviceID,
            sub_did = object[PCIECARD_INTF].SubDeviceID,
            sub_vid = object[PCIECARD_INTF].SubVendorID,
            sn = object[PCIECARD_INTF].SerialNumber,
            pn = object[PCIECARD_INTF].PartNumber
        }
        skynet.fork_once(function ()
            self.queue(function(service, info, pos)
                plug_record[class_name]['delete'](service, info, pos)
            end, self, obj_info, position)
        end)
    end
    local switch = {
        ['DPUCard'] = function ()
            self.dpu_objects[position]:set_clear_task_flag()
            self.dpu_objects[position] = nil
            cmn.remove_ele_by_value(self.ordered_dpu_position_objects, position)
            self.dpu_count = self.dpu_count - 1
            if self.dpu_objects_with_model[position] and self.dpu_objects_with_model[position].stop then
                self.dpu_objects_with_model[position]:stop()
                self.dpu_objects_with_model[position] = nil
            end
        end,
        ['SRUpgrade'] = function ()
            self.sr_upgrade_list[position] = nil
            if self.dpu_objects[position] then
                self.dpu_objects[position]:set_dpu_type('')
            end
        end
    }
    if switch[class_name] then
        switch[class_name]()
    end
end

function dpu_service:get_dpu_obj_by_slot_id(slot_id)
    for _, dpu_obj in pairs(self.dpu_objects) do
        if dpu_obj.pciecard['SlotID'] == slot_id then
            return dpu_obj
        end
    end
    return
end

function dpu_service:send_host_os_status_to_sdi_mcu(status)
    local ok, err
    for _, dpu_obj in pairs(self.dpu_objects) do
        ok, err = dpu_obj:set_sdi_host_os_status(status)
        if not ok then
            log:error('send host os status to sdi mcu failed, err: %s', err)
        end
    end
end

-- 通过path返回对应的DPUCard对象
function dpu_service:get_dpu_obj_by_path(path)
    for _, dpu_obj in pairs(self.dpu_objects) do
        if dpu_obj.inner.path == path then
            return dpu_obj
        end
    end

    for _, dpu_obj in pairs(self.dpu_objects_with_model) do
        if dpu_obj.obj.path == path then
            return dpu_obj
        end
    end
end

-- DeviceNo为设备逻辑序号，从1开始，根据slot_id从小到大排列
function dpu_service:get_dpu_obj_by_device_no(device_no)
    if not self.ordered_dpu_position_objects[device_no] then
        return
    end
    return self.dpu_objects[self.ordered_dpu_position_objects[device_no]]
end

function dpu_service:get_slot_id(position)
    if not position then
        return
    end
    local retries = 0
    local slot_id
    repeat
        retries = retries + 1
        if self.dpu_objects[position] then
            slot_id = self.dpu_objects[position]:get_slot_id()
            break
        else
            skynet.sleep(200)
        end
    until retries > 30 -- 防止出现卡没注册的情况
    return slot_id
end

-- SDI5.0启动项:0是unset，1是PXE启动，2是本地磁盘启动(注意LUA列表从1开始)
local op_log_for_boot_options<const> = {'None', 'PXE', 'HDD'}
local switch_boot_option<const> = {[0] = 2, [1] = 1, [255] = 0}

function dpu_service:set_dpu_boot_option(req, ctx)
    -- 合法性检查
    if req.ManufactureId ~= MANUFACTURE_ID then
        log:error('[SDI_IPMI]request ManufactureId(%s) is invaild', req.ManufactureId)
        return msg.SetDpuBootOptionRsp.new(cc.InvalidFieldRequest, MANUFACTURE_ID)
    end
    if req.BootOrder ~= 0 and req.BootOrder ~= 1 and req.BootOrder ~= 255 then
        log:error('[SDI_IPMI]SDI card  boot order(%s) is invaild', req.BootOrder)
        return msg.SetDpuBootOptionRsp.new(cc.ParmOutOfRange, MANUFACTURE_ID)
    end
    if req.BootValidType < 0 or req.BootValidType > 1 then
        log:error('[SDI_IPMI]SDI card  BootValidType(%s) is invaild', req.BootValidType)
        return msg.SetDpuBootOptionRsp.new(cc.ParmOutOfRange, MANUFACTURE_ID)
    end
    local dpu = self:get_dpu_obj_by_device_no(req.DeviceNo)
    if not dpu then
        log:error('[SDI_IPMI] get DPU Object by device_no(%s) fail', req.DeviceNo)
        return msg.SetDpuBootOptionRsp.new(cc.DataNotAvailable, MANUFACTURE_ID)
    end

    local new_boot_order = switch_boot_option[req.BootOrder]
    local ok, err = dpu:set_dpu_boot_option(req.BootValidType, new_boot_order)
    if not ok then
        log:error('set dpu boot option failed, err: %s', err)
        ipmi.ipmi_operation_log(ctx, 'general_hardware', 'Set the boot option of PCIeCard failed')
        return msg.SetDpuBootOptionRsp.new(cc.UnspecifiedError, MANUFACTURE_ID)
    end
    -- 操作日志
    ipmi.ipmi_operation_log(ctx, 'general_hardware',
        'Set the boot option of PCIeCard%s(%s) to %s %s successfully', dpu.pciecard['SlotID'],
        dpu.pciecard['Name'], op_log_for_boot_options[new_boot_order + 1],
        req.BootValidType == 0 and 'Once' or 'Permanent')
    return msg.SetDpuBootOptionRsp.new(cc.Success, MANUFACTURE_ID)
end

function dpu_service:reset_sdi_card(req, ctx)
    -- 合法性检查
    if req.ManufactureId ~= MANUFACTURE_ID then
        log:error('[SDI_IPMI]request ManufactureId(%s) is invaild', req.ManufactureId)
        return msg.RestSDIRsp.new(cc.InvalidFieldRequest, MANUFACTURE_ID)
    end
    local dpu = self:get_dpu_obj_by_device_no(req.DeviceNo)
    if not dpu then
        log:error('[SDI_IPMI] get DPU Object by device_no(%s) fail', req.DeviceNo)
        return msg.RestSDIRsp.new(cc.DataNotAvailable, MANUFACTURE_ID)
    end

    local ok, err = dpu:reset_sdi_card()
    if not ok then
        log:error('reset dpu card failed, err: %s', err)
        ipmi.ipmi_operation_log(ctx, 'general_hardware', 'Reset PCIeCard%s(%s) failed',
            dpu.pciecard['SlotID'], dpu.pciecard['Name'])
        return msg.RestSDIRsp.new(cc.UnspecifiedError, MANUFACTURE_ID)
    end
    ipmi.ipmi_operation_log(ctx, 'general_hardware', 'Reset PCIeCard%s(%s) successfully',
        dpu.pciecard['SlotID'], dpu.pciecard['Name'])
    return msg.RestSDIRsp.new(cc.Success, MANUFACTURE_ID)
end

function dpu_service:set_dpu_nmi(req, ctx)
    -- 合法性检查
    if req.ManufactureId ~= MANUFACTURE_ID then
        log:error('[SDI_IPMI]request ManufactureId(%s) is invaild', req.ManufactureId)
        return msg.SetDpuNMIRsp.new(cc.InvalidFieldRequest, MANUFACTURE_ID)
    end
    local dpu = self:get_dpu_obj_by_device_no(req.DeviceNo)
    if not dpu then
        log:error('[SDI_IPMI] get DPU Object by device_no(%s) fail', req.DeviceNo)
        ipmi.ipmi_operation_log(ctx, 'general_hardware', 'Set the sdi card NMI failed')
        return msg.SetDpuNMIRsp.new(cc.DataNotAvailable, MANUFACTURE_ID)
    end

    local ok, err = dpu:set_dpu_nmi()
    if not ok then
        log:error('set dpu nmi failed, err: %s', err)
        ipmi.ipmi_operation_log(ctx, 'general_hardware', 'Set PCIeCard%s(%s) NMI failed',
            dpu.pciecard['SlotID'], dpu.pciecard['Name'])
        return msg.SetDpuNMIRsp.new(cc.UnspecifiedError, MANUFACTURE_ID)
    end
    ipmi.ipmi_operation_log(ctx, 'general_hardware', 'Set PCIeCard%s(%s) NMI successfully',
        dpu.pciecard['SlotID'], dpu.pciecard['Name'])
    return msg.SetDpuNMIRsp.new(cc.Success, MANUFACTURE_ID)
end

function dpu_service:set_dpu_nmi_rpc(ctx, slot_id)
    local dpu = self:get_dpu_obj_by_slot_id(slot_id)
    if not dpu then
        log:error('[sdi_rpc] get DPU Object by slot_id(%s) fail', slot_id)
        log:operation(ctx:get_initiator(), 'general_hardware', 'Set the sdi card NMI failed')
        error(custom_messages.SetNMIFailed())
    end

    local ok, err = dpu:set_dpu_nmi()
    if not ok then
        log:error('set dpu nmi failed, err: %s', err)
        log:operation(ctx:get_initiator(), 'general_hardware',
            'Set PCIeCard%s(%s) NMI failed', dpu.pciecard['SlotID'], dpu.pciecard['Name'])
        error(custom_messages.SetNMIFailed())
    end
    log:operation(ctx:get_initiator(), 'general_hardware',
        'Set PCIeCard%s(%s) NMI successfully', dpu.pciecard['SlotID'], dpu.pciecard['Name'])
end

local SERIAL_DIRECTION_TO_STR = {
    [0] = 'OFF',
    [1] = 'ManagementControlUnit',
    [2] = 'SystemControlUnit',
    [3] = 'ComputationalProcessingUnit'
}

function dpu_service:set_serial_direction(obj, context, direction)
    log:notice('set_serial_direction %s start.', direction)
    local device_name = 'PCIeCard'

    local dpu = self:get_dpu_obj_by_path(obj.path)
    if not dpu or not dpu.set_dpu_serial_direction then
        log:error('get DPU Object by path(%s) failed or set serial direction is not supported', obj.path)
        log:operation(context:get_initiator(), 'general_hardware', "Set %s serial direction failed", device_name)
        error(custom_messages.OperationFailed())
    end

    local ok, err = dpu:set_dpu_serial_direction(direction)
    if not ok then
        log:error('set serial direction failed, err: %s', err)
        log:operation(context:get_initiator(), 'general_hardware',
            "Set %s serial direction to %s failed", dpu.pciecard['DeviceName'], SERIAL_DIRECTION_TO_STR[direction])
        error(custom_messages.OperationFailed())
    end
    -- 该操作日志只是代表命令是否下发成功
    log:operation(context:get_initiator(), 'general_hardware',
        'Set %s serial direction to %s successfully', dpu.pciecard['DeviceName'], SERIAL_DIRECTION_TO_STR[direction])
end

function dpu_service:reset_dpu_rpc(obj, context)
    if not obj or not obj.path then
        log:error('get path of DPUCard failed')
        error(custom_messages.OperationFailed())
    end
    local dpu = self:get_dpu_obj_by_path(obj.path)
    if not dpu or not dpu.reset_sdi_card then
        log:error('get DPU Object by path(%s) failed or reset DPU is not supported', obj.path)
        error(custom_messages.OperationFailed())
    end

    local ok, err = dpu:reset_sdi_card()
    if not ok then
        log:error('reset dpu card failed, err: %s', err)
        log:operation(context:get_initiator(), 'general_hardware', 'Reset PCIeCard%s(%s) failed',
            dpu.pciecard['SlotID'], dpu.pciecard['Name'])
        error(custom_messages.OperationFailed())
    end
    log:operation(context:get_initiator(), 'general_hardware', 'Reset PCIeCard%s(%s) successfully',
        dpu.pciecard['SlotID'], dpu.pciecard['Name'])
end

local boot_options<const> = {'None', 'Base Board PXE', 'HDD'}

function dpu_service:set_dpu_boot_option_rpc(obj, context, mode, enabled)
    if mode ~= 0 and mode ~= 1 and mode ~= 2 then
        log:error("boot mode is invalid, value: %s", mode)
        error(custom_messages.PropertyValueError('BootOption'))
    end
    if enabled ~= 0 and enabled ~= 1 then
        log:error("boot enabled is invalid, value: %s", enabled)
        error(custom_messages.PropertyValueError('BootEffective'))
    end
    if not obj or not obj.path then
        log:error('get path of DPUCard failed')
        error(custom_messages.OperationFailed())
    end
    local dpu = self:get_dpu_obj_by_path(obj.path)
    if not dpu or not dpu.set_dpu_boot_option then
        log:error('get DPU Object by path(%s) failed or set boot option is not supported', obj.path)
        error(custom_messages.OperationFailed())
    end

    local ok, err = dpu:set_dpu_boot_option(enabled, mode)
    if not ok then
        log:error('set dpu boot option failed, err: %s', err)
        log:operation(context:get_initiator(), 'general_hardware', 'Set the boot option of PCIeCard failed')
        error(custom_messages.OperationFailed())
    end

    dpu.dpucard['BootSourceOverrideMode'] = mode
    dpu.dpucard['BootSourceOverrideEnabled'] = enabled

    log:notice('Set the boot option of PCIeCard%s(%s) to %s %s successfully', dpu.pciecard['SlotID'],
        dpu.pciecard['Name'], boot_options[mode + 1], enabled == 0 and 'Once' or 'Permanent')

    log:operation(context:get_initiator(), 'general_hardware',
        'Set the boot option of PCIeCard%s(%s) to %s %s successfully', dpu.pciecard['SlotID'],
        dpu.pciecard['Name'], boot_options[mode + 1], enabled == 0 and 'Once' or 'Permanent')
end

function dpu_service:set_power_state(obj, context, power_state)
    if power_state ~= 0 and power_state ~= 1 and power_state ~= 5 then
        log:error("power state is invalid, value: %s", power_state)
        error(custom_messages.PropertyValueError('PowerState'))
    end
    local dpu = self:get_dpu_obj_by_path(obj.path)
    if not dpu or not dpu.set_dpu_power_state then
        log:error('get DPU Object by path(%s) failed or set power state is not supported', obj.path)
        error(custom_messages.OperationFailed())
    end

    local ok, err = dpu:set_dpu_power_state(power_state)
    local value = POWER_STATE_TO_STR[power_state]
    if not ok then
        log:error('set power state failed, err: %s', err)
        log:operation(context:get_initiator(), "general_hardware",
            "Set PCIeCard%s(%s) power %s failed", dpu.pciecard['SlotID'],
            dpu.pciecard['Name'], string.lower(value))
        error(custom_messages.OperationFailed())
    end

    -- 该操作日志只是代表命令是否下发成功
    log:operation(context:get_initiator(), "general_hardware",
        "Set PCIeCard%s(%s) power %s successfully", dpu.pciecard['SlotID'],
        dpu.pciecard['Name'], string.lower(value))
end

function dpu_service:set_dpu_power_state(req, ctx)
    -- 合法性检查
    if req.ManufactureId ~= MANUFACTURE_ID then
        log:error('[SDI_IPMI]request ManufactureId(%s) is invaild', req.ManufactureId)
        return msg.SetDpuPowerStateRsp.new(cc.InvalidFieldRequest, MANUFACTURE_ID)
    end
    local dpu = self:get_dpu_obj_by_device_no(req.DeviceNo)
    if not dpu then
        log:error('[SDI_IPMI] get DPU Object by device_no(%s) fail', req.DeviceNo)
        return msg.SetDpuPowerStateRsp.new(cc.DataNotAvailable, MANUFACTURE_ID)
    end
    if dpu:is_dpu_powerstate_valid(req.PowerState) then
        log:error('[SDI_IPMI] power state(%s) is vaild', req.PowerState)
        return msg.SetDpuPowerStateRsp.new(cc.ParmOutOfRange, MANUFACTURE_ID)
    end

    local ok, err = dpu:set_dpu_power_state(req.PowerState)
    if not ok then
        log:error('set dpu power state failed, err: %s', err)
        ipmi.ipmi_operation_log(ctx, 'general_hardware', 'Set PCIeCard%s(%s) power %s failed',
            dpu.pciecard['SlotID'], dpu.pciecard['Name'], (req.PowerState == 0 or req.PowerState == 5) and 'off' or 'on')
        return msg.SetDpuPowerStateRsp.new(cc.UnspecifiedError, MANUFACTURE_ID)
    end
    ipmi.ipmi_operation_log(ctx, 'general_hardware', 'Set PCIeCard%s(%s) power %s successfully',
        dpu.pciecard['SlotID'], dpu.pciecard['Name'], (req.PowerState == 0 or req.PowerState == 5) and 'off' or 'on')
    return msg.SetDpuPowerStateRsp.new(cc.Success, MANUFACTURE_ID)
end

function dpu_service:set_dpu_reset_linkage(req, ctx)
    -- 合法性检查
    if req.ManufactureId ~= MANUFACTURE_ID then
        log:error('[SDI_IPMI]request ManufactureId(%s) is invaild', req.ManufactureId)
        return msg.SetDpuResetLinkageRsp.new(cc.InvalidFieldRequest, MANUFACTURE_ID)
    end
    if req.ResetLinkage ~= 0 and req.ResetLinkage ~= 1 then
        log:error('[SDI_IPMI] reset linkage(%s) is vaild', req.ResetLinkage)
        return msg.SetDpuResetLinkageRsp.new(cc.ParmOutOfRange, MANUFACTURE_ID)
    end
    local dpu = self:get_dpu_obj_by_device_no(req.DeviceNo)
    if not dpu then
        log:error('[SDI_IPMI] get DPU Object by device_no(%s) fail', req.DeviceNo)
        ipmi.ipmi_operation_log(ctx, 'general_hardware', 'Set DPU card RESET LINKAGE failed')
        return msg.SetDpuResetLinkageRsp.new(cc.DataNotAvailable, MANUFACTURE_ID)
    end

    local ok, err = dpu:set_dpu_reset_linkage(req.ResetLinkage)
    if not ok then
        log:error('set dpu reset linkage failed, err: %s', err)
        ipmi.ipmi_operation_log(ctx, 'general_hardware', 'Set PCIeCard%s(%s) RESET LINKAGE failed',
            dpu.pciecard['SlotID'], dpu.pciecard['Name'])
        return msg.SetDpuResetLinkageRsp.new(cc.UnspecifiedError, MANUFACTURE_ID)
    end
    ipmi.ipmi_operation_log(ctx, 'general_hardware', 'Set PCIeCard%u(%s) RESET LINKAGE successfully',
        dpu.pciecard['SlotID'], dpu.pciecard['Name'])
    return msg.SetDpuResetLinkageRsp.new(cc.Success, MANUFACTURE_ID)
end

function dpu_service:set_dpu_ip(req, ctx)
    -- 合法性检查
    if req.ManufactureId ~= MANUFACTURE_ID then
        log:error('[SDI_IPMI]request ManufactureId(%s) is invaild', req.ManufactureId)
        return msg.SetDpuIpRsp.new(cc.InvalidFieldRequest, MANUFACTURE_ID)
    end
    local dpu = self:get_dpu_obj_by_device_no(req.DeviceNo)
    if not dpu then
        log:error('[SDI_IPMI] get DPU Object by device_no(%s) fail', req.DeviceNo)
        return msg.SetDpuIpRsp.new(cc.DataNotAvailable, MANUFACTURE_ID)
    end

    local ipv4 = string.format('%d.%d.%d.%d', string.unpack('BBBB', string.pack('I', req.Ipv4)))
    local mask = string.format('%d.%d.%d.%d', string.unpack('BBBB', string.pack('I', req.Mask)))
    local ok = dpu:set_sdi_ipv4(CONFIG_OS, ipv4, mask, req.Vlan)
    if not ok then
        log:error('[SDI_IPMI]set dpu ip failed')
        ipmi.ipmi_operation_log(ctx, 'general_hardware', 'Set PCIeCard%s(%s) card ip status failed',
            dpu.pciecard['SlotID'], dpu.pciecard['Name'])
        return msg.SetDpuIpRsp.new(cc.UnspecifiedError, MANUFACTURE_ID)
    end
    ipmi.ipmi_operation_log(ctx, 'general_hardware', 'Sent the command for setting the IPv4' ..
        'address(%s) and VLAN (%s) of the management network port of PCIeCard%s(%s) successfully',
        ipv4, req.Vlan, dpu.pciecard['SlotID'], dpu.pciecard['Name'])
    return msg.SetDpuIpRsp.new(cc.Success, MANUFACTURE_ID)
end

function dpu_service:set_dpu_sol_switch(req, ctx)
    local rsp = msg.SetDpuSolSwitchRsp.new(cc.Success, MANUFACTURE_ID)
    if req.ManufactureId ~= MANUFACTURE_ID then
        log:error('[SDI_IPMI]ManufactureId(%s) is invaild', req.ManufactureId)
        rsp.CompletionCode = cc.InvalidFieldRequest
        return rsp
    end
    if req.Uart < SOL_SWITCH_TYPE.U0_SPU.num or req.Uart > SOL_SWITCH_TYPE.U1_MPU.num then
        log:error('[SDI_IPMI]Sol Switch Type(%s) is invaild', req.Uart)
        rsp.CompletionCode = cc.ParmOutOfRange
        return rsp
    end
    local dpu = self:get_dpu_obj_by_device_no(req.DeviceNo)
    local cur_uart
    for _, v in pairs(SOL_SWITCH_TYPE) do
        if v.num == req.Uart then
            cur_uart = v
            break
        end
    end
    if not dpu then
        ipmi.ipmi_operation_log(ctx, 'general_hardware', 'Set DPU card SOL SWITCH to %s failed', cur_uart.name)
        log:error('[SDI_IPMI] get DPU Object by device_no(%s) failed', req.DeviceNo)
        rsp.CompletionCode = cc.UnspecifiedError
        return rsp
    end
    local slot = dpu:get_slot_id()
    local desc = dpu:get_description()
    local ret = dpu:set_sol_switch(req.Uart)
    if not ret then
        ipmi.ipmi_operation_log(ctx, 'general_hardware', 'Set PCIeCard%u(%s) SOL SWITCH to %s failed',
            slot, desc, cur_uart.name)
        log:error("set dpu card sol switch failed")
        rsp.CompletionCode = cc.UnspecifiedError
        return rsp
    end
    ipmi.ipmi_operation_log(ctx, 'general_hardware', 'Set PCIeCard%u(%s) SOL SWITCH to %s successfully',
        slot, desc, cur_uart.name)
    return rsp
end

function dpu_service:get_dpu_sol_switch(req, ctx)
    local rsp = msg.GetDpuSolSwitchRsp.new(cc.Success, MANUFACTURE_ID, 0, 0)
    if req.ManufactureId ~= MANUFACTURE_ID then
        log:error('[SDI_IPMI]ManufactureId(%s) is invaild', req.ManufactureId)
        rsp.CompletionCode = cc.InvalidFieldRequest
        return rsp
    end
    local dpu = self:get_dpu_obj_by_device_no(req.DeviceNo)
    if not dpu then
        log:error("[SDI IPMI] get DPU Object by device_no(%s) failed", req.DeviceNo)
        rsp.CompletionCode = cc.UnspecifiedError
        return rsp
    end
    local data = dpu:get_sol_switch()
    if not data then
        log:error("[SDI IPMI] get DPU sol switch value failed")
        rsp.CompletionCode = cc.UnspecifiedError
        return rsp
    end
    rsp.Uart = data.value
    return rsp
end

function dpu_service:set_dpu_bios_log_level(req, ctx)
    local rsp = msg.SetDpuBiosLogLevelRsp.new(cc.Success, MANUFACTURE_ID)
    if req.ManufactureId ~= MANUFACTURE_ID then
        log:error('[SDI_IPMI]request ManufactureId(%s) is invaild', req.ManufactureId)
        rsp.CompletionCode = cc.InvalidFieldRequest
        return rsp
    end
    if not BIOS_LOG_LEVEL[req.Type] then
        log:error('[SDI_IPMI]bios log type is invalid, type: %s', req.Type)
        rsp.CompletionCode = cc.InvalidFieldRequest
        return rsp
    end
    if not BIOS_LOG_LEVEL[req.Level] then
        log:error('[SDI_IPMI]bios log level is invalid, level: %s', req.Level)
        rsp.CompletionCode = cc.InvalidFieldRequest
        return rsp
    end
    local dpu = self:get_dpu_obj_by_device_no(req.DeviceNo)
    if not dpu then
        log:error("[SDI IPMI] get DPU Object by device_no(%s) failed", req.DeviceNo)
        rsp.CompletionCode = cc.InvalidFieldRequest
        return rsp
    end
    local ok = dpu:set_bios_log_level(req.Type, req.Level)
    if not ok then
        log:error('[SDI_IPMI]set dpu bios log level failed')
        ipmi.ipmi_operation_log(ctx, 'general_hardware', 'Failed to set BIOS %s log level of DPU card %s to %s',
            BIOS_LOG_TYPE[req.Type], req.DeviceNo, BIOS_LOG_LEVEL[req.Level])
        rsp.CompletionCode = cc.UnspecifiedError
        return rsp
    end
    dpu:set_bios_log_prop(req.Type, req.Level)
    ipmi.ipmi_operation_log(ctx, 'general_hardware', 'Set BIOS %s log level of DPU card %s to %s successfully',
        BIOS_LOG_TYPE[req.Type], req.DeviceNo, BIOS_LOG_LEVEL[req.Level])
    return rsp
end

function dpu_service:get_dpu_bios_log_level(req, ctx)
    local rsp = msg.GetDpuBiosLogLevelRsp.new(cc.Success, MANUFACTURE_ID, 0, 0, 0)
    if req.ManufactureId ~= MANUFACTURE_ID then
        log:error('[SDI_IPMI]request ManufactureId(%s) is invaild', req.ManufactureId)
        rsp.CompletionCode = cc.InvalidFieldRequest
        return rsp
    end
    local dpu = self:get_dpu_obj_by_device_no(req.DeviceNo)
    if not dpu then
        log:error("[SDI IPMI] get DPU Object by device_no(%s) failed", req.DeviceNo)
        rsp.CompletionCode = cc.DataNotAvailable
        return rsp
    end
    local data = dpu:get_mrc_log_level()
    if not data then
        log:error("[SDI IPMI] get DPU mrc log level failed")
        rsp.CompletionCode = cc.UnspecifiedError
        return rsp
    end
    rsp.MRC = data.level
    data = dpu:get_uefi_log_level()
    if not data then
        log:error("[SDI IPMI] get DPU uefi log level failed")
        rsp.CompletionCode = cc.UnspecifiedError
        return rsp
    end
    rsp.UEFI = data.level
    return rsp
end

function dpu_service:get_dpu_ip(req, ctx)
    -- 合法性检查
    if req.ManufactureId ~= MANUFACTURE_ID then
        log:error('[SDI_IPMI]request ManufactureId(%s) is invaild', req.ManufactureId)
        return msg.GetDpuStorageIPRsp.new(cc.InvalidFieldRequest, MANUFACTURE_ID, 0, 0, 0, 0)
    end
    local dpu = self:get_dpu_obj_by_device_no(req.DeviceNo)
    if not dpu then
        log:error('[SDI_IPMI] get DPU Object by device_no(%s) fail', req.DeviceNo)
        return msg.GetDpuStorageIPRsp.new(cc.DataNotAvailable, MANUFACTURE_ID, 0, 0, 0, 0)
    end

    local status = 1 -- SDI5.0没有该寄存器，默认为1，表示设定IP成功
    local ok, data = dpu:get_dpu_ip()
    if not ok then
        log:error('[SDI_IPMI]get dpu ip failed, error: %s', data)
        return msg.GetDpuStorageIPRsp.new(cc.UnspecifiedError, MANUFACTURE_ID, 0, 0, 0, 0)
    end
    log:notice('Get the PCIeCard%s(%s) IP successfully', dpu.pciecard['SlotID'],
        dpu.pciecard['Name'])
    return msg.GetDpuStorageIPRsp.new(cc.Success, MANUFACTURE_ID, LIST_END, status, data.ipv4,
        data.vlan)
end

function dpu_service:get_dpu_boot_option(req, ctx)
    -- 合法性检查
    if req.ManufactureId ~= MANUFACTURE_ID then
        log:error('[SDI_IPMI]request ManufactureId(%s) is invaild', req.ManufactureId)
        return msg.GetDpuBootOptionRsp.new(cc.InvalidFieldRequest, MANUFACTURE_ID, 0, 0, 0)
    end
    local dpu = self:get_dpu_obj_by_device_no(req.DeviceNo)
    if not dpu then
        log:error('[SDI_IPMI] get DPU Object by device_no(%s) fail', req.DeviceNo)
        return msg.GetDpuBootOptionRsp.new(cc.DataNotAvailable, MANUFACTURE_ID, 0, 0, 0)
    end

    local ok, data = dpu:get_dpu_boot_option()
    if not ok or data == nil then
        log:error('[SDI_IPMI]get dpu boot option failed, error: %s', data)
        return msg.GetDpuBootOptionRsp.new(cc.UnspecifiedError, MANUFACTURE_ID, 0, 0, 0)
    end
    for k, v in pairs(switch_boot_option) do
        if v ==  data.boot_option then
            data.boot_option = k
            break
        end
    end
    log:notice('Get the boot option of PCIeCard%s(%s) successfully, boot option: %s',
        dpu.pciecard['SlotID'], dpu.pciecard['Name'], data.save_option)
    return msg.GetDpuBootOptionRsp.new(cc.Success, MANUFACTURE_ID, LIST_END, data.boot_option,
        data.save_option)
end

function dpu_service:get_pxe_option(req, ctx)
    -- 合法性检查
    if req.ManufactureId ~= MANUFACTURE_ID then
        log:error('[SDI_IPMI]request ManufactureId(%s) is invaild', req.ManufactureId)
        return msg.GetDpuPxeOptionRsp.new(cc.InvalidFieldRequest, MANUFACTURE_ID, 0, 0)
    end
    local dpu = self:get_dpu_obj_by_device_no(req.DeviceNo)
    if not dpu then
        log:error('[SDI_IPMI] get DPU Object by device_no(%s) fail', req.DeviceNo)
        return msg.GetDpuPxeOptionRsp.new(cc.DataNotAvailable, MANUFACTURE_ID, 0, 0)
    end

    local pxe_option_r = dpu:get_pxe_option(dpu.dpucard['PxeOption'])
    return msg.GetDpuPxeOptionRsp.new(cc.Success, MANUFACTURE_ID, LIST_END, pxe_option_r)
end

local function pxe_option_support_sdi(pxe_option)
    return pxe_option == 0 or pxe_option == 4 or pxe_option == 5
end

function dpu_service:set_pxe_option_for_sdi(req, ctx)
    if not pxe_option_support_sdi(req.PxeOption) then
        log:error('[SDI_IPMI] pxe option(%s) is invaild', req.PxeOption)
        return msg.SetDpuPxeOptionRsp.new(cc.ParmOutOfRange, MANUFACTURE_ID)
    end
    return self:set_pxe_option(req, ctx)
end


function dpu_service:set_pxe_option(req, ctx)
    -- 合法性检查
    if req.ManufactureId ~= MANUFACTURE_ID then
        log:error('[SDI_IPMI]request ManufactureId(%s) is invaild', req.ManufactureId)
        return msg.SetDpuPxeOptionRsp.new(cc.InvalidFieldRequest, MANUFACTURE_ID)
    end

    local dpu = self:get_dpu_obj_by_device_no(req.DeviceNo)
    if not dpu then
        log:error('[SDI_IPMI] get DPU Object by device_no(%s) fail', req.DeviceNo)
        return msg.SetDpuPxeOptionRsp.new(cc.UnspecifiedError, MANUFACTURE_ID)
    end

    local pxe_option = dpu:get_pxe_option_str(req.PxeOption)
    if not pxe_option then
        log:error('[SDI_IPMI] pxe option(%s) is invaild', req.PxeOption)
        return msg.SetDpuPxeOptionRsp.new(cc.ParmOutOfRange, MANUFACTURE_ID)
    end

    local ok = dpu:set_dpu_pxe_option(req.PxeOption)
    if not ok then
        log:error('[SDI_IPMI] set DPU pxe option fail')
        ipmi.ipmi_operation_log(ctx, 'general_hardware', 'Set the %s pxe option failed', dpu:get_dpu_type())
        return msg.SetDpuPxeOptionRsp.new(cc.DataNotAvailable, MANUFACTURE_ID)
    end

    ipmi.ipmi_operation_log(ctx, 'general_hardware', 'Set the %s pxe option successfully, pxe option: %s',
        dpu:get_dpu_type(), pxe_option)

    skynet.fork(function ()
        dpu:fetch_pxe_option()
    end)
    return msg.SetDpuPxeOptionRsp.new(cc.Success, MANUFACTURE_ID)
end

function dpu_service:get_secure_boot(req, ctx)
    local dpu = self:get_dpu_obj_by_device_no(req.DeviceNo)
    if not dpu then
        log:error('[SDI_IPMI] get DPU Object by device_no(%s) fail', req.DeviceNo)
        return msg.GetDpuSecureBootRsp.new(cc.DataNotAvailable, MANUFACTURE_ID, 0, 0)
    end

    if not dpu:is_dpu_card() then
        log:error('[SDI_IPMI] only DPU Card can operate secure boot value, slot:%s', dpu.pciecard['SlotID'])
        return msg.GetDpuSecureBootRsp.new(cc.DataNotAvailable, MANUFACTURE_ID, 0, 0)
    end

    local secure_boot = dpu.dpucard_system['SecureBootOptionEnabled']
    return msg.GetDpuSecureBootRsp.new(cc.Success, MANUFACTURE_ID, LIST_END, secure_boot and 1 or 0)
end

function dpu_service:set_secure_boot(req, ctx)
    --0代表关闭，1代表开启
    if req.SecureBoot ~= 0 and req.SecureBoot ~= 1 then
        log:error('[SDI_IPMI] secure boot(%s) is invaild', req.SecureBoot)
        ipmi.ipmi_operation_log(ctx, 'general_hardware', 'Set the Secure Boot to %s failed', req.SecureBoot)
        return msg.SetDpuSecureBootRsp.new(cc.ParmOutOfRange, MANUFACTURE_ID)
    end
    local dpu = self:get_dpu_obj_by_device_no(req.DeviceNo)
    if not dpu then
        log:error('[SDI_IPMI] get Object by device_no(%s) fail', req.DeviceNo)
        ipmi.ipmi_operation_log(ctx, 'general_hardware', 'Set the Secure Boot to %s failed',
            dpu_enums.secure_boot_table[req.SecureBoot])
        return msg.SetDpuSecureBootRsp.new(cc.DataNotAvailable, MANUFACTURE_ID)
    end

    if not dpu:is_dpu_card() then
        log:error('[SDI_IPMI] only DPU Card can operate secure boot value, slot:%s, value:%s',
            dpu.pciecard['SlotID'], req.SecureBoot)
        ipmi.ipmi_operation_log(ctx, 'general_hardware', 'Set the  Boot of PCIeCard%s(%s) to %s failed',
            dpu.pciecard['SlotID'], dpu.pciecard['Name'], dpu_enums.secure_boot_table[req.SecureBoot])
        return msg.GetDpuSecureBootRsp.new(cc.DataNotAvailable, MANUFACTURE_ID, 0, 0)
    end

    local ok = dpu:set_dpu_secure_boot(req.SecureBoot)
    if not ok then
        log:error('[SDI_IPMI] set Dpu secure boot failed')
        ipmi.ipmi_operation_log(ctx, 'general_hardware', 'Set the Secure Boot of PCIeCard%s(%s) to %s failed',
            dpu.pciecard['SlotID'], dpu.pciecard['Name'], dpu_enums.secure_boot_table[req.SecureBoot])
            return msg.SetDpuSecureBootRsp.new(cc.DataNotAvailable, MANUFACTURE_ID)
    end

    ipmi.ipmi_operation_log(ctx, 'general_hardware', 'Set the Secure Boot of PCIeCard%s(%s) to %s successfully',
        dpu.pciecard['SlotID'], dpu.pciecard['Name'], dpu_enums.secure_boot_table[req.SecureBoot])

    dpu.dpucard_system['SecureBootOptionEnabled'] = req.SecureBoot == 1 and true or false
    return msg.SetDpuSecureBootRsp.new(cc.Success, MANUFACTURE_ID)
end

-- OS启动项状态(注意LUA列表从1开始)
local os_tlv<const> = {
    'os not start', 'bios start to guide os', 'bios guide os failed', 'os init finish',
    'os init out time', 'can not get hdd option'
}

function dpu_service:get_dpu_os_status(req, ctx)
    -- 合法性检查
    if req.ManufactureId ~= MANUFACTURE_ID then
        log:error('[SDI_IPMI]request ManufactureId(%s) is invaild', req.ManufactureId)
        return msg.GetDpuSystemStatusRsp.new(cc.InvalidFieldRequest, MANUFACTURE_ID, 0, 0)
    end
    local dpu = self:get_dpu_obj_by_device_no(req.DeviceNo)
    if not dpu then
        log:error('[SDI_IPMI] get DPU Object by device_no(%s) fail', req.DeviceNo)
        return msg.GetDpuSystemStatusRsp.new(cc.DataNotAvailable, MANUFACTURE_ID, 0, 0)
    end

    local ok, data = dpu:get_dpu_status(TLV_OS_STATUS)
    if not ok then
        log:error('[SDI_IPMI]get dpu os status failed, error: %s', data)
        return msg.GetDpuSystemStatusRsp.new(cc.UnspecifiedError, MANUFACTURE_ID, 0, 0)
    end
    log:notice('Get the os status of PCIeCard%s(%s) successfully, %s', dpu.pciecard['SlotID'],
        dpu.pciecard['Name'], os_tlv[data.status + 1])
    return msg.GetDpuSystemStatusRsp.new(cc.Success, MANUFACTURE_ID, LIST_END, data.status)
end

-- EP初始化状态(注意LUA列表从1开始)
local ep_tlv<const> = {'not ready', 'EP ready'}

function dpu_service:get_dpu_ep_status(req, ctx)
    -- 合法性检查
    if req.ManufactureId ~= MANUFACTURE_ID then
        log:error('[SDI_IPMI]request ManufactureId(%s) is invaild', req.ManufactureId)
        return msg.GetAllDpuEPInitStatusRsp.new(cc.InvalidFieldRequest, MANUFACTURE_ID, 0, 0)
    end
    local dpu = self:get_dpu_obj_by_device_no(req.DeviceNo)
    if not dpu then
        log:error('[SDI_IPMI] get DPU Object by device_no(%s) fail', req.DeviceNo)
        return msg.GetAllDpuEPInitStatusRsp.new(cc.DataNotAvailable, MANUFACTURE_ID, 0, 0)
    end

    local ok, data = dpu:get_dpu_status(TLV_EP_STATUS)
    if not ok then
        log:error('[SDI_IPMI]get dpu ep status failed, error: %s', data)
        return msg.GetAllDpuEPInitStatusRsp.new(cc.UnspecifiedError, MANUFACTURE_ID, 0, 0)
    end
    log:notice('Get the ep status of PCIeCard%s(%s) successfully, %s', dpu.pciecard['SlotID'],
        dpu.pciecard['Name'], ep_tlv[data.status + 1])
    return msg.GetAllDpuEPInitStatusRsp.new(cc.Success, MANUFACTURE_ID, LIST_END, data.status)
end

function dpu_service:get_device_name(card_type, position)
    if card_type == 'SDI' then
        for _, obj in ipairs(self.dpu_objects) do
            if obj.dpu_position == position then
                return 'PCIe'.. obj.pciecard['SlotID']
            end
        end
    end
    return 'NA'
end

-- SDI5.0 cpld info 保存在/var/log/pciecard目录下
function dpu_service:on_dump_cb(ctx, dest_path)
    if not next(self.dpu_objects) then
        return
    end
    local file_name = dest_path .. '/card_info'
    local file, _ = file_sec.open_s(file_name, 'a+')
    mc_utils.safe_close_file(file, function()
        if file then
            file:write('SmartNIC Info\n')
            local temp = string.format('%-4s | %-25s | %-25s \n',
                'Slot', 'M.2 Presence(M.2_1/M.2_2)', 'Network Board Presence')
            file:write(temp)
        end
        for _, obj in pairs(self.dpu_objects) do
            obj:get_sdi_cpld_info()
            obj:dump_info(file)
        end
    end)
    -- 收集SDI卡的MCU日志
    for _, obj in pairs(self.dpu_objects) do
        obj:collect_log_from_mcu()
    end
end

local function is_file_format_support(file_path)
    local file_format = file_path:sub(-4)
    local SUPPORT_CA_FORMAT = {
        ['.crt'] = true,
        ['.cer'] = true,
        ['.pem'] = true,
        ['.pfx'] = true,
        ['.p12'] = true
    }
    return SUPPORT_CA_FORMAT[file_format]
end

local function import_public_key_URI_prepare(file_path, ctx)
    if not is_file_format_support(file_path) then
        log:error("file format not support")
        error(custom_messages.PropertyValueError('Content'))
    end
    -- 检查文件是否存在
    local ret = file_sec.check_real_path_s(file_path)
    if ret ~= E_OK then
        log:error("file-%s not exist, err:%s", file_path, ret)
        error(custom_messages.PropertyValueError('Content'))
    end
    if not vos.get_file_accessible(file_path) then
        log:error("while import public key, file path not exist")
        error(custom_messages.PropertyValueError('Content'))
    end
    if not string.find(file_path, TMP_DIR_PATTERN) then
        log:error("the file path is incorrect")
        error(custom_messages.PropertyValueError('Content'))
    end
    if not vos.get_file_accessible(DEV_SHM_TMP_DIR) then
        local cmd = string.format('mkdir -p %s', DEV_SHM_TMP_DIR)
        vos.check_before_system_s('/bin/sh', '-c', cmd)
    end
    ret = file_sec.move_file_s(file_path, TMP_HTTPS_CERT_PUB_FILE)
    if ret ~= E_OK then
        log:error("move cert file failed, err:%s", ret)
        error(custom_messages.OperationFailed())
    end
    local uid = utils_core.get_uid_gid_by_name(ctx.UserName)
    local file_owner = utils_core.stat_s(TMP_HTTPS_CERT_PUB_FILE).st_uid
    if file_owner ~= uid then
        log:error("Config file owner(%d) is not match current uesr(%d)", file_owner, uid)
        utils.remove_file(file_path)
        error(custom_messages.OperationFailed())
    end
    if vos.get_file_length(TMP_HTTPS_CERT_PUB_FILE) > MAX_HTTPS_CERT_FILE_LEN then
        log:error("file size too large")
        error(custom_messages.OperationFailed())
    end
end

local function import_public_key_text_prepare(content)
    content = content:gsub("^%s*(.-)%s*$", "%1")   -- trim操作
    if #content == 0 or #content > MAX_HTTPS_CERT_FILE_LEN then
        log:error("file size can not empty or over 100KB")
        error(custom_messages.PropertyValueError('Content'))
    end
    if not vos.get_file_accessible(DEV_SHM_TMP_DIR) then
        local cmd = string.format('mkdir -p %s', DEV_SHM_TMP_DIR)
        vos.check_before_system_s('/bin/sh', '-c', cmd)
    end
    file_sec.write_file_s(TMP_HTTPS_CERT_PUB_FILE, content)
    utils_core.chown_s(TMP_HTTPS_CERT_PUB_FILE, ADMINISTRATOR_USER_UID, ADMINISTRATOR_GID)
    utils_core.chmod_s(TMP_HTTPS_CERT_PUB_FILE, utils.S_IRUSR | utils.S_IWUSR)   --给文件设置600权限
end

local import_public_key_func_map = {
    text = import_public_key_text_prepare,
    URI = import_public_key_URI_prepare
}

function dpu_service:import_public_key(obj, context, type, content)
    if type ~= 'text' and type ~= 'URI' then
        log:error("Type just support text or URI, current type: %s", type)
        log:operation(context:get_initiator(), "general_hardware", "Import public certificate failed")
        error(custom_messages.PropertyValueError('Type'))
    end
    local dpu = self:get_dpu_obj_by_path(obj.path)
    if not dpu or not dpu.import_public_key then
        log:error("get DPU Object by path(%s) failed or import public key is not supported", obj.path)
        log:operation(context:get_initiator(), "general_hardware", "Import public certificate failed")
        error(custom_messages.OperationFailed())
    end
    import_public_key_func_map[type](content, context)
    local ok, err = dpu:import_public_key(TMP_HTTPS_CERT_PUB_FILE)
    if not ok then
        utils.remove_file(TMP_HTTPS_CERT_PUB_FILE)
        log:error("PCIeCard%s(%s) import public certificate failed, err is %s",
            dpu.pciecard['SlotID'], dpu.pciecard['Name'], err)
        log:operation(context:get_initiator(), "general_hardware", "Import public certificate failed")
        error(custom_messages.OperationFailed())
    end
    utils.remove_file(TMP_HTTPS_CERT_PUB_FILE)
    log:operation(context:get_initiator(), "general_hardware", "Import public certificate success")
end

function dpu_service:set_LLDP_status(obj, context, port_id, status)
    local cur_status, new_status
    if port_id ~= COMPUTING_BOARD_PORT1 and port_id ~= COMPUTING_BOARD_PORT2 then
        log:error("set LLDP status param error, port_id: %s", port_id)
        error(custom_messages.OperationFailed())
    end
    if status ~= LLDP_DISABLE and status ~= LLDP_ENABLE then
        log:error("set LLDP status param error, status: %s", status)
        error(custom_messages.OperationFailed())
    end
    local dpu = self:get_dpu_obj_by_path(obj.path)
    if not dpu or not dpu.set_dpu_LLDP_status or not dpu.get_dpu_LLDP_status then
        log:error("get dpu object by path(%s) failed or get/set lldp status is not supported", obj.path)
        error(custom_messages.OperationFailed())
    end

    local ok, value = dpu:get_dpu_LLDP_status()
    if not ok then
        log:error("get dpu LLDP status failed, error: %s", value)
        error(custom_messages.OperationFailed())
    end
    if value.type ~= LLDP_STATUS_TYPE then
        log:error("get dpu LLDP status type error, type: %s", value.type)
        error(custom_messages.OperationFailed())
    end
    cur_status = value.status
    if port_id == COMPUTING_BOARD_PORT1 then
        new_status = status == 0 and cur_status & 2 or cur_status | 1
    end
    if port_id == COMPUTING_BOARD_PORT2 then
        new_status = status == 0 and cur_status & 1 or cur_status | 2
    end
    ok, value = dpu:set_dpu_LLDP_status(new_status)
    if not ok then
        log:error("set dpu LLDP status to %s failed, error: %s", new_status, value)
        error(custom_messages.OperationFailed())
    end
end

function dpu_service:get_LLDP_status(obj, context, port_id)
    local cur_status
    if port_id ~= COMPUTING_BOARD_PORT1 and port_id ~= COMPUTING_BOARD_PORT2 then
        log:error("get LLDP status param error, port_id: %s", port_id)
        error(custom_messages.OperationFailed())
    end
    local dpu = self:get_dpu_obj_by_path(obj.path)
    if not dpu then
        log:error("get dpu object by path(%s) failed", obj.path)
        error(custom_messages.OperationFailed())
    end
    cur_status = dpu.LLDP_status or 255
    -- port4返回bit0的值，port5返回bit1的值
    return (cur_status >> (port_id - COMPUTING_BOARD_PORT_OFFSET)) & 1
end

function dpu_service:dpu_set_linkage_powerstate_cb(power_state)
    for _, dpu_obj in pairs(self.dpu_objects) do
        dpu_obj:dpu_set_linkage_powerstate(power_state)
    end
end

local function dpu_register_observer(self)
    local dpu_firmware_type = {"DPUMcu", "DPUCpld", "DPUVrd"}
    for i = 1, #dpu_firmware_type do
        upgrade_subject.get_instance():register_upgrade_observer(self, dpu_firmware_type[i])
    end
end

function dpu_service:init(bus, db)
    self.bus = bus
    self.dpu_objects = {}
    self.ordered_dpu_position_objects = {}
    self.upgraded_objects = {}
    self.dpu_count = 0
    self.signal = {}
    self.monitor_serial_task = false
    self.db = db
    self.queue = skynet_queue()
    self.upgrade_state = {}
    self.upgrade_state['DPUCpld'] = false
    self.upgrade_state['DPUVrd'] = false
    self.upgrade_state['Mcu'] = false
    -- 接收到bmc重启信号，BMC重启这种只触发一次，且如果在超时前已经有其它模式触发收集则不再收集
    log_collector.log_dump_reset_bmc_sig:on(function()
        log:info('receive bmc reset signature')
        for _, obj in pairs(self.dpu_objects) do
            obj:collect_log_from_mcu()
        end
    end)
    -- 接收到os重启信号，只要没有任务正在收集则可以启动收集
    log_collector.log_dump_reset_os_sig:on(function()
        log:info('receive os reset signature')
        for _, obj in pairs(self.dpu_objects) do
            obj:collect_log_from_mcu()
        end
    end)

    -- 注册固件升级监测
    dpu_register_observer(self)
end

return singleton(dpu_service)
