-- 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 log = require 'mc.logging'
local ipmi_msg = require 'frudata.ipmi.ipmi_message'
local ipmi_rsp = require 'frudata.ipmi.ipmi'
local frudata_intf = require 'frudata_intf'
local custom_messages = require 'messages.custom'
local class_mgnt = require 'mc.class_mgnt'
local signal = require 'mc.signal'
local c_tasks = require 'mc.orm.tasks'
local vos = require 'utils.vos'
local ipmi = require 'ipmi'
local comp_code = ipmi.types.Cc
local common = require 'common'
local ipmi_error = require 'frudata.errors'
local fru_func = require 'function_get_fru_info'
local fru_singleton = require 'fru_singleton'
local client = require 'frudata.client'

local PICMG_IDENTIFIER <const> = 0x00
local PICMG_EXTENSION_VERSION <const> = 0x22
local SDI_DEVICE_INFO_PARAMETER <const> = 0x39
local PCIE_CARD_INFO_PARAMETER <const> = 0x08
local MCU_WRITE <const> = '\00'
local MCU_CLEAR <const> = '\01'

local fru_ipmi = {
    write_fru_data_sig = signal.new(),
    write_system_data_sig = signal.new(),
    clear_elabel_data_sig = signal.new()
}

function fru_ipmi.ipmi_result_deal(result)
    local switch = {
        [comp_code.InvalidCommand] = function()
            error(custom_messages.IPMIInvalidCommand())
        end,
        [comp_code.InvalidFieldRequest] = function()
            error(custom_messages.IPMIInvalidFieldRequest())
        end,
        [comp_code.ParmOutOfRange] = function()
            error(custom_messages.IPMIOutOfRange())
        end,
        [comp_code.DestinationUnavailable] = function()
            error(custom_messages.IPMIDestinationUnavailable())
        end,
        [comp_code.UnspecifiedError] = function()
            error(ipmi_error.unknown_cause())
        end
    }

    if switch[result] then
        switch[result]()
    end
end

local function get_fru_host_id(fru_id)
    for _, obj in pairs(class_mgnt('FruData'):get_all()) do
        if obj.FruId == fru_id then
            return obj:get_system_id(), obj.FruName
        end
    end
    log:debug('fru(%s) is not existent', fru_id)
    error(custom_messages.IPMIOutOfRange())
end

function fru_ipmi.updata_file_id(fru_id)
    fru_ipmi.write_fru_data_sig:emit(fru_id)
end

function fru_ipmi.updata_system(fru_id)
    fru_ipmi.write_system_data_sig:emit(fru_id)
end

function fru_ipmi.support_mcu(area, field)
    -- MCU只支持写MfgDate、BoardProductName、BoardSerialNumber
    if area == common.property['MfgDate'][1] and field == common.property['MfgDate'][2] then
        return false
    elseif area == common.property['BoardProductName'][1] and field == common.property['BoardProductName'][2] then
        return false
    elseif area == common.property['BoardSerialNumber'][1] and field == common.property['BoardSerialNumber'][2] then
        return false
    end
    return true
end

-- 带内ipmi命令只支持查询或者设置自己的fru信息或者公共fru信息
local function ipmi_check_host_id(ctx, fru_id)
    if ctx.HostId then -- 带外命令不校验
        local os_host_id = ctx.HostId
        local fru_host_id, fruname = get_fru_host_id(fru_id)
        if fru_host_id ~= 0 and fru_host_id ~= os_host_id and string.find(fruname, 'CpuBoard') then
            log:error('host(%s) not support to get host(%s) cpuboard info', os_host_id, fru_host_id)
            error(custom_messages.IPMIOutOfRange())
        end
    end
end

function fru_ipmi.read_elabel_data(req, ctx)
    local end_flag
    local read_value
    local result
    local rsp_data = ''

    ipmi_check_host_id(ctx, req.FruId)
    result, end_flag, read_value = frudata_intf.ipmi_read_elabel(req.FruId, req.Area, req.Field, req.Len, req.Offset)
    if result ~= comp_code.Success then
        log:info('Read fru%d E-label data failed', req.FruId)
        return fru_ipmi.ipmi_result_deal(result)
    end

    if req.Area == common.FRU_AREA.BOARD and req.Field == common.FRU_FIELD.BOARD.MFGDATE then
        for _, value in ipairs(read_value) do
            -- 保证value为0~255的无符号数
            value.Index = value.Index < 0 and value.Index & 0xFF or value.Index
            rsp_data = rsp_data .. string.char(value.Index)
        end
    elseif req.Area == common.FRU_AREA.CHASSIS and req.Field == common.FRU_FIELD.CHASSIS.TYPE
        and type(read_value) == 'number' then -- 有chassis域时才会返回number
        rsp_data = string.char(read_value)
    else
        rsp_data = read_value
    end

    return ipmi_msg.ReadElabelDataRsp.new(0, end_flag, rsp_data)
end

function fru_ipmi.clear_elabel_data(non_standard, req, ctx)
    if req.Option ~= 0xAA and req.Option ~= 0x00 then
        log:notice('fru%d not support. Option = %x', req.FruId, req.Option)
        ipmi.ipmi_operation_log(ctx, 'frudata', 'Clear fru%d E-label data failed', req.FruId)
        error(custom_messages.IPMIInvalidCommand())
    end

    if non_standard[req.FruId] then
        
        return ipmi_msg.ClearElabelDataRsp.new(common.COMP_CODE_PARA_NOT_SUPPORT, 1)
    end

    ipmi_check_host_id(ctx, req.FruId)
    local result = frudata_intf.clear_fru_info(req.FruId)
    if result ~= comp_code.Success then
        ipmi.ipmi_operation_log(ctx, 'frudata', 'Clear fru%d E-label data failed', req.FruId)
        return fru_ipmi.ipmi_result_deal(result)
    end
    local bmc_ver = fru_singleton.get_instance():get_bmc_version()
    frudata_intf.ipmi_write_elabel(req.FruId, common.FRU_AREA.BOARD, common.FRU_FIELD.BOARD.FRUFILEID, 0, #bmc_ver,
        bmc_ver)
    frudata_intf.ipmi_write_elabel(req.FruId, common.FRU_AREA.PRODUCT, common.FRU_FIELD.PRODUCT.FRUFILEID, 0, #bmc_ver,
        bmc_ver)

    fru_ipmi.clear_elabel_data_sig:emit(req.FruId, MCU_CLEAR)
    log:notice('clear E-label data successfully, bmc_ver = %s, fru_id = %d', bmc_ver, req.FruId)
    ipmi.ipmi_operation_log(ctx, 'frudata', 'Clear fru%d E-label data successfully', req.FruId)
    return ipmi_msg.ClearElabelDataRsp.new(0, 1)
end

local function check_chassistype(datas)
    if #datas ~= 0 and string.byte(datas) >= #common.chassis_type then
        return false
    end
    return true
end

function fru_ipmi.write_elabe_data(non_standard, req, ctx)
    if non_standard[req.FruId] then
        
        local rsp = {}
        rsp.CompletionCode = common.COMP_CODE_PARA_NOT_SUPPORT
        return rsp
    end

    ipmi_check_host_id(ctx, req.FruId)
    local buf = string.format('%02X-%02X-%02X-%02X-%02X-', req.FruId, req.Area, req.Field, req.Offset, req.Len)
    if not req.Datas or req.Len ~= #req.Datas then
        ipmi.ipmi_operation_log(ctx, 'frudata', 'Write fru%d E-label data(RAW:%s) failed', req.FruId, buf)
        error(custom_messages.IPMIOutOfRange())
    end
    for i = 1, req.Len - 1, 1 do
        buf = buf .. string.format('%02X-', req.Datas:sub(i):byte())
    end
    if req.Len ~= 0 then
        buf = buf .. string.format('%02X', req.Datas:sub(req.Len):byte())
    end

    local is_mcu_fru = fru_singleton.get_instance().is_mcu_fru[req.FruId]
    if (is_mcu_fru and fru_ipmi.support_mcu(req.Area, req.Field)) or
        not common.validate_byte(req.Area, req.Field, req.Datas) then
        ipmi.ipmi_operation_log(ctx, 'frudata', 'Write fru%d E-label data(RAW:%s) failed', req.FruId, buf)
        error(custom_messages.IPMIOutOfRange())
    end

    -- 写入Chassis Type值超出范围时报错
    if req.Area == 1 and req.Field == 0 and not check_chassistype(req.Datas) then
        log:error("the value(%s) of chassis type is out of range", string.byte(req.Datas))
        ipmi.ipmi_operation_log(ctx, 'frudata', 'Write fru%d E-label data(RAW:%s) failed', req.FruId, buf)
        error(custom_messages.IPMIOutOfRange())
    end

    local ok, result = pcall(frudata_intf.ipmi_write_elabel, req.FruId, req.Area, req.Field,
        req.Offset, req.Len, req.Datas)
    if not ok then
        log:error('execute frudata_intf.ipmi_write_elabel failed, ret: %s', result)
        result = comp_code.UnspecifiedError
    end
    if result ~= comp_code.Success then
        ipmi.ipmi_operation_log(ctx, 'frudata', 'Write fru%d E-label data(RAW:%s) failed', req.FruId, buf)
        return fru_ipmi.ipmi_result_deal(result)
    end
    log:notice("Write fru%d E-lable data(RAW:%s) successfully", req.FruId, buf)

    -- Area为6代表写System域
    if req.Area == common.FRU_AREA.SYSTEM then
        fru_ipmi.write_system_data_sig:emit(req.FruId)
    else
        fru_ipmi.write_fru_data_sig:emit(req.FruId,
            MCU_WRITE .. string.char(tonumber(req.Area .. req.Field, 16)) .. req.Datas)
    end
    ipmi.ipmi_operation_log(ctx, 'frudata', 'Write fru%d E-label data(RAW:%s) successfully', req.FruId, buf)
    return ipmi_msg.WriteElabelDataRsp.new(0)
end

function fru_ipmi.update_elabel_data(non_standard, req, ctx)
    log:info("ipmi update elabel data")
    local rsp = ipmi_rsp.UpdateElabelData.rsp.new()
    rsp.CompletionCode = comp_code.Success

    if non_standard[req.FruId] then
        -- 非标电子标签当前不支持清空
        return ipmi_msg.UpdateElabelDataRsp.new(common.COMP_CODE_PARA_NOT_SUPPORT, 1)
    end

    ipmi_check_host_id(ctx, req.FruId)
    if not fru_singleton.get_instance().update_status[req.FruId] then
        error(custom_messages.IPMIDestinationUnavailable())
    end

    if req.Option ~= 0xAA and req.Option ~= 0x00 then
        ipmi.ipmi_operation_log(ctx, 'frudata', 'Update fru%d E-label data failed', req.FruId)
        error(custom_messages.IPMIInvalidCommand())
    end

    if req.Option == 0xAA then
        rsp.UpdateProgress = '\x00'
    else
        -- status为0时表示eep已写入完成
        -- ipmi命令1表示更新完成，0表示正在更新
        local status = fru_singleton.get_instance().update_status[req.FruId] == 0 and 1 or 0
        rsp.UpdateProgress = string.char(status) .. '\x00'
    end

    ipmi.ipmi_operation_log(ctx, 'frudata', 'Update fru%d E-label data successfully', req.FruId)
    return rsp
end

function fru_ipmi.get_picmg_properties(req, ctx)
    -- 判断PICMG标识是否正确
    if req.PicmgIdentifier ~= PICMG_IDENTIFIER then
        return fru_ipmi.ipmi_result_deal(comp_code.InvalidFieldRequest)
    end
    local rsp = ipmi_rsp.GetPicmgProperties.rsp.new()
    rsp.CompletionCode = 0x00
    rsp.PicmgId = PICMG_IDENTIFIER
    rsp.PicmgExtensionVersion = PICMG_EXTENSION_VERSION
    rsp.FruCount = 1
    rsp.FruDeviceId = 0
    return rsp
end

-- 获取fru的槽位、地址等相关信息
function fru_ipmi.ipmi_get_addr_info(req, ctx)
    local fru_id = 0
    if #req.FruId >= 1 then
        fru_id = req.FruId:byte(1)
    end

    log:info("current fru id %s want to obtain the address information.", fru_id)
    local rsp = ipmi_rsp.GetAddressInfo.rsp.new()
    
    rsp.CompletionCode = 0x00
    rsp.Identifier = PICMG_IDENTIFIER
    rsp.HardwareAddr = 0x10
    rsp.IpmbAddr = 0x20
    rsp.Reserved = 0xff
    rsp.Id = 0x00
    rsp.SiteNumber = fru_singleton.get_instance():get_slot_id(fru_id)
    rsp.SiteType = 0x28
    return rsp
end

-- 获取电源的Fruid信息
function fru_ipmi.compute_power(req, ctx)
    return fru_ipmi.ipmi_result_deal(comp_code.InvalidCommand)
end

local function get_fruid_by_device_num(objs, read_offset, read_len, group_id, device_type)
    local fru_id_list = {}

    local grp_id, fru_id
    for path, obj in pairs(objs) do
        grp_id = group_id
        if group_id ~= 0xff then
            grp_id = obj['GroupId']
        end
        fru_id = obj['FruId']
        -- 需要排除fruid设置为0xff的无效设置
        if obj['Type'] == device_type and grp_id == group_id and fru_id ~= 0xff then
            log:info("fru[%s] path is %s",fru_id, path)
            fru_id_list[#fru_id_list + 1] = fru_id
        end
    end
    -- 命令指定的起始偏移已经大于fru id列表的个数
    if #fru_id_list == 0 or read_offset >= #fru_id_list then
        return
    end
    local end_flag = 0
    -- 如果本次命令无法完全返回所有fru id,则在返回数据中标记还有未返回的数据
    if read_offset + read_len < #fru_id_list then
        end_flag = 1
    end
    local fruid_str = ''
    local resp_index = 0
    for i = read_offset + 1, #fru_id_list do
        fruid_str = fruid_str .. string.char(fru_id_list[i])
        resp_index = resp_index + 1
        -- 已经到命令指定的数据长度
        if resp_index == read_len then
            break
        end
    end
    return end_flag, fruid_str
end

local function get_fruid_by_binded_object(objs, device_num, group_id, device_type)
    local fru_id
    for _, obj in pairs(objs) do
        if obj['Type'] == device_type and obj['GroupId'] == group_id and obj['Instance'] == device_num then
            fru_id = obj['FruId']
            break
        end
    end
    return fru_id
end

local function get_fruid_by_device_type(read_offset, read_len)
    local objs = class_mgnt('Fru'):get_all()
    local fru_id_list = {}
    local fru_index = 0
    local end_flag = 0
    local fru_num = 0

    for _ in pairs(objs) do
        fru_num = fru_num + 1
    end

    for _, obj in pairs(objs) do
        if fru_index < read_offset then
            fru_index = fru_index + 1
            goto continue
        end

        if fru_index > read_offset + read_len then
            end_flag = 1
            break
        end
        fru_id_list[#fru_id_list + 1] = obj['FruId']
        if #fru_id_list ~= common.DEVICE_INFO_LEN - 1 and #fru_id_list ~= read_len then
            fru_index = fru_index + 1
            goto continue
        end
        if fru_num > read_offset + read_len then
            end_flag = 1
        end
        break
        ::continue::
    end

    if #fru_id_list == 0 then
        return
    end

    local fruid_str = ''
    for i = 1, #fru_id_list do
        fruid_str = fruid_str .. string.char(fru_id_list[i])
    end
    return end_flag, fruid_str
end

-- 处理OEM命令,用来获取指定Fru的Fruid信息
function fru_ipmi.get_fruid_info(req, ctx)
    local device_type = req.DeviceType

    local device_num = req.DeviceNumber
    local objs = class_mgnt('Component'):get_all()
    -- 以前未使用，只能为1，现用于区分不同模块下的器件
    local group_id = req.GroupId
    local read_offset = req.ReadOffset
    local read_len = req.Length
    local rsp = ipmi_rsp.GetFruidInfo.rsp.new()
    rsp.CompletionCode = 0x00
    rsp.ManufactureId = req.ManufactureId

    -- 获取所有Fru对象的fruid
    if device_type == common.COMPONENT_TYPE_ALL then
        local end_flag, fruid_str = get_fruid_by_device_type(read_offset, read_len)
        if not end_flag or not fruid_str then
            error(custom_messages.IPMIOutOfRange())
        end
        rsp.EndofList = end_flag
        rsp.Data = fruid_str
        return rsp
    end

    -- 返回相同device type的所有fruid
    if device_num == common.COMPONENT_NUM_ALL then
        local end_flag, fruid_str = get_fruid_by_device_num(objs, read_offset, read_len, group_id, device_type)
        if not end_flag or not fruid_str then
            error(custom_messages.IPMIOutOfRange())
        end
        rsp.EndofList = end_flag
        rsp.Data = fruid_str
        return rsp
    end
    local fru_id = get_fruid_by_binded_object(objs, device_num, group_id, device_type)
    if not fru_id then
        error(custom_messages.IPMIOutOfRange())
    end
    rsp.EndofList = 0
    rsp.Data = string.char(fru_id)
    return rsp
end

local NEED_SYNC = 1
local write_fru_timer = nil
function fru_ipmi.ipmi_write_frudata(req, ctx)
    ipmi_check_host_id(ctx, req.FruDeviceId)

    local now_tick = vos.vos_tick_get()
    local src_id = ''
    if ctx.session and ctx.session.ip then
        src_id = ctx.session.ip
    else
        src_id = ctx.src_addr
    end
    local offset = (req.OffsetMs << 8) | req.OffsetLs
    local ok, result, is_sync = pcall(frudata_intf.ipmi_write_frudata, now_tick, src_id, req.FruDeviceId,
        offset, req.Data)
    if not ok then
        log:error('execute frudata_intf.ipmi_write_frudata failed, ret: %s', result)
        result = comp_code.UnspecifiedError
    end

    local data_len = #req.Data
    local buf = string.format('%02X-%02X-%02X', req.FruDeviceId, offset, data_len)
    for i = 1, data_len do
        buf = buf .. string.format("-%02x", string.byte(req.Data, i, i))
    end
    if result ~= comp_code.Success then
        ipmi.ipmi_operation_log(ctx, 'frudata', 'Write fru%d standard data(RAW:%s) failed', req.FruDeviceId, buf)
        return fru_ipmi.ipmi_result_deal(result)
    end

    if is_sync == NEED_SYNC then
        if write_fru_timer then
            write_fru_timer:cancel()
        end
        -- 2s后写入，避免频繁写入
        write_fru_timer = c_tasks.timeout_ms(2000, function()
            fru_ipmi.write_fru_data_sig:emit(req.FruDeviceId)
        end)
        ipmi.ipmi_operation_log(ctx, 'frudata', 'Write fru%d standard data successfully', req.FruDeviceId)
    end

    local rsp = ipmi_rsp.IpmiWriteFrudata.rsp.new()
    rsp.CompletionCode = comp_code.Success
    rsp.Count = data_len
    return rsp
end

function fru_ipmi.get_component_info(req, ctx)
    local fruid = req.BoardIndex
    local info_type = req.InfoType
    local fru_obj = fru_func.get_fru_obj_by_fruid(fruid)
    local component_obj = fru_func.get_component_obj_by_fruid(fruid)
    if fru_obj and fru_obj['Type'] == 4 then
        error(custom_messages.IPMIOutOfRange())
    end
    if not fru_func.get[info_type] then
        error(custom_messages.IPMIOutOfRange())
    end
    local code, rsp = fru_func.get[info_type](fru_obj, component_obj, fruid)
    if code ~= comp_code.Success then
        error(custom_messages.IPMIOutOfRange())
    end
    return ipmi_msg.GetComponentInfoRsp.new(code, rsp)
end

-- dft定制化命令, 对系统名称有所影响
function fru_ipmi.ipmi_dft_custom(req, ctx)
    local fruid = 0
    local customize_flag = req.CustomizeFlag
    local result, operation_log = frudata_intf.dft_custom(fruid, customize_flag)

    if result ~= comp_code.Success then
        log:error('Set customize failed')
        ipmi.ipmi_operation_log(ctx, 'frudata', 'Set customize failed')
        return fru_ipmi.ipmi_result_deal(result)
    end

    ipmi.ipmi_operation_log(ctx, 'frudata', operation_log)
    return ipmi_msg.IpmiDftCustomRsp.new(comp_code.Success)
end

-- 获取定制化标志位(装备调用)
function fru_ipmi.ipmi_get_dft_custom(req, ctx)
    local result, config_parameter = frudata_intf.get_dft_custom()
    if result ~= comp_code.Success then
        return fru_ipmi.ipmi_result_deal(result)
    end

    local rsp = ipmi_rsp.IpmiGetDftCustom.rsp.new()
    rsp.CompletionCode = comp_code.Success
    rsp.Reserved = 0
    rsp.ConfigParameter = config_parameter
    return rsp
end

local function is_support_fru_id(fruid)
    --  判断是否支持FRUID
    local is_support = false
    local objs = class_mgnt('Fru'):get_all()
    for _, obj in pairs(objs) do
        if obj['FruId'] == fruid then
            is_support = true
        end
    end
    if not is_support then
        log:error('fruid(%d) invalid', fruid)
        error(custom_messages.IPMIOutOfRange())
    end
    is_support = false
    -- 对象索引不到说明不在位, 返回0xD3
    objs = class_mgnt('FruData'):get_all()
    for _, obj in pairs(objs) do
        if obj['FruId'] == fruid then
            is_support = true
        end
    end
    if not is_support then
        log:error('fruid(%d) invalid', fruid)
        error(custom_messages.IPMIDestinationUnavailable())
    end
end

-- 通过IPMI命令进行FRU控制
function fru_ipmi.fru_control_capabilities(req, ctx)
    local fruid = req.FruDeviceId
    is_support_fru_id(fruid)

    local rsp = ipmi_rsp.FruControlCapabilities.rsp.new()
    rsp.CompletionCode = comp_code.Success
    rsp.PicmgIdentifier = fruid
    rsp.FruControlMask = 1 << common.FRU_CONTROL_GRACEFUL_REBOOT
    return rsp
end

-- 获取FRU库存区域信息
function fru_ipmi.get_fru_inventory(req, ctx)
    local fruid = req.FruDeviceId
    local rsp = ipmi_rsp.GetFruInventory.rsp.new()
    rsp.CompletionCode = comp_code.Success
    local frudatas = class_mgnt('FruData'):get_all()
    local frudata
    for _, obj in pairs(frudatas) do
        if obj['FruId'] == fruid then
            frudata = obj
            break
        end
    end
    if not frudata then
        error(custom_messages.IPMIOutOfRange())
    end

    rsp.LSByte = frudata['FruAreaSizeBytes'] & 0xff
    rsp.MSByte = (frudata['FruAreaSizeBytes'] & 0xff00) >> 8
    rsp.DeviceOfAccess = 0
    return rsp
end

-- 通过uid获取指定Fru的Fruid信息
function fru_ipmi.get_fruid_from_uid(req, ctx)
    local uid_len = req.UidLen
    if uid_len >= common.MAX_UID_LENGTH then
        error(custom_messages.IPMIInvalidFieldRequest())
    end
    if uid_len > #req.Uid then
        error(custom_messages.IPMIRequestLengthInvalid())
    end

    local uid = string.sub(req.Uid, 1, uid_len)
    local fru_cnt = 0
    local objs = class_mgnt('Component'):get_all()
    local rsp = ipmi_rsp.GetFruidFromUid.rsp.new()
    rsp.CompletionCode = comp_code.Success
    rsp.ManufactureId = req.ManufactureId
    rsp.Fruid = ''

    for _, obj in pairs(objs) do
        if obj['UniqueId'] == uid then
            if obj['FruId'] == 0xff then
                log:debug('fruid(%u) is invalid', obj['FruId'])
            else
                log:debug('uid(%s) fruid(%u)', uid, obj['FruId'])
                rsp.Fruid = rsp.Fruid .. string.char(obj['FruId'])
                fru_cnt = fru_cnt + 1
            end
        end
    end
    rsp.Count = fru_cnt

    return rsp
end

function fru_ipmi.read_fru_data(req, ctx)
    local fruid = req.FruId
    is_support_fru_id(fruid)

    local objs = class_mgnt('FruData'):get_all()
    local frudata
    for _, obj in pairs(objs) do
        if obj['FruId'] == fruid then
            frudata = obj
        end
    end
    if not frudata then
        error(custom_messages.IPMIDestinationUnavailable())
    end

    local offset = req.Offset
    local length = req.Length
    log:info('ctx ChanType = %d, read fru(%d) data', ctx.ChanType, fruid)

    local result, count, read_value = frudata_intf.ipmi_read_frudata(fruid, offset, length, ctx.ChanType)
    local rsp_data = ''
    for _, value in ipairs(read_value) do
        rsp_data = rsp_data .. string.char(value.Index)
    end

    if result ~= comp_code.Success then
        log:error('Read fru%d E-label data failed', fruid)
        return fru_ipmi.ipmi_result_deal(result)
    end

    local rsp = ipmi_rsp.ReadFruData.rsp.new()
    rsp.CompletionCode = comp_code.Success
    rsp.Count = count
    rsp.Data = rsp_data
    return rsp
end

local function get_fru_obj_by_property_name(property_name, property_value)
    local fru_objs = class_mgnt('Fru'):get_all()
    local fru_obj = nil
    for _, obj in pairs(fru_objs) do
        if obj[property_name] == property_value then
            fru_obj = obj
        end
    end
    return fru_obj
end

local function get_connector_obj_by_property_name(property_name, property_value)
    local objects = client:GetConnectorObjects()
    for _, obj in pairs(objects) do
        if obj[property_name] == property_value then
            return obj
        end
    end

    log:error('[%s] not find, value[%s]', property_name, property_value)
    return nil
end

local function get_position_info_by_fruid(fruid)
    local fru_obj = get_fru_obj_by_property_name("FruId", fruid)

    if not fru_obj then
        error(custom_messages.IPMIOutOfRange())
    end

    local group_id = fru_obj.ConnectorGroupId
    local connector_obj = get_connector_obj_by_property_name('GroupId', group_id)

    if not connector_obj then
        error(custom_messages.IPMIOutOfRange())
    end


    return group_id, connector_obj.SilkText
end

local function get_position_info_by_position_id(group_id)
    local connector_obj = get_connector_obj_by_property_name('GroupId', group_id)
    if not connector_obj then
        error(custom_messages.IPMIOutOfRange())
    end

    local fru_obj = get_fru_obj_by_property_name("ConnectorGroupId", group_id)

    if not fru_obj then
        error(custom_messages.IPMIOutOfRange())
    end

    return fru_obj.FruId, connector_obj.SilkText
end

local function get_position_info_by_silk(silk_text)
    local connector_obj = get_connector_obj_by_property_name('SilkText', silk_text)
    if not connector_obj then
        error(custom_messages.IPMIOutOfRange())
    end

    local group_id = connector_obj.GroupId

    local fru_obj = get_fru_obj_by_property_name("ConnectorGroupId", group_id)

    if not fru_obj then
        error(custom_messages.IPMIOutOfRange())
    end

    return fru_obj.FruId, group_id, connector_obj.SilkText
end

-- dft定制化命令, fruid和丝印号的对应关系
function fru_ipmi.get_component_position_info(req, ctx)
    local position_type = req.Type

    local rsp = ipmi_rsp.GetComponentPositionInfo.rsp.new()
    rsp.CompletionCode = comp_code.Success

    if position_type == common.TYPE_FRU_ID then
        -- fruid长度为1
        if #req.PositionInfo ~= 1 then
            error(custom_messages.IPMIRequestLengthInvalid())
        end
        local fruid = string.byte(req.PositionInfo)
        rsp.FruId = fruid
        rsp.UniqueId, rsp.SilkText = get_position_info_by_fruid(fruid)
    elseif position_type == common.TYPE_POSITION_ID then
        -- group_id长度为4字节
        if #req.PositionInfo ~= 4 then
            error(custom_messages.IPMIRequestLengthInvalid())
        end
        -- 将4字节字符串拼接为一个4字节数字
        local group_id = req.PositionInfo:byte(1) + (req.PositionInfo:byte(2) << 8) + (req.PositionInfo:byte(3) << 16) +
            (req.PositionInfo:byte(4) << 24)
        rsp.UniqueId = group_id
        rsp.FruId, rsp.SilkText = get_position_info_by_position_id(group_id)
    elseif position_type == common.TYPE_SILK_TEXT then
        local silk_text = req.PositionInfo
        rsp.FruId, rsp.UniqueId, rsp.SilkText = get_position_info_by_silk(silk_text)
    else
        error(custom_messages.IPMIOutOfRange())
    end

    return rsp
end

local function get_pysical_device_num(device_num, device_type)
    if device_num == 0xff or device_type ~= SDI_DEVICE_INFO_PARAMETER then
        return device_num
    end

    local objs = class_mgnt('Component'):get_all()
    local devices = {}
    for _, obj in pairs(objs) do
        if obj['Type'] == device_type then
            table.insert(devices, obj['Instance'])
        end
    end
    if device_num > #devices or device_num == 0 then
        error(custom_messages.IPMIOutOfRange())
    end
    table.sort(devices)
    return devices[device_num]
end

function fru_ipmi.get_device_presence(req, ctx)
    local device_type = req.DeviceType
    local device_num = get_pysical_device_num(req.DeviceNumber, device_type)

    local rsp = ipmi_rsp.GetDevicePresence.rsp.new()
    rsp.CompletionCode = comp_code.Success
    rsp.ManufactureId = req.ManufactureId
    rsp.EndOfList = 0

    local objs = class_mgnt('Component'):get_all()
    local component_obj = nil
    for _, obj in pairs(objs) do
        if obj['Type'] == device_type and obj['Instance'] == device_num and obj['GroupId'] == 1 then
            component_obj = obj
        end
    end

    if not component_obj or component_obj['Presence'] == 0 then
        rsp.Data = '\x00'
    else
        rsp.Data = '\x01'
    end
    return rsp
end

function fru_ipmi.get_device_health(req, ctx)
    local device_type = req.DeviceType
    local device_num = req.DeviceNumber

    local rsp = ipmi_rsp.GetDeviceHealth.rsp.new()
    rsp.CompletionCode = comp_code.Success
    rsp.ManufactureId = req.ManufactureId
    rsp.EndOfList = 0

    local objs = class_mgnt('Component'):get_all()
    local component_obj = nil
    for _, obj in pairs(objs) do
        if obj['Type'] == device_type and obj['Instance'] == device_num and obj['GroupId'] == 1 then
            component_obj = obj
        end
    end

    if not component_obj or component_obj['Presence'] == 0 then
        log:error('According to the protocol, the value 4 is returned if it is not in presence.')
        rsp.Data = '\x04'
    elseif component_obj['Presence'] == 2 then
        log:error('According to the protocol, the value 5 is returned if it is forbidden scan.')
        rsp.Data = '\x05'
    else
        rsp.Data = string.char(component_obj['Health'])
    end
    return rsp
end

function fru_ipmi.get_device_boardid(req, ctx)
    local device_type = req.DeviceType
    local device_num = req.DeviceNumber

    local rsp = ipmi_rsp.GetDeviceBoardID.rsp.new()
    rsp.CompletionCode = comp_code.Success
    rsp.ManufactureId = req.ManufactureId
    rsp.EndOfList = 0

    local objs = class_mgnt('Component'):get_all()
    local component_obj = nil
    for _, obj in pairs(objs) do
        if obj['Instance'] == device_num and obj['GroupId'] == 1 then
            if obj['Type'] == device_type or
                (device_type == PCIE_CARD_INFO_PARAMETER and obj['Type'] == SDI_DEVICE_INFO_PARAMETER) then
                component_obj = obj
                break
            end
        end
    end

    if not component_obj or not component_obj['BoardId'] then
        log:error('get_device_boardid failed.')
        error(ipmi_error.unknown_cause())
    else
        local boardid_high = (component_obj['BoardId'] >> 8) & 0xff
        local boardid_lower = component_obj['BoardId'] & 0xff
        rsp.Data = string.char(boardid_lower) .. string.char(boardid_high)
    end
    return rsp
end

local function find_componet_by_device_property(device_type, device_num)
    local objs = class_mgnt('Component'):get_all()
    local component_obj = nil
    for _, obj in pairs(objs) do
        if obj['Type'] == device_type and obj['Instance'] == device_num and obj['GroupId'] == 1 then
            component_obj = obj
        end
    end
    return component_obj
end

local function get_device_property(name, device_type, device_num)
    local component_obj = find_componet_by_device_property(device_type, device_num)
    if not component_obj then
        log:error('get_device_%s failed.', name)
        error(custom_messages.IPMIInvalidFieldRequest())
    else
        return component_obj[name]
    end
end

function fru_ipmi.get_device_location(req, ctx)
    local device_type = req.DeviceType
    local device_num = req.DeviceNumber
    local device_read_offset = req.ReadOffset
    local device_len = req.Length

    local rsp = ipmi_rsp.GetDeviceLocation.rsp.new()
    local f_data = get_device_property('Location', device_type, device_num)
    rsp.CompletionCode = comp_code.Success
    rsp.ManufactureId = req.ManufactureId
    rsp.EndOfList = 1
    rsp.Data = f_data

    if #rsp.Data + 1 - device_read_offset <= device_len then
        rsp.EndOfList = 0
    end

    if device_len == 0 then
        error(ipmi_error.unknown_cause())
    else
        rsp.Data = string.sub(rsp.Data, device_read_offset + 1, device_len + device_read_offset)
    end
    return rsp
end

function fru_ipmi.get_device_function(req, ctx)
    local device_type = req.DeviceType
    local device_num = req.DeviceNumber
    local device_read_offset = req.ReadOffset
    local device_len = req.Length

    local rsp = ipmi_rsp.GetDeviceFunction.rsp.new()
    local f_data = get_device_property('Function', device_type, device_num)
    rsp.CompletionCode = comp_code.Success
    rsp.ManufactureId = req.ManufactureId
    rsp.EndOfList = 1
    rsp.Data = f_data

    if #rsp.Data + 1 - device_read_offset <= device_len then
        rsp.EndOfList = 0
    end

    if device_len == 0 then
        error(ipmi_error.unknown_cause())
    else
        rsp.Data = string.sub(rsp.Data, device_read_offset + 1, device_len + device_read_offset)
    end
    return rsp
end

function fru_ipmi.get_device_name(req, ctx)
    local device_type = req.DeviceType
    local device_num = get_pysical_device_num(req.DeviceNumber, device_type)
    local device_read_offset = req.ReadOffset
    local device_len = req.Length

    local rsp = ipmi_rsp.GetDeviceName.rsp.new()
    local f_data = get_device_property('Name', device_type, device_num)
    rsp.CompletionCode = comp_code.Success
    rsp.ManufactureId = req.ManufactureId
    rsp.EndOfList = 1
    rsp.Data = f_data

    if #rsp.Data + 1 - device_read_offset <= device_len then
        rsp.EndOfList = 0
    end

    if device_len == 0 then
        error(ipmi_error.unknown_cause())
    else
        rsp.Data = string.sub(rsp.Data, device_read_offset + 1, device_len + device_read_offset)
    end
    return rsp
end

local function get_device_id_property(name, device_type, device_num)
    local component_obj = find_componet_by_device_property(device_type, device_num)

    if not component_obj or not component_obj[name] then
        log:error('get_device_%s failed.', name)
        error(custom_messages.IPMIInvalidFieldRequest())
    else
        return string.char(component_obj[name])
    end
end

function fru_ipmi.get_device_groupid(req, ctx)
    local device_type = req.DeviceType
    local device_num = req.DeviceNumber
    local device_read_offset = req.ReadOffset
    local device_len = req.Length

    if device_read_offset < 0 or device_read_offset > 511 then
        log:error('Parameter out of range.')
        error(custom_messages.IPMIOutOfRange())
    end

    if device_len < 0 or device_len > 512 then
        log:error('Parameter out of range.')
        error(custom_messages.IPMIOutOfRange())
    end

    local rsp = ipmi_rsp.GetDeviceGroupID.rsp.new()
    rsp.CompletionCode = comp_code.Success
    rsp.ManufactureId = req.ManufactureId
    rsp.EndOfList = 0
    rsp.Data = get_device_id_property('GroupId', device_type, device_num)

    return rsp
end

function fru_ipmi.get_device_fruid(req, ctx)
    local device_type = req.DeviceType
    local device_num = req.DeviceNumber

    local rsp = ipmi_rsp.GetDeviceFruid.rsp.new()
    rsp.CompletionCode = comp_code.Success
    rsp.ManufactureId = req.ManufactureId
    rsp.EndOfList = 0
    rsp.Data = get_device_id_property('FruId', device_type, device_num)

    return rsp
end

function fru_ipmi.get_device_uniqueid(req, ctx)
    local device_type = req.DeviceType
    local device_num = req.DeviceNumber
    local device_read_offset = req.ReadOffset
    local device_len = req.Length

    local rsp = ipmi_rsp.GetDeviceUniqueId.rsp.new()
    rsp.CompletionCode = comp_code.Success
    rsp.ManufactureId = req.ManufactureId
    rsp.EndOfList = 0

    local component_obj = find_componet_by_device_property(device_type, device_num)
    if not component_obj
        or not component_obj['UniqueId']
        or component_obj['UniqueId'] == 'N/A'
        or component_obj['UniqueId'] == 'NA'
        or component_obj['UniqueId'] == 'null'
        or component_obj['UniqueId'] == 'unknow'
        or device_len == 0
    then
        log:error('get_device_UniqueId failed.')
        error(custom_messages.IPMICommandCannotExecute())
    else
        rsp.Data = string.sub(component_obj['UniqueId'], device_read_offset + 1, device_len + device_read_offset)
    end
    return rsp
end

function fru_ipmi.ipmi_get_general_device_manufacture(req, ctx)
    local device_type = req.DeviceType
    local device_num = req.DeviceNumber
    local offset = req.ReadOffset
    local len = req.Length

    local rsp = ipmi_rsp.GetGeneralDeviceManufacture.rsp.new()
    rsp.CompletionCode = comp_code.Success
    rsp.ManufactureId = req.ManufactureId
    rsp.EndOfList = 1

    local component_obj = find_componet_by_device_property(device_type, device_num)
    if not component_obj or len == 0 then
        log:error('ipmi get device type(%d) num(%d) manufacture name failed.', device_type, device_num)
        error(custom_messages.IPMICommandCannotExecute())
    end

    rsp.DeviceManufactureName = string.sub(component_obj.Manufacturer, offset + 1, offset + len)
    if #component_obj.Manufacturer - offset <= len then
        rsp.EndOfList = 0
    end

    return rsp
end

return fru_ipmi
