-- 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 class = require 'mc.class'
local singleton = require 'mc.singleton'
local log = require 'mc.logging'
local signal = require 'mc.signal'
local c_tasks = require 'mc.orm.tasks'
local mc_initiator = require 'mc.initiator'
local context = require 'mc.context'
local custom_messages = require 'messages.custom'
local client = require 'network_adapter.client'
local utils_core = require 'utils.core'
local utils = require 'mc.utils'
local file_sec = require 'utils.file'
local vos = require 'utils.vos'
local task_mgmt = require 'mc.mdb.task_mgmt'
local skynet = require 'skynet'
local bs = require 'mc.bitstring'
local fructl = require 'infrastructure.fructl'
local ipmi = require 'ipmi'
local cc = ipmi.types.Cc
local msg = require 'network_adapter.ipmi.ipmi_message'
local json = require 'cjson'
local log_collector = require 'device.class.log_collector'
local c_network_adapter = require 'device.class.network_adapter'
local c_network_port = require 'device.class.network_port'
require 'device.class.optical_module'

local BLACKBOX_LEN<const> = 1024 * 60 --黑匣子日志大小固定为60k
local collect_blackbox_lock = 0
local SMARTNIC_BLACKBOX_DIR<const> = '/dev/shm/tmp/SmartNIC_blackbox/'
local SMARTNIC_BLACKBOX_DAT_NAME<const> = '/dev/shm/tmp/SmartNIC_blackbox/SmartNIC_blackbox'
local PACKAGE_PATH<const> = '/bmc/kepler/Managers/1/Package'
local MANUFACTURE_ID<const> = 0x0007db
local ncsi_core = require 'ncsi.ncsi_core'
local base_messages = require 'messages.base'
local common = require 'bma.rpc.common'
local log_netcard_info = require 'device.class.log_netcard_info'
local common_func = require 'common.common_func'

local MAX_PORT_NUM <const> = 4

local BIOS_STARTUP_POST_STAGE_FINISH = 0xFE -- BIOS启动完成
-- 历史占用率同步数据尝试次数
local HISTORY_SYNC_RECORD_MAX_COUNT <const> = 30
-- 历史占用率最短记录间隔
local HISTORY_MIN_RECORD_INTERVAL <const> = 60
-- 历史占用率有效条目数
local HISTORY_MAX_ITEM_COUNT <const> = 60
-- 历史占用率最大记录间隔，超出则重新从头记录
local HISTORY_MAX_RECORD_INTERVAL <const> = HISTORY_MIN_RECORD_INTERVAL * HISTORY_MAX_ITEM_COUNT
local RESPONSE_OK<const> = 0

local NET_DEV_FUNC_TYPE_FC<const> = 2

local DPU_OS_STATUS<const> = 5
local DPU_OS_STARTUP_COMPLETED<const> = 1
local DPU_OS_STARTUP_UNCOMPLETED<const> = 0
local BIOS_BOOT_WAIT_FLAG_ENABLE<const> = 1
local BIOS_BOOT_WAIT_FLAG_DISABLE<const> = 0

local BIOS_CHAN_NUM<const> = 13
local SYS_CHAN_NUM<const> = 15
local DPU_CARD_TYPE<const> = {
    'BF2',
    'BF3'
}

local c_device_manager = class(nil, nil, true)

function c_device_manager:on_log_file_dump(ctx, path, bma)
    log_collector.log_dump_cb(path)
    log_netcard_info.log_dump_cb(path, c_network_adapter.collection, c_network_port.collection)
    c_network_adapter.collect_dump_info(path, bma)
    c_device_manager:log_device_npu_optical_module(path)
end

function c_device_manager:log_device_npu_optical_module(path)
    local nas = c_network_adapter.collection:fetch(({Model = 'NPU'}))
    if not nas then
        return
    end
    local res_text = {}
    for _, na in pairs(nas) do
        res_text[#res_text + 1] = na:collect_optical_static_info()
    end
    local dest_path_base_info = string.format("%s/optical_module/", path)
    log_collector.dump_string_to_log(dest_path_base_info, 'optical_module_static_info', table.concat(res_text))
end

function c_device_manager:reset_port_link_status(position)
    local na = c_network_adapter.collection:find_by_position(position)
    if not na then
        return
    end

    local ports = c_network_port.collection:fetch_by_position(position)
    for _, port in pairs(ports) do
        port:set_link_status('N/A')
        port:set_link_status_numeric('N/A')
    end

    log:info('[network_adapter]: reset_port_link_status called success, ports have been reset')
end

function c_device_manager:method_get_netcard_port_num(type, system_id, slot_number)
    local obj = c_network_adapter.collection:find({
        Type = type,
        SystemID = system_id,
        SlotNumber = slot_number
    })
    if obj then
        return obj:get_port_count()
    end
end

function c_device_manager:method_get_network_adapter_num()
    return c_network_adapter.collection.count
end

function c_device_manager:method_get_network_port_num()
    return c_network_port.collection.count
end

function c_device_manager:method_get_netport_speed(type, slot, port_id)
    local obj = c_network_adapter.collection:find({
        Type = type,
        SlotNumber = slot
    })
    if not obj then
        error(custom_messages.InvalidValue(slot, 'Slot'))
    end
    local ports = c_network_port.collection:fetch_by_position(obj:get_position())
    local port = nil
    for _, v in pairs(ports) do
        if v.PortID == port_id then
            port = v
        end
    end
    if not port then
        error(custom_messages.InvalidValue(port_id, 'PortID'))
    end
    local link_status = (port.LinkStatus == 'LinkUp' or port.LinkStatus == 'NoLink' or
        port.LinkStatus == 'Connected') and 1 or 0
    return link_status, port.SpeedMbps
end

function c_device_manager:method_start_removing_device(ctx, id)
    if not id or #id == 0 then
        log:operation(ctx:get_initiator(), 'NetworkAdapter', 'Set the FlexIO to preinstalled state failed')
    end

    local card = c_network_adapter.collection:find({ NodeId = id })
    if not card then
        log:error('failed to remove the device, card not exists, id = %s', id)
        log:operation(ctx:get_initiator(), 'NetworkAdapter', 'Set the FlexIO to preinstalled state failed')
        error(custom_messages.PropertyModificationNotSupported('HotPlugAttention'))
    end

    if not card.HotPluggable then
        log:error('failed to remove the device, not support hotplug, id = %s', id)
        log:operation(ctx:get_initiator(), 'NetworkAdapter', 'Set the FlexIO to preinstalled state failed')
        error(custom_messages.PropertyModificationNotSupported('HotPlugAttention'))
    end

    -- 获取bios的启动状态
    local bios_obj = client:GetBiosBiosObject()
    if bios_obj.SystemStartupState ~= BIOS_STARTUP_POST_STAGE_FINISH then
        log:error('failed to remove the device, bios state: %s, card id: %s', bios_obj.SystemStartupState, id)
        error(custom_messages.PropertyModificationNotSupported('HotPlugAttention'))
    end

    if card.AttentionHotPlugState ~= 1 then
        log:error('current card %s AttentionHotPlugState is %s, not supported hot remove',
            id, card.AttentionHotPlugState)
        error(custom_messages.PropertyModificationNotSupported('HotPlugAttention'))
    end
    local cards = c_network_adapter.collection:fetch({HotPluggable = true})
    self:set_remove_value(id, cards)
    self:monitor_pg(id, cards)
    self.sig_ready_2_remove_reset:emit(card:get_position())
    log:info('device removed successfully., card id: %s', id)
    log:operation(ctx:get_initiator(), 'NetworkAdapter', 'Set FlexIO HotPlugAttention state successfully')
end

function c_device_manager:set_remove_value(id, cards)
    for _, card in pairs(cards) do
        if card.NodeId == id then
            card:set_prop_on_removing(true)
        else
            card:set_prop_on_removing(false)
        end
        log:notice("set %s AttentionHotPlugState %s success", card.NodeId, card.AttentionHotPlugState)
    end
end

-- 当卡下电后设置热插拔相关的属性
function c_device_manager:monitor_pg(id, cards)
    self.monitor_pg_task = c_tasks.get_instance():new_task("monitor network powergood"):loop(function()
        local cur_card = c_network_adapter.collection:find({NodeId = id})
        if cur_card and ((cur_card.SlotPowerState == "" and cur_card.CardPowerGood == 0) or  -- 0标识卡已下电
            cur_card.SlotPowerState == "Off") then
            self:set_remove_value_power(id, cards)
            self.monitor_pg_task:stop()
        end
    end):set_timeout_ms(100)
end

function c_device_manager:set_remove_value_power(id, cards)
    for _, card in pairs(cards) do
        if card.NodeId == id then
            card:set_prop_on_removed(true)
        else
            card:set_prop_on_removed(false)
        end
        log:notice('network_pg set %s AttentionHotPlugState %s success', card.NodeId, card.AttentionHotPlugState)
    end
end

-- 监听bios的启动状态
function c_device_manager:start_bios_state_listening()
    client:OnBiosPropertiesChanged(function(values, _, _)
        if values.SystemStartupState == nil then
            return
        end
        self:set_attention_by_bios_state(values.SystemStartupState:value())
    end)
end

--根据bios状态设置属性
function c_device_manager:set_attention_by_bios_state(bios_state)
    local cards = c_network_adapter.collection:fetch({HotPluggable = true})
    for _, card in pairs(cards) do
        card:set_attention_on_bios(bios_state)
    end
end

function c_device_manager:set_nic_mac_address_from_ipmi(req, ctx)
    if req.InterfaceType ~= 1 then
        log:error('Unable to set nic mac address from ipmi: Unsupported interface type: %s',
            req.InterfaceType)
        ipmi.ipmi_operation_log(ctx, 'NetworkAdapter', 'Set mac failed')
        return
    end
    log:debug('set nic mac address from ipmi: fruid: %s', req.FruId)
    local card = c_network_adapter.collection:find({ FruId = req.FruId })
    if not card then
        log:error('Unable to set nic mac address from ipmi: cannot find related NIC, fruid: %s',
            req.FruId)
        ipmi.ipmi_operation_log(ctx, 'NetworkAdapter', 'Set mac failed')
        return
    end

    local port = c_network_port.collection:find({
        NetworkAdapterId = card.NodeId,
        PortID = req.MacId - 1 -- MacId 从1开始，PortID从0开始
    })

    if not port then
        log:error(
            'Unable to set nic mac address from ipmi: cannot find related NIC port, NIC id: %s, port id: %s',
            card.NodeId, req.MacId)
        ipmi.ipmi_operation_log(ctx, 'NetworkAdapter', 'Set mac failed')
        return
    end
    local mac = string.format(string.rep('%02X', 6, ':'), req.MacAddress1, req.MacAddress2,
        req.MacAddress3, req.MacAddress4, req.MacAddress5, req.MacAddress6)
    if not mac then
        log:error('Unable to set nic mac address from ipmi: invalid mac address')
        ipmi.ipmi_operation_log(ctx, 'NetworkAdapter', 'Set mac failed')
        return
    end

    log:debug('Set Nic default mac address from ipmi')
    port.PermanentMACAddress = mac
    port.MACAddress = mac
    port.SystemID = ctx.HostId
    log:info('Successfully set nic mac address from ipmi,fruid:%s', req.FruId)
end

function c_device_manager:get_mac_address_from_ipmi(req)
    local card = c_network_adapter.collection:find({ FruId = req.FruId })
    local interface_type = 1

    local mac_id = tonumber(string.format('%u', string.unpack('B', req.Data, 1)), 10)
    if not card then
        log:error('Unable to get nic mac address from ipmi: cannot find related NIC, fruid: %s, macid: %s',
            req.FruId, mac_id)
        return mac_id, interface_type, ''
    end

    local port = c_network_port.collection:find({
        NetworkAdapterId = card.NodeId,
        PortID = mac_id - 1 -- MacId 从1开始，PortID从0开始
    })

    if not port then
        log:error(
            'Unable to get nic mac address from ipmi: cannot find related NIC port, NIC id: %s, port id: %s',
            card.NodeId, mac_id)
        return mac_id, interface_type, ''
    end

    local mac = string.gsub(port.PermanentMACAddress, '(%x%x):?',
        function(v)
            return string.char(tonumber(v, 16))
        end)

    if #req.Data == 2 then
        local next_mac_id = mac_id + 1
        if mac_id == MAX_PORT_NUM then
            next_mac_id = 255
        end
        mac = string.char(tonumber(string.format('%x', next_mac_id), 16)) .. mac
    end

    return mac_id, interface_type, mac
end

local NET_TYPE_LOM <const> = 1
local NET_TYPE_PCIE <const> = 3
local NET_TYPE_LOM2 <const> = 5
local NET_TYPE_OCP <const> = 10
local NET_TYPE_OCP2 <const> = 11

local function sort_network_adpater_by_slot_id(network_adpaters)
    table.sort(network_adpaters, function (a, b)
        return a.SlotNumber < b.SlotNumber
    end)
end

function c_device_manager:get_sort_network_adpater()
    local cards = {}
    local lom_cards = c_network_adapter.collection:fetch({Type = NET_TYPE_LOM})
    sort_network_adpater_by_slot_id(lom_cards)
    for _, v in ipairs(lom_cards) do
        table.insert(cards, v)
    end

    local lom2_cards = c_network_adapter.collection:fetch({Type = NET_TYPE_LOM2})
    sort_network_adpater_by_slot_id(lom2_cards)
    for _, v in ipairs(lom2_cards) do
        table.insert(cards, v)
    end

    local pcie_cards = c_network_adapter.collection:fetch({Type = NET_TYPE_PCIE})
    sort_network_adpater_by_slot_id(pcie_cards)
    for _, v in ipairs(pcie_cards) do
        table.insert(cards, v)
    end

    return cards
end

local function filter_unsupport_port(port)
    -- 只支持Eth网口
    if port.NetDevFuncType ~= 1 then
        return false
    end
    -- 只支持物理网口
    if port.FunctionType ~= 'Physical' then
        return false
    end
    return true
end

function c_device_manager:get_sort_business_port()
    local sort_ports = {}
    local cards = self:get_sort_network_adpater()
    local ports
    for _, card in pairs(cards) do
        ports = c_network_port.collection:fetch({NetworkAdapterId = card.ID})
        table.sort(ports, function (a, b)
            return a.PortID < b.PortID
        end)
        for _, port in pairs(ports) do
            if filter_unsupport_port(port) then
                table.insert(sort_ports, port)
            end
        end
    end
    return sort_ports
end

function c_device_manager:get_business_port_info_from_ipmi(req, ctx)
    local sort_ports = self:get_sort_business_port()
    if req.PortNo < 0 or req.PortNo > #sort_ports then
        log:error('[NET_IPMI]get port info failed, port is not valid, port :%s', req.PortNo)
        return msg.GetBusinessPortInfoRsp.new(cc.ParmOutOfRange, MANUFACTURE_ID, 0, 0, 0)
    end
    -- PortNow为0x0000表示查询网口数量
    if req.PortNo == 0 then
        return msg.GetBusinessPortInfoRsp.new(cc.Success, MANUFACTURE_ID, #sort_ports, 0, '')
    end

    local port = sort_ports[req.PortNo]
    local card = c_network_adapter.collection:find({NodeId = port.NetworkAdapterId})
    -- 输出数据为产品名称、丝印、RootBDF、网口名、BDF、Mac地址
    local mac = ''
    local port_mac = port.MACAddress
    local val
    -- 输出的mac地址为xx:xx:xx:xx:xx:Xx对应的数字
    for i = 1, #port_mac, 3 do
        val =  tonumber(string.sub(port_mac, i, i + 1), 16)
        mac = mac .. string.pack('B', val)
    end
    local list = {
        card.Name,
        card.Name,
        card.RootBDF,
        'Port' .. port.PortID,
        port.BDF,
        mac
    }
    local total_len = 0
    local data = ''
    for _,v in ipairs(list) do
        data = data .. string.pack('B', #v) .. v
        total_len = total_len + #v
    end
    return msg.GetBusinessPortInfoRsp.new(cc.Success, MANUFACTURE_ID, req.PortNo, total_len, data)
end

-- 通过BDF进行板载网卡、OCP扣卡芯片型号查询
function c_device_manager:get_network_adapter_chip_model_from_ipmi(rsp, req)
    -- 获取BDF
    local _, bus_value, device_value, function_value = string.match(req.Data, '^(%x+):(%x+):(%x+).(%x+)$')
    if not bus_value or not device_value or not function_value then
        log:error('ipmi convert network adapter bdf info failed')
        return rsp.new(cc.InvalidFieldRequest, MANUFACTURE_ID, '')
    end
    local bus_num = tonumber(bus_value, 16)
    local device_num = tonumber(device_value, 16)
    local function_num = tonumber(function_value, 16)

    -- 获取板载网卡、OCP扣卡
    local cards = {}
    c_network_adapter.collection:fold(function(_, obj)
        if obj.Type == NET_TYPE_LOM or obj.Type == NET_TYPE_LOM2 or
            obj.Type == NET_TYPE_OCP or obj.Type == NET_TYPE_OCP2 then
            table.insert(cards, obj)
        end
    end)

    -- 根据BDF进行匹配查找
    local card = nil
    for _, item in ipairs(cards) do
        if item.Bus == bus_num and item.Device == device_num and item.Function == function_num then
            card = item
            break
        end
    end

    -- 返回芯片型号信息
    if not card then
        log:info('cannot get network adapter by bdf b=%s d=%s f=%s', bus_num, device_num, function_num)
        return rsp.new(cc.DataNotAvailable, MANUFACTURE_ID, '')
    end
    log:notice('get network adapter model for b=%s d=%s f=%s. model=%s', bus_num, device_num, function_num,
        card.Model)
    return rsp.new(cc.Success, MANUFACTURE_ID, card.Model)
end

function c_device_manager:ctor(db, bus)
    self.db = db
    self.bus = bus
    self.sig_ready_2_remove_reset = signal.new()
    self.port_usage_history = self.db.PortUsageHistory
    self.port_usage_history_sync = false -- 标识是否同步过持久化数据
    self.history_sync_count = HISTORY_SYNC_RECORD_MAX_COUNT -- 同步尝试次数
    self.port_usage_history_deleting = false -- 标识是否正在删除数据
end

function c_device_manager:init()
    log_collector.init()
    log_netcard_info.init()
    self.monitor_bios_state_slot = 0
    local ok, err
    self.sig_ready_2_remove_reset:on(function(position)
        self:reset_port_link_status(position)
    end)
    self:start_bios_state_listening()

    -- 最后一次有效占用率记录的时间和ID
    self.record_info = {
        ['last_record_time'] = 0,
        ['last_record_id'] = 0
    }
    -- 同步的数据库历史数据
    self.port_usage_history_temp = {}

    -- 记录网口历史占用率
    skynet.fork_loop({count = 0}, function()
        skynet.sleep(12000) -- 启动阶段等待2分钟后更新网口历史占用率
        while true do
            skynet.sleep(500)  -- 扫描周期5s
            ok, err = pcall(function()
                self:init_port_usage_history_temp() -- 尝试同步持久化数据
                self:record_port_usage_history()
            end)
            if not ok then
                log:debug('port_usage_history fail, err:%s', err)
            end
        end
    end)
end

local task_state_all = {
    ['Starting'] = task_mgmt.state.Starting,
    ['Running'] = task_mgmt.state.Running,
    ['Stopping'] = task_mgmt.state.Stopping,
    ['Exception'] = task_mgmt.state.Exception,
    ['Completed'] = task_mgmt.state.Completed
}

local task_status_all = {
    ['OK'] = task_mgmt.status.OK,
    ['Warning'] = task_mgmt.status.Warning,
    ['Error'] = task_mgmt.status.Error,
    ['Critical'] = task_mgmt.status.Critical
}

local function update_task_status(task_id, progress, state, status, messaged_id)
    local data = {
        Progress = progress,
        State = task_state_all[state],
        Status = task_status_all[status],
        MessageId = messaged_id
    }
    local update = task_mgmt.update_task(task_id, data)
    if update ~= task_mgmt.update_code.TASK_UPDATE_SUCCESSFUL then
        log:error('update %s failed!', task_mgmt.get_task_obj(task_id).Name)
    end
end

-- Description: 获取电源状态
function c_device_manager:get_power_status()
    return fructl.get_power_status()
end

-- Description: 判断文件路径是否有效
-- Notes：1. 是否有文件名 2. 是否是有效的绝对路径 3. 路径必须在path_header目录下
local function check_filepath(file_path, path_header)
    -- 判断路径长度是否超过255
    if file_path:len() > 255 then
        log:error('File real path is too long.')
        return false
    end

    -- 判断路径是否为软链接
    local ret = file_sec.check_realpath_before_open_s(file_path)
    if ret ~= 0 then
        log:error('File path is symlink. err:%s', ret)
        return false
    end

    local file_name = file_sec.get_file_name_s(file_path)
    if file_name == '' or file_name == '.' or file_name == '..' then
        log:error('File path not include file name.')
        return false
    end

    local index = file_path:len() - file_name:len()
    local dir_path = file_path:sub(1, index - 1)

    -- 判断目录路径是否在path_header目录下
    ret = file_sec.check_real_path_s(dir_path, path_header)
    if ret ~= 0 then
        log:error('Directory path is illegal. err:%s', ret)
        return false
    end

    -- 防止恶意上传同名文件到该目录，先删除
    if vos.get_file_accessible(file_path) then
        utils.remove_file(file_path)
    end
    return true
end

local function create_blackbox_task(service, obj, ctx)
    -- 创建任务
    local name = 'SmartNICBlackBoxProcessbar'
    local timeout = 30 -- 任务总时长，单位min

    local create, err, task_id = task_mgmt.create_task(service.bus, name, obj.path, timeout)
    if create ~= task_mgmt.create_code.TASK_CREATE_SUCCESSFUL then
        log:error('creat SmartNICBlackBoxProcessbar task failed')
        collect_blackbox_lock = 0
        log:operation(ctx:get_initiator(), 'NetworkAdapter', 'Export SmartNIC_blackbox data to local failed')
        error(err)
    end
    return task_id
end

local function compress_file_to_path(path)
    local ret = file_sec.check_realpath_before_open_s(path)
    if ret ~= 0 then
        log:error('check realpath fail, err:%s', ret)
        return false
    end
    -- 移除原有黑匣子压缩文件后，重新生成压缩文件
    utils.remove_file(path)
    vos.system_s('/bin/tar', '-cf', path, '-C', SMARTNIC_BLACKBOX_DIR, './')

    -- 删除临时文件夹，压缩包权限控制在440
    utils.remove_file(SMARTNIC_BLACKBOX_DIR)
    utils_core.chmod_s(path, utils.S_IRUSR | utils.S_IRGRP)
    utils_core.chown_s(path, 502, 204)
    return true
end

local function write_blackbox_to_file(card_obj, data)
    local file_name = SMARTNIC_BLACKBOX_DAT_NAME  .. card_obj.SlotNumber .. '_' .. card_obj.Name.. '.dat'
    -- 临时文件夹需要压缩，可执行，权限设置为700
    if not utils.realpath(SMARTNIC_BLACKBOX_DIR) then
        utils.mkdir(SMARTNIC_BLACKBOX_DIR, utils.S_IRUSR | utils.S_IWUSR | utils.S_IXUSR)
    end
    local fp = file_sec.open_s(file_name, 'wb+')
    -- 写入blackbox.data
    if not fp then
        log:error('open blackbox file error')
        return false
    end
    utils.close(fp, pcall(fp.write, fp, data))

    -- dat文件权限控制为640
    utils_core.chmod_s(file_name, utils.S_IRUSR | utils.S_IWUSR | utils.S_IRGRP)
    return true
end

local function get_blackbox_by_pldm(card_obj)
    local receive_len = 0
    local rsptext = {}
    local rsp
    local frame_index = 1
    local reqdata = 0
    local req_format = bs.new([[<<
        offset:32,
        length:32,
        res:32
    >>]])

    -- 通过pldm获取60帧数据，每帧长度1024
    while frame_index <= 60 do
        reqdata = req_format:pack({
            offset = 1024 * (frame_index - 1),
            length = 1024,
            res = 0
        })
        rsp = card_obj.pldm_config_obj:BlackBox({
            data = reqdata
        }):value()
        if not rsp then
            log:error('get blackbox of SmartNIC by pldm failed')
            return false, table.concat(rsptext)
        end

        rsptext[frame_index] = rsp.data
        frame_index = frame_index + 1
        receive_len = receive_len + rsp.length
    end

    return true, table.concat(rsptext)
end

local function dump_one_card_blackbox_by_rmii(card_obj)
    -- 临时文件夹需要压缩，可执行，权限设置为700
    if not utils.realpath(SMARTNIC_BLACKBOX_DIR) then
        utils.mkdir(SMARTNIC_BLACKBOX_DIR, utils.S_IRUSR | utils.S_IWUSR | utils.S_IXUSR)
    end    
    -- dat文件权限控制为640
    local file_name = SMARTNIC_BLACKBOX_DAT_NAME  .. card_obj.SlotNumber .. '_' .. card_obj.Name.. '.dat'
    utils_core.chmod_s(file_name, utils.S_IRUSR | utils.S_IWUSR | utils.S_IRGRP)
    local ok = ncsi_core.ncsi_get_blackbox(0, 'eth0', file_name)
    if not ok then
        log:notice('cannot fetch blackbox by RMII Based Transport')
        return false
    end
    return true
end

local function dump_one_card_blackbox_by_pldm(card_obj)
    if next(card_obj.pldm_config_obj) == nil then
        log:error('pldm_config_obj is not init')
        return false
    end

    local ret, data = get_blackbox_by_pldm(card_obj)
    if not ret then
        log:error('get_blackbox_by_pldm failed')
        return false
    end

    -- 将黑匣子日志写入指定路径文件
    ret = write_blackbox_to_file(card_obj, data)
    if not ret then
        log:error('write_blackbox_to_file failed')
        return false
    end
    return true
end

local function dump_func(task_id, cards, file_path, ctx)
    local ret
    -- 创建临时文件夹前先删除
    if utils.realpath(SMARTNIC_BLACKBOX_DIR) then
        utils.remove_file(SMARTNIC_BLACKBOX_DIR)
    end

    for _, card_obj in ipairs(cards) do
        ret = dump_one_card_blackbox_by_pldm(card_obj)
        if not ret then
            log:error('dump_one_card_blackbox_by_pldm failed, try rmii')
            ret = dump_one_card_blackbox_by_rmii(card_obj)
        end

        if not ret then
            update_task_status(task_id, nil, 'Exception','Error', 'InternalError')
            return
        end
    end

    -- 校验文件软链接，删除遗留导出文件后压缩
    ret = compress_file_to_path(file_path)
    if not ret then
        log:error('compress_file_to_path failed')
        update_task_status(task_id, nil, 'Exception','Error', 'InternalError')
        return
    end
    update_task_status(task_id, 100, 'Completed', 'OK', 'Success')
    log:operation(ctx:get_initiator(), 'NetworkAdapter',
        'Export SmartNIC_blackbox data to local successfully')
end

function c_device_manager:start_blackbox_coroutine(obj, path, ctx)
    -- 找到与dpu卡同position下的所有网卡
    local positions = {}
    local dpu_cards = client:GetDPUCardObjects()
    for _, dpu_card in pairs(dpu_cards) do
        log:debug('start_blackbox_coroutine, dpu position = %s', dpu_card.extra_params.Position)
        if dpu_card.OSSupported then
            table.insert(positions, dpu_card.extra_params.Position)
        end
        table.insert(positions, dpu_card.extra_params.Position)
    end

    local cards = c_network_adapter.collection:fetch(function(obj)
        return utils.array_contains(positions, obj:get_position())
    end)

    -- 创建任务
    local task_id = create_blackbox_task(self, obj, ctx)

    -- 启动黑匣子导出协程
    skynet.fork_once(dump_func, task_id, cards, path, ctx)
    return true, task_id
end

-- 无需单独校验是否为SDI卡，上层会校验
function c_device_manager:method_dump_black_box(obj, ctx, path)
    if collect_blackbox_lock == 1 then
        log:error('Blackbox logs are being collected.')
        log:operation(ctx:get_initiator(), 'NetworkAdapter', 'Export SmartNIC_blackbox data to local failed')
        error(custom_messages.OperationFailed())
    end
    local ret, task_id

    if self:get_power_status() ~= 'ON' then
        log:operation(ctx:get_initiator(), 'NetworkAdapter',
            'Downloading SmartNIC blackbox not allowed in power-off state')
        error(custom_messages.OperationFailed())
    end

    -- 路径校验
    ret = check_filepath(path, '/tmp/')

    if not ret then
        log:operation(ctx:get_initiator(), 'NetworkAdapter', 'Export SmartNIC_blackbox  data to local failed')
        error(custom_messages.InvalidPath('******', 'Content'))
    end

    if utils_core.is_dir(path) then
        log:operation(ctx:get_initiator(), 'NetworkAdapter', 'Export SmartNIC_blackbox  data to local failed')
        error(custom_messages.NotAllowedToOverwriteDirectory())
    end

    collect_blackbox_lock = 1
    ret, task_id = self:start_blackbox_coroutine(obj, path, ctx)
    collect_blackbox_lock = 0
    if not ret then
        log:operation(ctx:get_initiator(), 'NetworkAdapter', 'Export SmartNIC_blackbox data to local failed')
        error(custom_messages.OperationFailed())
    end
    return task_id
end

-- 网卡历史数据排序，排序前 table的key需要从1开始。
local function sort_card_history(card_v)
    local sorted_t = {}
    for _, obj_v in pairs(card_v) do
        table.insert(sorted_t, obj_v)
    end
    table.sort(sorted_t, function (a, b)
        if a and b then
            return a.Time < b.Time -- 按时间戳从小到大排序
        end
        return false
    end)
    return sorted_t
end

function c_device_manager:update_cur_util_percents(cur_util_percents, card_id)
    local ports = c_network_port.collection:fetch({NetworkAdapterId = card_id})
    local usage_port_id
    for _, port in pairs(ports) do
        usage_port_id = tonumber(port.PortID) + 1 -- 从1开始
        if port.LinkStatus ~= 'LinkUp' and port.LinkStatus ~= 'Connected' then
            cur_util_percents[usage_port_id] = json.null
        end
    end
end

-- 获取网卡历史占用率，从内存中组数据。
function c_device_manager:method_get_bandwidth_history()
    local history = {}
    -- 如果bma没连接，就返回空
    if not common.is_bma_running() then
        return json.encode(history)
    end
    local ports
    -- 根据当前网卡来生成返回数据。未加载的网卡的数据需要删除。
    local cards = c_network_adapter.collection:fetch({})
    local card_id, card_obj, card_v, util_obj, time, cur_util_percents_id
    for _, card in pairs(cards) do
        card_id = card.ID
        ports = c_network_port.collection:fetch({NetworkAdapterId = card_id})
        for _, port in pairs(ports) do
            if port.FunctionType == 'Virtual' or port.NetDevFuncType == NET_DEV_FUNC_TYPE_FC then
                goto continue
            end
        end
        card_obj = {
            ['BWUWaveTitle'] = card_id,
            ['CurrentUtilisePercents'] = {},
            ['UtilisePercents'] = {},
            ['PortCount'] = 0
        }
        card_v = self.port_usage_history_temp[card_id]
        if card_v and next(card_v) then
            card_v = sort_card_history(card_v)
            for id, port_v in pairs(card_v) do
                util_obj = {}
                time = os.date('%Y/%m/%d %H:%M:%S', port_v['Time'])
                util_obj['UtilisePercent'] = port_v['UtilisePercent']
                util_obj['Time'] = time
                table.insert(card_obj['UtilisePercents'], util_obj)
                cur_util_percents_id = id
            end
            -- 时间戳最大的点作为 CurrentUtilisePercents
            card_obj['CurrentUtilisePercents'] = utils.table_copy(card_v[cur_util_percents_id]['UtilisePercent'])
            card_obj['PortCount'] = #card_v[cur_util_percents_id]['UtilisePercent']
            self:update_cur_util_percents(card_obj['CurrentUtilisePercents'], card_id)
        end
        table.insert(history, card_obj)
        ::continue::
    end
    self:delete_expired_history()
    return json.encode(history)
end

-- 检查并删除未加载的网卡历史数据。
function c_device_manager:delete_expired_history()
    local cards = c_network_adapter.collection:fetch({})
    local exist
    for card_id, card_v in pairs(self.port_usage_history_temp) do
        exist = false
        for _, card in pairs(cards) do
            if card_id == card.ID then
                exist = true
            end
        end
        if not exist then
            self:clear_invalid_port_usage_history(card_id)
        end
    end
end

-- 获取网卡历史占用率，初始化 port_usage_history_temp 
function c_device_manager:init_port_usage_history_temp()
    if self.port_usage_history_sync then
        return -- 已经同步过
    end

    local history = {}
    local local_db_data = self.db:select(self.port_usage_history):all()
    if not local_db_data then
        log:debug("init_port_usage_history_temp fail. retry left: %s", self.history_sync_count)
        if self.history_sync_count > 0 then
            self.history_sync_count = self.history_sync_count - 1
            return -- 没有查到数据，重试
        end
    end

    -- 更新最后一次有效占用率记录
    self:init_last_record_info()
    local id, card_id, usage_port_id, now, v
    for _, record in pairs(local_db_data) do
        if record.Id > 0 and record.Id <= HISTORY_MAX_ITEM_COUNT then
            id = record.Id
            card_id = record.NetworkAdapterId
            usage_port_id = tonumber(record.PortID) + 1 -- 从1开始
            now = tonumber(record.Time)
            v = record.Percent
            if not history[card_id] then
                history[card_id] = {}
            end
            if not history[card_id][id] then
                history[card_id][id] = {}
            end
            if not history[card_id][id]['UtilisePercent'] then
                history[card_id][id]['UtilisePercent'] = {}
            end
            if not history[card_id][id]['Time'] then
                history[card_id][id]['Time'] = now
            end
            history[card_id][id]['UtilisePercent'][usage_port_id] = v
        end
    end
    self.port_usage_history_temp = history
    self.port_usage_history_sync = true -- 查询并同步成功
end

local function get_op_initiator()
    local initiator = {}
    local ctx = context.get_context()
    if ctx and common_func:has_initiator(ctx) then
        initiator = ctx:get_initiator()
    else
        initiator = mc_initiator.new('N/A', 'N/A', 'localhost')
    end
    return initiator
end

-- 清除网口历史占用率。同时清除数据库和内存数据。
function c_device_manager:clear_port_usage_history(card_id)
    local cards = c_network_adapter.collection:fetch({})
    -- 判断参数是否有效
    local valid = false
    for _, card in pairs(cards) do
        if card.ID == card_id then
            valid = true
        end
    end
    if not valid then
        error(custom_messages.ActionParameterValueInvalid(card_id, 'BWUWaveTitle'))
    end

    self.port_usage_history_deleting = true
    local ok, err = pcall(function()
        self.db:delete(self.port_usage_history):where({NetworkAdapterId = card_id}):exec()
        if self.port_usage_history_temp[card_id] then
            self.port_usage_history_temp[card_id] = nil
        end
    end)
    self.port_usage_history_deleting = false
    if ok then
        log:operation(get_op_initiator(), 'NetworkAdapter',
            "Clear %s network bandwidth usage history record successfully", card_id)
    else
        log:operation(get_op_initiator(), 'NetworkAdapter',
            "Clear %s network bandwidth usage history record failed,err:%s", card_id, err)
    end
end

-- 自动删除无效数据
function c_device_manager:clear_invalid_port_usage_history(card_id)
    if card_id then
        self.port_usage_history_deleting = true
        pcall(function()
            self.db:delete(self.port_usage_history):where({NetworkAdapterId = card_id}):exec()
            if self.port_usage_history_temp[card_id] then
                self.port_usage_history_temp[card_id] = nil
            end
        end)
        self.port_usage_history_deleting = false
    else
        self.port_usage_history_deleting = true
        pcall(function()
            self.db:delete(self.port_usage_history):where({NetworkAdapterId = card_id}):exec()
            self.port_usage_history_temp = {}
        end)
        self.port_usage_history_deleting = false
    end
end

-- 获取到有效的即时占用率后增加到数据库
function c_device_manager:record_port_usage_history()
    if self.port_usage_history == nil or not self.port_usage_history_sync or self.port_usage_history_deleting then
        log:error("[record_port_usage_history] db not ready")
        return
    else
        local now = os.time()
        -- BMC启动时会有时间回到1970年的短暂阶段，不使用这段时间数据
        if now < 180 then
            return
        end
        -- 时间发生回滚或者间隔太长则重新开始记录
        if now < self.record_info['last_record_time'] or
           now - self.record_info['last_record_time'] >= HISTORY_MAX_RECORD_INTERVAL then
            log:error("invalid timestamp")
            self:clear_invalid_port_usage_history()
            self:add_one_record(self.record_info, now, self.port_usage_history)
        -- 时间未达到最小间隔则不记录
        elseif now - self.record_info['last_record_time'] < HISTORY_MIN_RECORD_INTERVAL then
            return
        else
            self:add_one_record(self.record_info, now, self.port_usage_history)
        end
    end
end

-- 获取最后一次有效占用率记录的时间和ID
function c_device_manager:init_last_record_info()
    local max_timestamp = 0
    local id = 0
    local record = self.db:select(self.port_usage_history):order_by(self.port_usage_history.Time, true):first()
    if record ~= nil then
        log:notice("init_last_record_info %s", os.date('%Y/%m/%d %H:%M:%S', record.Time))
        max_timestamp = record.Time
        id = record.Id
    end
    self.record_info = {
        ['last_record_time'] = max_timestamp,
        ['last_record_id'] = id
    }
end

-- 执行添加记录，最多记录HISTORY_MAX_ITEM_COUNT条数据
function c_device_manager:add_one_record(last_record_info, now, tbl)
    local id = last_record_info['last_record_id'] % HISTORY_MAX_ITEM_COUNT + 1 -- 从1开始，后面用table.sort
    last_record_info['last_record_time'] = now
    last_record_info['last_record_id'] = id

    -- 获取并记录每个网口的数据，同时记录在数据库和内存中。
    local cards = c_network_adapter.collection:fetch({})
    local ports, usage_port_id
    for _, card in pairs(cards) do
        -- 槽位是0或card.ID为空，就是无效数据。
        if card.ID == '' or card.SlotNumber == 0 then
            goto next
        end

        if not self.port_usage_history_temp[card.ID] then
            self.port_usage_history_temp[card.ID] = {}
        end

        ports = c_network_port.collection:fetch({NetworkAdapterId = card.ID})
        for _, port in pairs(ports) do
            usage_port_id = tonumber(port.PortID) + 1 -- 从1开始
            if not self.port_usage_history_temp[card.ID][id] then
                self.port_usage_history_temp[card.ID][id] = {}
            end
            self.port_usage_history_temp[card.ID][id]['Time'] = now -- 更新时间戳
            if not self.port_usage_history_temp[card.ID][id]['UtilisePercent'] then
                self.port_usage_history_temp[card.ID][id]['UtilisePercent'] = {}
            end
            self.port_usage_history_temp[card.ID][id]['UtilisePercent'][usage_port_id] = port.BandwidthUsagePercent

        end
        ::next::
    end
end

function c_device_manager:set_usage_threshold(usage_threshold)
    if usage_threshold > 100 then
        log:error("UsageThreshold:%u is invalid!", usage_threshold)
        log:operation(get_op_initiator(), 'NetworkAdapter', "Set network bandwidth utilization threshold failed")
        error(base_messages.InternalError())
    end

    local cards = c_network_adapter.collection:fetch({})
    for _, card in pairs(cards) do
        if not card.BandwidthThresholdPercent then
            log:error("set_usage_threshold fail")
            log:operation(get_op_initiator(), 'NetworkAdapter', "Set network bandwidth utilization threshold failed")
            error(base_messages.InternalError())
        end
        card.BandwidthThresholdPercent = usage_threshold
    end
    log:operation(get_op_initiator(), 'NetworkAdapter', 
        "Set network bandwidth utilization threshold to (%u) successfully", usage_threshold)
    return RESPONSE_OK
end

function c_device_manager:filter_msg_by_sys_channel(channel_num)
    if channel_num == BIOS_CHAN_NUM or channel_num == SYS_CHAN_NUM then
        return true
    end
    return false
end

function c_device_manager:get_network_adapter_by_model()
    for _, dpu_card_type in pairs(DPU_CARD_TYPE) do
        local bf_card = c_network_adapter.collection:find({Model = dpu_card_type})
        if bf_card then
            return bf_card
        end
    end
end

function c_device_manager:get_customer()
    local package_objs = client:GetPackagePackageObject({['ManagerId'] = 1})
    if not package_objs then
        log:error('Get package objects failed')
        return ''
    end
    local customer = package_objs.Customer
    if customer == nil then
        log:error('Get customer failed')
        return ''
    end
    return customer
end

function c_device_manager:get_dpu_os_startup_status(req, ctx)
    --限制带内通道
    local res = self:filter_msg_by_sys_channel(ctx.chan_num)
    if not res then
        log:error('get dpu os startup status: channel num(%s) is invalid', ctx.chan_num)
        return msg.GetDpuOSStartupStatusRsp.new(cc.CommandDisable, MANUFACTURE_ID, 0, DPU_OS_STARTUP_UNCOMPLETED)
    end
    -- 合法性检查
    if req.ManufactureId ~= MANUFACTURE_ID then
        log:error('get dpu os startup status: ManufactureId(%s) is invaild', req.ManufactureId)
        return msg.GetDpuOSStartupStatusRsp.new(cc.InvalidFieldRequest, MANUFACTURE_ID, 0, DPU_OS_STARTUP_UNCOMPLETED)
    end

    local network_adapter = self:get_network_adapter_by_model()
    if not network_adapter then
        log:error('get dpu os startup status: get network_adapter failed')
        return msg.GetDpuOSStartupStatusRsp.new(cc.UnspecifiedError, MANUFACTURE_ID, 0, DPU_OS_STARTUP_UNCOMPLETED)
    end
    return msg.GetDpuOSStartupStatusRsp.new(cc.Success, MANUFACTURE_ID, 0,
        (network_adapter:get_dpu_os_status() == DPU_OS_STATUS) and
        DPU_OS_STARTUP_COMPLETED or DPU_OS_STARTUP_UNCOMPLETED)
end

function c_device_manager:get_dpu_force_power_on_status(req, ctx)
    -- 合法性检查
    if req.ManufactureId ~= MANUFACTURE_ID then
        log:error('get dpu force power on status:  ManufactureId(%s) is invaild', req.ManufactureId)
        return msg.GetDpuForcePowerOnStateRsp.new(cc.InvalidFieldRequest, MANUFACTURE_ID, 0, 0)
    end

    local network_adapter = self:get_network_adapter_by_model()
    if not network_adapter then
        log:error('get dpu force power on status: get network_adapter failed')
        return msg.GetDpuForcePowerOnStateRsp.new(cc.UnspecifiedError, MANUFACTURE_ID, 0, 0)
    end
    local bios_boot_wait_flag = network_adapter.bios_boot_wait_flag
    if bios_boot_wait_flag == BIOS_BOOT_WAIT_FLAG_ENABLE and self:filter_msg_by_sys_channel(ctx.chan_num) then
        network_adapter:set_bios_boot_wait_flag(BIOS_BOOT_WAIT_FLAG_DISABLE)
    end

    return msg.GetDpuForcePowerOnStateRsp.new(cc.Success, MANUFACTURE_ID, 0, bios_boot_wait_flag)
end

function c_device_manager:set_dpu_force_power_on_status(req, ctx)
    if self:get_customer() ~= 'CTCC' then
        log:error('set dpu force power on status: only support CTCC customization')
        return msg.SetDpuForcePowerOnStateRsp.new(cc.CommandDisable)
    end

    local network_adapter = self:get_network_adapter_by_model()
    if not network_adapter then
        log:error('set dpu force power on status: get network_adapter failed')
        return msg.SetDpuForcePowerOnStateRsp.new(cc.UnspecifiedError)
    end
    network_adapter:set_bios_boot_wait_flag(BIOS_BOOT_WAIT_FLAG_ENABLE)

    return msg.SetDpuForcePowerOnStateRsp.new(cc.Success)
end

function c_device_manager:collect_netcard_info()
    log:notice("debug: collect netcard info")
    local ok = log_netcard_info.collect_netcard_info(c_network_adapter.collection, c_network_port.collection)
    if not ok then
        error(custom_messages.OperationFailed())
    end
end

function c_device_manager:get_bf3_by_model()
    local bf_card = c_network_adapter.collection:find({Model = 'BF3'})
    if bf_card then
        return bf_card
    end
end

function c_device_manager:get_dpu_host_access(req, ctx)
    local network_adapter = self:get_bf3_by_model()
    if not network_adapter then
        log:error('get dpu host access: get network_adapter failed')
        return msg.GetDpuHostAccessRsp.new(cc.CommandDisabled,0)
    end

    local compcode, host_access = network_adapter:get_dpu_host_access()
    return msg.GetDpuHostAccessRsp.new(compcode, host_access)
end

function c_device_manager:set_dpu_host_access(req, ctx)
    local network_adapter = self:get_bf3_by_model()
    if not network_adapter then
        log:error('set dpu host access: get network_adapter failed')
        return msg.SetDpuHostAccessRsp.new(cc.CommandDisabled)
    end

    local compcode = network_adapter:set_dpu_host_access(req.Status, ctx)

    if compcode ~= cc.Success then
        ipmi.ipmi_operation_log(ctx, 'NetworkAdapter', 'set dpu host access failed')
    end

    return msg.SetDpuHostAccessRsp.new(compcode)
end

function c_device_manager:get_dpu_extended_host_privilege(req, ctx)
    local network_adapter = self:get_bf3_by_model()
    if not network_adapter then
        log:error('get dpu extended host privilege: get network adapter failed')
        return msg.GetDpuExtendedHostPrivilegeRsp.new(cc.CommandDisabled, 0)
    end

    local compcode, privilege = network_adapter:get_dpu_extended_host_privilege()
    return msg.GetDpuExtendedHostPrivilegeRsp.new(compcode, privilege)
end

function c_device_manager:set_dpu_extended_host_privilege(req, ctx)
    local network_adapter = self:get_bf3_by_model()
    if not network_adapter then
        log:error('set dpu extended host privilege: get network_adapter failed')
        return msg.SetDpuExtendedHostPrivilegeRsp.new(cc.CommandDisabled)
    end

    local compcode = network_adapter:set_dpu_extended_host_privilege(req.Status, ctx)
    
    if compcode ~= cc.Success then
        ipmi.ipmi_operation_log(ctx, 'NetworkAdapter', 'set dpu extended host privilege failed')
    end

    return msg.SetDpuExtendedHostPrivilegeRsp.new(compcode)
end

function c_device_manager:get_dpu_mode(req, ctx)
    local network_adapter = self:get_bf3_by_model()
    if not network_adapter then
        log:error('get dpu mode: get network_adapter failed')
        return msg.GetDpuModeRsp.new(cc.CommandDisabled, 0)
    end

    local compcode, mode = network_adapter:get_dpu_mode()
    return msg.GetDpuModeRsp.new(compcode, mode)
end

function c_device_manager:set_dpu_mode(req, ctx)
    local network_adapter = self:get_bf3_by_model()
    if not network_adapter then
        log:error('set dpu mode: get network_adapter failed')
        return msg.SetDpuModeRsp.new(cc.CommandDisabled)
    end

    local compcode = network_adapter:set_dpu_mode(req.Mode, ctx)
    
    if compcode ~= cc.Success then
        ipmi.ipmi_operation_log(ctx, 'NetworkAdapter', 'set dpu mode failed')
    end

    return msg.SetDpuModeRsp.new(compcode)
end

return singleton(c_device_manager)
