-- 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 ipmi = require 'ipmi'
local comp_code = ipmi.types.Cc
local msg = require 'storage.ipmi.ipmi_message'
local class = require 'mc.class'
local log = require 'mc.logging'
local bs = require 'mc.bitstring'
local mdb = require 'mc.mdb'
local skynet = require 'skynet'
local context = require 'mc.context'
local sqlite3 = require 'lsqlite3'
local c_drives_object = require "drives.drives_object"
local controller_collection = require 'controller.controller_collection'
local drive_collection = require 'drive.drive_collection'
local array_collection = require 'array.array_collection'
local volume_collection = require 'volume.volume_collection'
local rpc_service_volume = require 'rpc_services.rpc_service_volume'
local rpc_service_drive = require 'rpc_services.rpc_service_drive'
local rpc_service_controller = require 'rpc_services.rpc_service_controller'
local bus_monitor_service = require 'bus_monitor_service'
local singleton = require 'mc.singleton'
local cm = require 'common_def'
local INVALID_BYTE = cm.INVALID_U8
local CT_HOST <const> = 3
local SMBIOS_WRITE_FINISH <const> = 3
local GET_SILK_BY_LIGHT_UP <const> = 0
local GET_SILK_BY_LIGHT_UP_NEW <const> = 4
local IPMI_MSG_MAX_PAYLOAD_LEN <const> = 248
local COMP_CODE_AND_MANU_LEN <const> = 4    -- completion_code + manfacture_id
local RESERVED_LENGTH <const> = 2   -- 前两个字节预留为总亮灯硬盘个数、实际返回丝印个数
local CONTROLLER_ID_DEFAULT <const> = 255   -- ControllerId默认值

local c_ipmi_service = class(nil, nil, true)

local cache_data = ''

local per_db
local smbios_status

function c_ipmi_service:init()
    bus_monitor_service.get_instance().on_smbios_status_changed:on(function(status)
        smbios_status = status
    end)
end

function c_ipmi_service:ctor(db)
    per_db = db
end

-- 整合分多帧发送的IPMI请求数据包
local function integrate_request_data(req)
    local cache_len = #cache_data

    --  先校验数据的长度
    if #req.Data ~= req.WritingLength then
        log:error('[Storage]DataLength = %s, Writinglength = %s not equal, they should be equal!',
            #req.Data, req.WritingLength)
        error(comp_code.InvalidFieldRequest, 0)
    end

    --  第一帧的offset如果不等于0，则返回失败
    if cache_len == 0 and req.WritingOffset ~= 0 then
        log:error('[Storage]Fisrt frame offset not zero')
        error(comp_code.InvalidFieldRequest, 0)
    end

    -- 如果offset为0但是缓存区不为空，则清空缓存
    if cache_len ~= 0 and req.WritingOffset == 0 then
        cache_data = ''
        cache_len = 0
    end

    -- 如果后一帧的offset不等于缓存的长度，返回Invalid
    if cache_len ~= 0 and req.WritingOffset ~= cache_len then
        log:error('[Storage]Frame offset not equal received length')
        error(comp_code.InvalidFieldRequest, 0)
    end

    -- 第一帧和非第一帧数据都可以这样写
    cache_data = cache_data .. string.sub(req.Data, 1, req.WritingLength)

    if req.Frame == 0 then
        local retval = cache_data
        cache_data = '' -- 清空缓存数据
        return retval
    else
        -- 表示命令继续
        log:error('[Storage]Received success. continue...')
        error(comp_code.Success, 0)
    end
end

-- 根据ID查找controller，未找到返回对应错误 CompletionCode
local function get_controller(req)
    if req.ManufacturerId ~= cm.MANUFACTURE_ID then
        log:error('[Storage] ManufacturerId wrong. ManufacturerId is %d', req.ManufacturerId)
        error(comp_code.InvalidFieldRequest, 0)
    end

    local ctrl = controller_collection.get_instance():get_by_controller_id(req.ControllerId)
    if not ctrl then
        error(comp_code.SensorInvalid, 0)
    end

    return ctrl
end

local function judge_manufacture_id_and_get_array(req)
    if req.ManufacturerId ~= cm.MANUFACTURE_ID then
        log:error('[Storage] ManufacturerId wrong. ManufacturerId is %d', req.ManufacturerId)
        error(comp_code.InvalidFieldRequest, 0)
    end

    local array_id = (req.ArrayIDHigh << 4) + req.ArrayIDLow
    local array = array_collection.get_instance():get_ctrl_array_by_id(req.ControllerId, array_id)
    if not array then
        error(comp_code.SensorInvalid, 0)
    end

    return array
end

-- 根据 ReadingOffset和 ReadingLength 获取实际返回字符串
local function get_actual_str_rsp(req, str)
    -- 字符串相比 list 第一个字节会多返回一个字节的字符串长度
    str = string.pack('B', #str & INVALID_BYTE) .. str
    if req.ReadingOffset > #str then
        return comp_code.InvalidFieldRequest, 0, ""
    end

    local read_end = req.ReadingOffset + req.ReadingLength
    local reserve = #str > read_end and 1 or 0 -- 是否读完，读不完为1
    if req.ReadingLength == 0 and req.ReadingOffset == #str then
        return comp_code.Success, reserve, ""
    end

    local min = #str > read_end and read_end or #str

    -- IPMI实际返回的长度区间为 [readingoffset+1, min]
    local retval = string.sub(str, req.ReadingOffset + 1, min)
    return comp_code.Success, reserve, retval
end

local function get_actual_list_resp(req, list, resp_len)
    if req.ReadingOffset > #list then
        return comp_code.InvalidFieldRequest, 0, ""
    end

    local read_end = req.ReadingOffset + req.ReadingLength
    if req.ReadingLength == 0 and #list == req.ReadingOffset then
        local reserve = req.ReadingOffset < #list and 1 or 0 -- 偏移量小于目标长度表示没读完
        return comp_code.Success, reserve, ""
    end

    local text = {}
    local resp_format = resp_len == 1 and 'B' or 'H' -- 只支持一个字节或者两个字节长度的返回值
    local resp_turn = resp_len == 1 and INVALID_BYTE or cm.INVALID_U16
    for i = 1, #list, 1 do
        text[i] = string.pack(resp_format, list[i] & resp_turn)
    end

    local total_length = #table.concat(text)

    local reserve = total_length > read_end and 1 or 0 -- 读不完为1
    local min = total_length > read_end and read_end or total_length

    return comp_code.Success, reserve, string.sub(table.concat(text), req.ReadingOffset + 1, min)
end

local function get_physical_drive_state_str(fw_state)
    local state_table = {
        [0] = "UNCONFIGURED GOOD",
        [1] = "UNCONFIGURED BAD",
        [2] = "HOT SPARE",
        [3] = "OFFLINE",
        [4] = "FAILED",
        [5] = "REBUILD",
        [6] = "ONLINE",
        [7] = "COPYBACK",
        [8] = "JBOD",
        [9] = "UNCONFIGURED(Shielded)",
        [10] = "HOT SPARE(Shielded)",
        [11] = "CONFIGURED(Shielded)"
    }

    if fw_state <= #state_table then
        return state_table[fw_state]
    else
        return "Unknown"
    end
end

function c_ipmi_service.get_hdd_pwr_status (req, ctx)
    if req.ManufacturerId ~= cm.MANUFACTURE_ID then
        log:error('[Storage] ManufacturerId wrong. ManufacturerId is %d', req.ManufacturerId)
        return msg.GetHddPwrStatusRsp.new(comp_code.InvalidFieldRequest, cm.MANUFACTURE_ID, '')
    end

    local drive = drive_collection.get_instance():get_drive_by_drive_id(req.HddID)
    if not drive then
        log:error('[Storage] Drive(Id:%s) not exist', req.HddID)
        return msg.GetHddPwrStatusRsp.new(comp_code.SensorInvalid, cm.MANUFACTURE_ID, '')
    end

    -- IPMI 上下电状态与drive.PowerState相反
    local power_state = drive.PowerState == 0 and 1 or 0
    return msg.GetHddPwrStatusRsp.new(comp_code.Success, cm.MANUFACTURE_ID, string.pack('B', power_state))
end

function c_ipmi_service.get_controller_list(req, ctx)
    if req.ManufacturerId ~= cm.MANUFACTURE_ID then
        log:error('[Storage] ManufacturerId wrong. ManufacturerId is %d', req.ManufacturerId)
        return msg.GetControllerListRsp.new(comp_code.InvalidFieldRequest, cm.MANUFACTURE_ID, 0, '')
    end

    local ctrl_id_list = controller_collection.get_instance():get_all_controllers_id()
    local cc, reserve, data = get_actual_list_resp(req, ctrl_id_list, 1)
    return msg.GetControllerListRsp.new(cc, cm.MANUFACTURE_ID, reserve, data)
end

function c_ipmi_service.get_controller_fw_version(req, ctx)
    local ok, rsp = pcall(get_controller, req)
    if not ok then
        return msg.GetControllerFwVersionRsp.new(rsp, cm.MANUFACTURE_ID, 0, '')
    end

    local fw_version = rsp.FirmwareVersion

    local completion_code, reserve, retval = get_actual_str_rsp(req, fw_version)
    return msg.GetControllerFwVersionRsp.new(completion_code, cm.MANUFACTURE_ID, reserve, retval)
end

function c_ipmi_service.get_controller_nvdata_version(req, ctx)
    local ok, rsp = pcall(get_controller, req)
    if not ok then
        return msg.GetControllerNvdataVersionRsp.new(rsp, cm.MANUFACTURE_ID, 0, '')
    end

    local nv_data_version = rsp.NVDataVersion
    local completion_code, reserve, retval = get_actual_str_rsp(req, nv_data_version)
    return msg.GetControllerNvdataVersionRsp.new(completion_code, cm.MANUFACTURE_ID, reserve, retval)
end

local ctrl_info_format = bs.new([[<<
        OOBSupport:8,
        MemorySizeMiB:16,
        DeviceInterface:8,
        CachePinnedState:8,
        MaintainPDFailHistrory:8,
        CopyBackState:8,
        SmarterCopyBackState:8,
        JBODState:8,
        MinStripSizeBytes:8,
        MaxStripSizeBytes:8,
        Reserve1:32,
        Reserve2:8,
        NameLength:8
    >>]])

function c_ipmi_service.get_controller_info(req, ctx)
    local ok, rsp = pcall(get_controller, req)
    if not ok then
        return msg.GetControllerInfoRsp.new(rsp, cm.MANUFACTURE_ID, 0, '')
    end

    local ctrl_info = {
        ['OOBSupport'] = rsp.OOBSupport,
        ['MemorySizeMiB'] = rsp.MemorySizeMiB,
        ['DeviceInterface'] = cm.INTERFACE_TYPE[rsp.DeviceInterface] and
            cm.INTERFACE_TYPE[rsp.DeviceInterface] or cm.INVALID_U8,
        ['CachePinnedState'] = rsp.CachePinnedState,
        ['MaintainPDFailHistrory'] = rsp.MaintainPDFailHistrory,
        ['CopyBackState'] = rsp.CopyBackState,
        ['SmarterCopyBackState'] = rsp.SmarterCopyBackState,
        ['JBODState'] = rsp.JBODState,
        ['MinStripSizeBytes'] = cm.STRIP_SIZE_TABLE[rsp.MinStripSizeBytes] and
            cm.STRIP_SIZE_TABLE[rsp.MinStripSizeBytes] or cm.INVALID_U8,
        ['MaxStripSizeBytes'] = cm.STRIP_SIZE_TABLE[rsp.MaxStripSizeBytes] and
            cm.STRIP_SIZE_TABLE[rsp.MaxStripSizeBytes] or cm.INVALID_U8,
        ['Reserve1'] = 0,
        ['Reserve2'] = 0,
        ['NameLength'] = #rsp.ControllerName
    }

    local str = ctrl_info_format:pack(ctrl_info)
    str = string.sub(str, 1, 4) .. rsp.SASAddr .. string.sub(str, 5, #str) .. rsp.ControllerName

    local completion_code, reserve, retval = get_actual_str_rsp(req, str)
    return msg.GetControllerInfoRsp.new(completion_code, cm.MANUFACTURE_ID, reserve, string.sub(retval, 2, #retval))
end

local function get_logical_drive_list_base(req, ex)
    local msg_rsp = ex == 1 and "GetLogicalDriveListRsp" or "GetLogicalDriveListExRsp"
    local ok, rsp = pcall(get_controller, req)
    if not ok then
        return msg[msg_rsp].new(rsp, cm.MANUFACTURE_ID, 0, '')
    end

    local volume_list = rsp:get_ctrl_volume_list()
    if not volume_list then
        return msg[msg_rsp].new(rsp, cm.MANUFACTURE_ID, 0, '')
    end

    if #volume_list == 0 then
        return msg[msg_rsp].new(comp_code.Success, cm.MANUFACTURE_ID, 0, '')
    end

    local volume_id_list = {}
    for _, v in pairs(volume_list) do
        volume_id_list[#volume_id_list + 1] = v.volume_id
    end

    local cc, reserve, retval = get_actual_list_resp(req, volume_id_list, ex)
    return msg[msg_rsp].new(cc, cm.MANUFACTURE_ID, reserve, retval)
end

function c_ipmi_service.get_logical_drive_list(req, ctx)
    return get_logical_drive_list_base(req, 1)
end

function c_ipmi_service.get_logical_drive_list_ex(req, ctx)
    return get_logical_drive_list_base(req, 2)
end

local function get_cachecade_associated_lds_base(req, ex)
    local msg_rsp = ex == 1 and "GetCachecadeAssociatedLdsRsp" or "GetCachecadeAssociatedLdsExRsp"

    if req.ManufacturerId ~= cm.MANUFACTURE_ID then
        log:error('[Storage] ManufacturerId wrong. ManufacturerId is %d', req.ManufacturerId)
        return msg[msg_rsp].new(comp_code.InvalidFieldRequest, cm.MANUFACTURE_ID, 0, '')
    end

    local volume = volume_collection.get_instance():get_ctrl_volume_by_id(req.ControllerId, req.CachecadeID)
    if not volume then
        log:error('[Storage]Volume %s not exist in Controller %s', req.CachecadeID, req.ControllerId)
        return msg[msg_rsp].new(comp_code.SensorInvalid, cm.MANUFACTURE_ID, 0, '')
    end

    local associated_cachecade_volume = {}
    local len = ex == 1 and INVALID_BYTE or cm.INVALID_U16
    for _, v in pairs(volume.AssociatedCacheCadeVolume) do
        table.insert(associated_cachecade_volume, string.pack('B', v & len))
    end

    return msg[msg_rsp].new(comp_code.Success, cm.MANUFACTURE_ID, 0, table.concat(associated_cachecade_volume))
end

function c_ipmi_service.get_cachecade_associated_lds(req, ctx)
    return get_cachecade_associated_lds_base(req, 1)
end

function c_ipmi_service.get_cachecade_associated_lds_ex(req, ctx)
    return get_cachecade_associated_lds_base(req, 2)
end

function c_ipmi_service.get_logical_drive_pds(req, ctx)
    if req.ManufacturerId ~= cm.MANUFACTURE_ID then
        log:error('[Storage] ManufacturerId wrong. ManufacturerId is %d', req.ManufacturerId)
        return msg.GetLogicalDrivePdsRsp.new(comp_code.InvalidFieldRequest, cm.MANUFACTURE_ID, 0, '')
    end

    local volume = volume_collection.get_instance():get_ctrl_volume_by_id(req.ControllerId, req.VolumeId)
    if not volume then
        log:error('[Storage]Volume %s not exist in Controller %s', req.VolumeId, req.ControllerId)
        return msg.GetLogicalDrivePdsRsp.new(comp_code.SensorInvalid, cm.MANUFACTURE_ID, 0, '')
    end

    if not volume.RefDriveList then
        return msg.GetLogicalDrivePdsRsp.new(comp_code.Success, cm.MANUFACTURE_ID, 0, '')
    end

    local drive_id_list = {}
    for _, v in pairs(volume.RefDriveList) do
        local drive = drive_collection.get_instance():get_drive(v)
        if not drive then
            log:error('[Storage]Drive %s not exist', v)
            table.insert(drive_id_list, string.pack('B', INVALID_BYTE))
        else
            table.insert(drive_id_list, string.pack('B', drive.Id))
        end
    end

    return msg.GetLogicalDrivePdsRsp.new(comp_code.Success, cm.MANUFACTURE_ID, 0, table.concat(drive_id_list))
end

local function fill_drive_id_by_pd(pd_list, pd_id_list)
    for _, pd in pairs(pd_list) do
        if pd.ref_drive then
            pd_id_list[#pd_id_list + 1] = pd.ref_drive.Id
        end
    end
end

function c_ipmi_service.get_controller_pds(req, ctx)
    if req.ManufacturerId ~= cm.MANUFACTURE_ID then
        log:error('[Storage] ManufacturerId wrong. ManufacturerId is %d', req.ManufacturerId)
        return msg.GetControllerPdsRsp.new(comp_code.InvalidFieldRequest, cm.MANUFACTURE_ID, 0, '')
    end

    local pd_id_list = {}

    -- 0xFF - 表示获取所有控制器的所有物理盘ID
    if req.ControllerId == INVALID_BYTE then
        local controller_set = controller_collection.get_instance():get_all_controllers()
        local pd_list = {}
        for _, v in pairs(controller_set) do
            pd_list = v.pd_list
            fill_drive_id_by_pd(pd_list, pd_id_list)
        end
    else
        local ctrl = controller_collection.get_instance():get_by_controller_id(req.ControllerId)
        if not ctrl then
            return msg.GetControllerPdsRsp.new(comp_code.SensorInvalid, cm.MANUFACTURE_ID, 0, '')
        end

        local pd_list = ctrl.pd_list
        fill_drive_id_by_pd(pd_list, pd_id_list)
    end

    local cc, reserve, retval = get_actual_list_resp(req, pd_id_list, 1)
    return msg.GetControllerPdsRsp.new(cc, cm.MANUFACTURE_ID, reserve, retval)
end

local function confirm_manufacture_id_and_get_drive(req)
    if req.ManufacturerId ~= cm.MANUFACTURE_ID then
        log:error('[Storage] ManufacturerId wrong. ManufacturerId is %d', req.ManufacturerId)
        error(comp_code.InvalidFieldRequest, 0)
    end

    local drive = drive_collection.get_instance():get_drive_by_drive_id(req.PhysicalDriveID)
    if not drive then
        log:error('[Storage] Drive(Id:%s) not exist', req.PhysicalDriveID)
        error(comp_code.SensorInvalid, 0)
    end

    return drive
end

function c_ipmi_service.get_physical_drive_location(req, ctx)
    local ok, rsp = pcall(confirm_manufacture_id_and_get_drive, req)
    if not ok then
        return msg.GetPhysicalDriveLocationRsp.new(rsp, cm.MANUFACTURE_ID, 0, '')
    end

    local drive = drive_collection.get_instance():get_drive_by_drive_id(req.PhysicalDriveID)
    if not drive then
        return msg.GetPhysicalDriveLocationRsp.new(comp_code.SensorInvalid, cm.MANUFACTURE_ID, 0, '')
    end

    local retval = cm.PHYSCIAL_DRIVE_LOCATION .. ' ' .. drive.Name
    retval = string.pack('B', #retval) .. retval
    return msg.GetPhysicalDriveLocationRsp.new(comp_code.Success, cm.MANUFACTURE_ID, 0, retval)
end

local function get_drive_info(rsp)
    local drive_info = {
        ['Health'] = rsp.Health,
        ['FirmwareStatus'] = rsp.FirmwareStatus,
        ['PowerState'] = rsp.PowerState,
        ['MediaType'] = rsp.MediaType,
        ['Protocol'] = rsp.Protocol,
        ['CapableSpeedGbs'] = rsp.CapableSpeedGbs,
        ['NegotiatedSpeedGbs'] = rsp.NegotiatedSpeedGbs,
        ['TemperatureCelsius'] = rsp.TemperatureCelsius,
        ['CapacityMiB'] = rsp.CapacityMiB,
        ['Reserve1'] = 0,
        ['HotspareType'] = rsp.HotspareType,
        ['RebuildState'] = rsp.RebuildState,
        ['RebuildProgress'] = rsp.RebuildProgress,
        ['PatrolState'] = rsp.PatrolState,
        ['Reserve2'] = 0,
        ['PredictedMediaLifeLeftPercent'] = rsp.PredictedMediaLifeLeftPercent,
        ['MediaErrorCount'] = rsp.MediaErrorCount,
        ['PredictedFailCount'] = rsp.PredictedFailCount,
        ['OtherErrorCount'] = rsp.OtherErrorCount,
        ['LocationIndicatorState'] = rsp.LocationIndicatorState,
        ['PowerOnHours'] = rsp.PowerOnHours > cm.INVALID_U16 and cm.INVALID_U16 or rsp.PowerOnHours,
    }

    return drive_info
end

function c_ipmi_service.get_physical_drive_info(req, ctx)
    local ok, rsp = pcall(confirm_manufacture_id_and_get_drive, req)
    if not ok then
        return msg.GetPhysicalDriveInfoRsp.new(rsp, cm.MANUFACTURE_ID, 0, '')
    end

    local drive_info = get_drive_info(rsp)
    local drive_info_format = bs.new([[<<
        Health:8,
        FirmwareStatus:8,
        PowerState:8,
        MediaType:8,
        Protocol:8,
        CapableSpeedGbs:8,
        NegotiatedSpeedGbs:8,
        TemperatureCelsius:8,
        CapacityMiB:32,
        Reserve1:32,
        HotspareType:8,
        RebuildState:8,
        RebuildProgress:8,
        PatrolState:8,
        Reserve2:24,
        PredictedMediaLifeLeftPercent:8,
        MediaErrorCount:32,
        PredictedFailCount:32,
        OtherErrorCount:32,
        LocationIndicatorState:8,
        PowerOnHours:16
    >>]])
    -- string类型的数据不能被bs转换
    local t = {}
    local manu_info = string.pack('B', #rsp.Manufacturer) .. rsp.Manufacturer
    local seri_info = string.pack('B', #rsp.SerialNumber) .. rsp.SerialNumber
    local model_info = string.pack('B', #rsp.Model) .. rsp.Model
    local revision_info = string.pack('B', #rsp.Revision) .. rsp.Revision
    local pack_info = drive_info_format:pack(drive_info)
    table.insert(t, manu_info)
    table.insert(t, seri_info)
    table.insert(t, model_info)
    table.insert(t, revision_info)
    table.insert(t, pack_info)

    local cc, reserve, retval = get_actual_str_rsp(req, table.concat(t))
    return msg.GetPhysicalDriveInfoRsp.new(cc, cm.MANUFACTURE_ID, reserve, string.sub(retval, 2, #retval))
end

function c_ipmi_service.get_physical_drive_led_status(req, ctx)
    local ok, rsp = pcall(confirm_manufacture_id_and_get_drive, req)
    if not ok then
        return msg.GetPhysicalDriveLedStatusRsp.new(rsp, cm.MANUFACTURE_ID, 0, '')
    end

    local led_info_format = bs.new([[<<
        Presence:8,
        Activation:8,
        Location:8,
        Fault:8
    >>]])

    local led_info = {
        ['Presence'] = rsp.Presence,
        ['Activation'] = rsp.ActivationLed,
        ['Location'] = rsp.LocateLed,
        ['Fault'] = rsp.FaultLed
    }

    local cc, reserve, retval = get_actual_str_rsp(req, led_info_format:pack(led_info))
    return msg.GetPhysicalDriveLedStatusRsp.new(cc, cm.MANUFACTURE_ID, reserve, string.sub(retval, 2, #retval))
end

function c_ipmi_service.get_array_list(req, ctx)
    local ok, rsp = pcall(get_controller, req)
    if not ok then
        return msg.GetArrayListRsp.new(rsp, cm.MANUFACTURE_ID, 0, '')
    end

    local array_list = rsp.array_list
    local array_id_list = {}
    for k, v in pairs(array_list) do
        if v then
            array_id_list[#array_id_list + 1] = k
        end
    end

    -- array id 长度为2字节
    local cc, reserve, retval = get_actual_list_resp(req, array_id_list, 2)
    return msg.GetArrayListRsp.new(cc, cm.MANUFACTURE_ID, reserve, retval)
end

function c_ipmi_service.get_array_info(req, ctx)
    local ok, rsp = pcall(judge_manufacture_id_and_get_array, req)
    if not ok then
        return msg.GetArrayInfoRsp.new(rsp, cm.MANUFACTURE_ID, 0, '')
    end

    local array_info = {
        ['UsedSpaceMiB'] = rsp.UsedSpaceMiB,
        ['Reserve1'] = 0,
        ['TotalFreeSpaceMiB'] = rsp.TotalFreeSpaceMiB,
        ['Reserve2'] = 0,
        ['RefVolumesLength'] = #rsp.RefVolumes,
        ['RefDrivesLength'] = #rsp.RefDrives
    }

    local array_info_format = bs.new([[<<
        UsedSpaceMiB:32,
        Reserve1:32,
        TotalFreeSpaceMiB:32,
        Reserve2:32,
        RefVolumesLength:8,
        RefDrivesLength:8
    >>]])

    local cc, reserve, retval = get_actual_str_rsp(req, array_info_format:pack(array_info))
    return msg.GetArrayInfoRsp.new(cc, cm.MANUFACTURE_ID, reserve, string.sub(retval, 2, #retval))
end

local function get_array_lds_base(req, ex)
    local ok, rsp = pcall(judge_manufacture_id_and_get_array, req)
    if not ok then
        return rsp, 0, ''
    end

    local ld_id_list = rsp.RefVolumes
    local cc, reserve, retval = get_actual_list_resp(req, ld_id_list, ex)
    return cc, reserve, retval
end

function c_ipmi_service.get_array_lds(req, ctx)
    local cc, reserve, data = get_array_lds_base(req, 1)
    return msg.GetArrayLdsRsp.new(cc, cm.MANUFACTURE_ID, reserve, data)
end

function c_ipmi_service.get_array_lds_ex(req, ctx)
    local cc, reserve, data = get_array_lds_base(req, 2)
    return msg.GetArrayLdsExRsp.new(cc, cm.MANUFACTURE_ID, reserve, data)
end

function c_ipmi_service.get_array_pds(req, ctx)
    local ok, rsp = pcall(judge_manufacture_id_and_get_array, req)
    if not ok then
        return msg.GetArrayPdsRsp.new(rsp, cm.MANUFACTURE_ID, 0, '')
    end

    local drive_name_list = rsp.RefDrives
    local drive_id_list = {}
    local drive = nil
    for _, v in pairs(drive_name_list) do
         drive = drive_collection.get_instance():get_drive(v)
         if drive then
            drive_id_list[#drive_id_list + 1] = drive.Id
         end
    end

    local cc, reserve, retval = get_actual_list_resp(req, drive_id_list, 1)
    return msg.GetArrayPdsRsp.new(cc, cm.MANUFACTURE_ID, reserve, retval)
end

function c_ipmi_service.get_controller_capability(req, ctx)
    if req.ManufacturerId ~= cm.MANUFACTURE_ID then
        log:error('[Storage] ManufacturerId wrong. ManufacturerId is %d', req.ManufacturerId)
        return msg.GetControllerCapabilityRsp.new(comp_code.InvalidFieldRequest, cm.MANUFACTURE_ID, 0, '')
    end

    local controller_capability_info = bs.new([[<<
        ControllerID:8,
        BusID:8,
        DeviceID:8,
        FunctionID:8,
        OOBSupport:1,
        SmarterCopyBackState:1,
        ReserveBit:6,
        Reserve: 24
    >>]])

    local controller_set = controller_collection.get_instance():get_all_controllers()
    local rettable = {}
    local tmp_table = {}
    for _, v in pairs(controller_set) do
        tmp_table['ControllerID'] = v.Id
        tmp_table['BusID'] = v.DevBus
        tmp_table['DeviceID'] = v.DevDevice
        tmp_table['FunctionID'] = v.DevFunction
        tmp_table['OOBSupport'] = v.OOBSupport
        tmp_table['SmarterCopyBackState'] = v.SmarterCopyBackState
        tmp_table['ReserveBit'] = 0
        tmp_table['Reserve'] = 0
        table.insert(rettable, controller_capability_info:pack(tmp_table))
    end

    return msg.GetControllerCapabilityRsp.new(comp_code.Success, cm.MANUFACTURE_ID, 0, table.concat(rettable))
end

-- 逻辑盘设置相关
local check_ctrl_and_volume = function (req)
    if req.ManufacturerId ~= cm.MANUFACTURE_ID then
        log:error('[Storage] ManufacturerId wrong. ManufacturerId is %d', req.ManufacturerId)
        return comp_code.InvalidFieldRequest, 0, ''
    end

    local controller = controller_collection.get_instance():get_by_controller_id(req.ControllerId)
    if not controller then
        log:error("[Storage] IPMI get controller failed. ControllerId is %d", req.ControllerId)
        return comp_code.SensorInvalid, 0, ''
    end

    local volume_list = controller:get_ctrl_volume_list()
    if not volume_list then
        log:error("[Storage] IPMI get volume(%s) failed. Volume list is empty", req.VolumeId)
        return comp_code.SensorInvalid, 0, ''
    end
    local volume = nil
    for _, v in pairs(volume_list) do
        if v.volume_id == req.VolumeId then
            volume = v
        end
    end

    if not volume then
        log:error("[Storage] IPMI get volume failed. VolumeId is %d", req.VolumeId)
        return comp_code.SensorInvalid, 0, ''
    end

    return nil
end

-- 在新的阵列上创建逻辑盘
function c_ipmi_service.create_logical_drive_on_new_array(req, ctx)
    if req.ManufacturerId ~= cm.MANUFACTURE_ID then
        log:error('[Storage] ManufacturerId wrong. ManufacturerId is %d', req.ManufacturerId)
        return msg.CreateLogicalDriveOnNewArrayRsp.new(comp_code.InvalidFieldRequest, cm.MANUFACTURE_ID, 0, '')
    end

    local finish, integrated_data = pcall(integrate_request_data, req)
    if not finish then
        return msg.CreateLogicalDriveOnNewArrayRsp.new(integrated_data, cm.MANUFACTURE_ID, 0, '')
    else
        req.Data = integrated_data
    end

    local data = req.Data
    local name_length = string.unpack('B', string.sub(data, 1, 1))
    if name_length > cm.SL_LD_NAME_LENGTH or #req.Data < name_length + 17 then
        log:error('[Storage]VolumeName length is %d, param length is %d', name_length, #req.Data)
        return msg.CreateLogicalDriveOnNewArrayRsp.new(comp_code.ReqDataLenInvalid, cm.MANUFACTURE_ID, 0, '')
    end

    local name_len = name_length
    local name = string.sub(data, 2, name_len + 1)
    local raid_type = string.unpack('B', string.sub(data, name_len + 2, name_len + 2))
    local capacity = string.unpack('I4', string.sub(data, name_len + 3, name_len + 6))
    local capacity_unit = string.unpack('B', string.sub(data, name_len + 7, name_len + 7))
    local strip_size = string.unpack('B', string.sub(data, name_len + 8, name_len + 8))
    local read_policy = string.unpack('B', string.sub(data, name_len + 9, name_len + 9))
    local write_policy = string.unpack('B', string.sub(data, name_len + 10, name_len + 10))
    local io_policy = string.unpack('B', string.sub(data, name_len + 11, name_len + 11))
    local access_policy = string.unpack('B', string.sub(data, name_len + 12, name_len + 12))
    local drive_cache_policy = string.unpack('B', string.sub(data, name_len + 13, name_len + 13))
    local initialization_mode = string.unpack('B', string.sub(data, name_len + 14, name_len + 14))
    local span_number = string.unpack('B', string.sub(data, name_len + 15, name_len + 15))
    local span_drive_number = string.unpack('B', string.sub(data, name_len + 16, name_len + 16))
    local span_drive_id_list = string.sub(data, name_len + 17, #data)

    local drive_list_len = #span_drive_id_list
    if span_number * span_drive_number ~= drive_list_len then
        log:notice('[Storage]span number and drive each span not match drive_id_list len')
        return msg.CreateLogicalDriveOnNewArrayRsp.new(comp_code.InvalidFieldRequest, cm.MANUFACTURE_ID, 0, '')
    end

    local drive_id_list = {}
    for i = 1, drive_list_len, 1 do
        drive_id_list[#drive_id_list + 1] = string.unpack('B', string.sub(span_drive_id_list, i, i))
    end

    local ok, retval = pcall(rpc_service_controller.ctrl_operate, rpc_service_controller, 'CreateVolumeInNewArray',
        req.ControllerId, ctx, span_drive_id_list, raid_type, span_number, name, capacity, capacity_unit, strip_size,
        read_policy, write_policy, io_policy, access_policy, drive_cache_policy, initialization_mode, INVALID_BYTE)

    retval = ok and comp_code.Success or cm.IPMI_CODE[retval.err_code] or cm.IPMI_CODE_DEFAULT
    local create_result = retval == 0 and 'successfully' or 'failed'
    ipmi.ipmi_operation_log(ctx, 'storage', "Create RAID controller %s logic drive RAID %s %s", req.ControllerId,
                            raid_type, create_result)
    return msg.CreateLogicalDriveOnNewArrayRsp.new(retval, cm.MANUFACTURE_ID, 0, '')
end

-- 当前博通卡环境不支持测试
function c_ipmi_service.create_cachecade_logical_drive(req, ctx)
    if req.ManufacturerId ~= cm.MANUFACTURE_ID then
        log:error('[Storage] ManufacturerId wrong. ManufacturerId is %d', req.ManufacturerId)
        return msg.CreateCachecadeLogicalDriveRsp.new(comp_code.InvalidFieldRequest, cm.MANUFACTURE_ID, 0, '')
    end

    local finish, integrated_data = pcall(integrate_request_data, req)
    if not finish then
        return msg.CreateCachecadeLogicalDriveRsp.new(integrated_data, cm.MANUFACTURE_ID, 0, '')
    else
        req.Data = integrated_data
    end

    local data = req.Data
    local name_length = string.unpack('B', string.sub(data, 1, 1))
    if name_length > cm.SL_LD_NAME_LENGTH or #req.Data < name_length + 5 then
        log:error('[Storage]VolumeName length is %d, param length is %d', name_length, #req.Data)
        return msg.CreateCachecadeLogicalDriveRsp.new(comp_code.ReqDataLenInvalid, cm.MANUFACTURE_ID, 0, '')
    end

    local name = string.sub(data, 2, name_length + 1)
    local raid_type = string.unpack('B', string.sub(data, name_length + 2, name_length + 2))
    local write_policy = string.unpack('B', string.sub(data, name_length + 3, name_length + 3))
    local ssd_drive_num = string.unpack('B', string.sub(data, name_length + 4, name_length + 4))
    local ssd_id_list = string.sub(data, name_length + 5, name_length + 4 + ssd_drive_num)
    local drive_id_list = {}
    for i = 1, #ssd_id_list, 1 do
        local id = string.sub(ssd_id_list, i, i)
        table.insert(drive_id_list, string.unpack('B', id))
    end

    local ok, retval = pcall(rpc_service_controller.ctrl_operate, rpc_service_controller, 'CreateCacheCadeVolume',
        req.ControllerId, ctx, drive_id_list, raid_type, name, write_policy, INVALID_BYTE, INVALID_BYTE,
        INVALID_BYTE, INVALID_BYTE, INVALID_BYTE)
    retval = ok and comp_code.Success or cm.IPMI_CODE[retval.err_code] or cm.IPMI_CODE_DEFAULT
    local create_result = retval == 0 and 'successfully' or 'failed'
    ipmi.ipmi_operation_log(ctx, 'storage', "Create RAID controller %s cachedcade logical drive RAID %s %s",
                            req.ControllerId, raid_type, create_result)

    return msg.CreateCachecadeLogicalDriveRsp.new(retval, cm.MANUFACTURE_ID, 0, '')
end

-- 在已有的阵列上创建逻辑盘
function c_ipmi_service.create_logical_drive_on_existed_array(req, ctx)
    if req.ManufacturerId ~= cm.MANUFACTURE_ID then
        log:error('[Storage] ManufacturerId wrong. ManufacturerId is %d', req.ManufacturerId)
        return msg.CreateLogicalDriveOnExistedArrayRsp.new(comp_code.InvalidFieldRequest, cm.MANUFACTURE_ID, 0, '')
    end

    local finish, integrated_data = pcall(integrate_request_data, req)
    if not finish then
        return msg.CreateLogicalDriveOnExistedArrayRsp.new(integrated_data, cm.MANUFACTURE_ID, 0, '')
    else
        req.Data = integrated_data
    end

    local data = req.Data
    local name_length = string.unpack('B', string.sub(data, 1, 1))
    if name_length > cm.SL_LD_NAME_LENGTH or #req.Data < name_length + 13 then
        log:error('[Storage]VolumeName length is %d, param length is %d', name_length, #req.Data)
        return msg.CreateLogicalDriveOnExistedArrayRsp.new(comp_code.ReqDataLenInvalid, cm.MANUFACTURE_ID, 0, '')
    end

    local name_len = name_length
    local name = string.sub(data, 2, name_len + 1)
    local capacity = string.unpack('I4', string.sub(data, name_len + 2, name_len + 5))
    local capacity_unit = string.unpack('B', string.sub(data, name_len + 6, name_len + 6))
    local strip_size = string.unpack('B', string.sub(data, name_len + 7, name_len + 7))
    local read_policy = string.unpack('B', string.sub(data, name_len + 8, name_len + 8))
    local write_policy = string.unpack('B', string.sub(data, name_len + 9, name_len + 9))
    local io_policy = string.unpack('B', string.sub(data, name_len + 10, name_len + 10))
    local access_policy = string.unpack('B', string.sub(data, name_len + 11, name_len + 11))
    local drive_cache_policy = string.unpack('B', string.sub(data, name_len + 12, name_len + 12))
    local initialization_mode = string.unpack('B', string.sub(data, name_len + 13, name_len + 13))

    local array_id = (req.ArrayIDHigh << 4) + req.ArrayIDLow

    local ok, retval = pcall(rpc_service_controller.ctrl_operate, rpc_service_controller,
        'CreateVolumeInExistingArray', req.ControllerId, ctx, array_id, 0xff, 0xff, 0xff, name,
        capacity, capacity_unit, strip_size, read_policy, write_policy, io_policy, access_policy,
        drive_cache_policy, initialization_mode, 0xff)

    retval = ok and cm.SM_CODE_SUCCESS or retval

    local create_result = retval == 0 and 'successfully' or 'failed'
    ipmi.ipmi_operation_log(ctx, 'storage', "Add RAID controller %s logic drive on Array %s %s", req.ControllerId,
                            array_id, create_result)
    return msg.CreateLogicalDriveOnExistedArrayRsp.new(retval, cm.MANUFACTURE_ID, 0, '')
end

-- 删除逻辑盘
function c_ipmi_service.delete_logical_drive(req, ctx)
    if req.ManufacturerId ~= cm.MANUFACTURE_ID then
        log:error('[Storage] ManufacturerId wrong. ManufacturerId is %d', req.ManufacturerId)
        return msg.DeleteLogicalDriveRsp.new(comp_code.InvalidFieldRequest, cm.MANUFACTURE_ID, 0, '')
    end
    local ok, retval = pcall(rpc_service_controller.ctrl_operate, rpc_service_controller, 'DeleteVolume',
        req.ControllerId, ctx, req.VolumeId)
    retval = ok and comp_code.Success or cm.IPMI_CODE[retval.err_code] or cm.IPMI_CODE_DEFAULT
    local delete_result = retval == 0 and 'successfully' or 'failed'
    ipmi.ipmi_operation_log(ctx, 'storage', "Delete RAID controller %s logic drive %s %s", req.ControllerId,
                            req.VolumeId, delete_result)
    return msg.DeleteLogicalDriveRsp.new(retval, cm.MANUFACTURE_ID, 0, '')
end

-- 设置逻辑盘名称
function c_ipmi_service.set_logical_drive_name(req, ctx)
    local finish, integrated_data = pcall(integrate_request_data, req)
    if not finish then
        return msg.SetLogicalDriveNameRsp.new(integrated_data, cm.MANUFACTURE_ID, 0, '')
    else
        req.Data = integrated_data
    end

    if req.ManufacturerId ~= cm.MANUFACTURE_ID then
        log:error('[Storage] ManufacturerId wrong. ManufacturerId is %d', req.ManufacturerId)
        return msg.SetLogicalDriveNameRsp.new(comp_code.InvalidFieldRequest, cm.MANUFACTURE_ID, 0, '')
    end

    local name_length = string.unpack('B', string.sub(req.Data, 1, 1))
    local name = string.sub(req.Data, 2, name_length + 1)

    if name_length > cm.SL_LD_NAME_LENGTH then
        log:error('[Storage]VolumeName length is %d, param length is %d', name_length, #req.Data)
        return msg.SetLogicalDriveNameRsp.new(comp_code.ReqDataLenInvalid, cm.MANUFACTURE_ID, 0, '')
    end

    local ok, retval = pcall(rpc_service_volume.volume_operate, rpc_service_volume, "SetName",
        req.ControllerId, req.VolumeId, ctx, name)
    retval = ok and comp_code.Success or cm.IPMI_CODE[retval.err_code] or cm.IPMI_CODE_DEFAULT
    local operation_result = retval == comp_code.Success and 'successfully' or 'failed'
    ipmi.ipmi_operation_log(ctx, 'storage',
        "Set RAID controller %s logical drive %s name to %s " .. operation_result,
        req.ControllerId, req.VolumeId, name)
    return msg.SetLogicalDriveNameRsp.new(retval, cm.MANUFACTURE_ID, 0, '')
end

local function set_logical_drive_property(req, ctx, rsp_name, method_name)
    local finish, integrated_data = pcall(integrate_request_data, req)
    if not finish then
        error(msg[rsp_name].new(integrated_data, cm.MANUFACTURE_ID, 0, ''))
    else
        req.Data = integrated_data
    end

    local cc, reserve, data = check_ctrl_and_volume(req)
    if cc then
        error(msg[rsp_name].new(cc, cm.MANUFACTURE_ID, reserve, data))
    end

    local prop_val = method_name == 'SetBootable' and 1 or string.unpack('B', string.sub(req.Data, 1, 1))
    local ok, retval = pcall(rpc_service_volume.volume_operate, rpc_service_volume, method_name,
        req.ControllerId, req.VolumeId, ctx, prop_val)

    retval = ok and cm.SM_CODE_SUCCESS or retval.err_code
    return msg[rsp_name].new(cm.IPMI_CODE[retval] or cm.IPMI_CODE_DEFAULT, cm.MANUFACTURE_ID, 0, "")
end

function c_ipmi_service.set_logic_drive_rp(req, ctx)
    local ok, res = pcall(set_logical_drive_property, req, ctx, "SetLogicDriveRpRsp", "SetReadPolicy")

    if ok then
        local rp = string.unpack('B', string.sub(req.Data, 1, 1))
        local operation_result = res.CompletionCode == comp_code.Success and 'successfully' or 'failed'
        ipmi.ipmi_operation_log(ctx, 'storage',
            "Set RAID controller %s logical drive %s Read Policy to %s " .. operation_result,
            req.ControllerId, req.VolumeId, cm.READ_POLICY_LIST[rp] or "Unknown")
    end
    return res
end

function c_ipmi_service.set_logic_drive_wp(req, ctx)
    local ok, res = pcall(set_logical_drive_property, req, ctx, "SetLogicDriveWpRsp", "SetWritePolicy")

    if ok then
        local wp = string.unpack('B', string.sub(req.Data, 1, 1))
        local operation_result = res.CompletionCode == comp_code.Success and 'successfully' or 'failed'
        ipmi.ipmi_operation_log(ctx, 'storage',
            "Set RAID controller %s logical drive %s Write Policy to %s " .. operation_result,
            req.ControllerId, req.VolumeId, cm.WRITE_PLOICY_LIST[wp] or "Unknown")
    end
    return res
end

function c_ipmi_service.set_logical_drive_iop(req, ctx)
    local ok, res = pcall(set_logical_drive_property, req, ctx, "SetLogicalDriveIOPRsp", "SetIOPolicy")

    if ok then
        local prop_val = string.unpack('B', string.sub(req.Data, 1, 1))
        local operation_result = res.CompletionCode == comp_code.Success and 'successfully' or 'failed'
        ipmi.ipmi_operation_log(ctx, 'storage',
            "Set RAID controller %s logical drive %s IO Policy to %s " .. operation_result,
            req.ControllerId, req.VolumeId, cm.IO_PLOICY_LIST[prop_val] or "Unknown")
    end
    return res
end

function c_ipmi_service.set_logical_drive_ap(req, ctx)
    local ok, res = pcall(set_logical_drive_property, req, ctx, "SetLogicalDriveAPRsp", "SetAccessPolicy")

    if ok then
        local prop_val = string.unpack('B', string.sub(req.Data, 1, 1))
        local operation_result = res.CompletionCode == comp_code.Success and 'successfully' or 'failed'
        ipmi.ipmi_operation_log(ctx, 'storage',
            "Set RAID controller %s logical drive %s Access Policy to %s " .. operation_result,
            req.ControllerId, req.VolumeId, cm.ACCESS_POLICY_LIST[prop_val] or "Unknown")
    end
    return res
end

function c_ipmi_service.set_logical_drive_dcp(req, ctx)
    local ok, res = pcall(set_logical_drive_property, req, ctx, "SetLogicalDriveDCPRsp", "SetDiskCachePolicy")

    if ok then
        local prop_val = string.unpack('B', string.sub(req.Data, 1, 1))
        local operation_result = res.CompletionCode == comp_code.Success and 'successfully' or 'failed'
        ipmi.ipmi_operation_log(ctx, 'storage',
            "Set RAID controller %s logical drive %s Disk Cache Policy to %s " .. operation_result,
            req.ControllerId, req.VolumeId, cm.DRIVE_CACHE_POLICY_LIST[prop_val] or "Unknown")
    end
    return res
end

function c_ipmi_service.set_logical_drive_bgi(req, ctx)
    local ok, res = pcall(set_logical_drive_property, req, ctx, "SetLogicalDriveBGIRsp", "SetBGIEnable")

    if ok then
        local prop_val = string.unpack('B', string.sub(req.Data, 1, 1))
        local operation_result = res.CompletionCode == comp_code.Success and 'successfully' or 'failed'
        ipmi.ipmi_operation_log(ctx, 'storage',
            "Set RAID controller %s logical drive %s BGI to %s " .. operation_result,
            req.ControllerId, req.VolumeId, prop_val == 1 and 'enabled' or 'disabled')
    end
    return res
end

function c_ipmi_service.set_logical_drive_cachecade(req, ctx)
    local ok, res = pcall(set_logical_drive_property, req, ctx, "SetLogicalDriveCachecadeRsp", "SetCachecadeEnable")

    if ok then
        local prop_val = string.unpack('B', string.sub(req.Data, 1, 1))
        local operation_result = res.CompletionCode == comp_code.Success and 'successfully' or 'failed'
        ipmi.ipmi_operation_log(ctx, 'storage',
            "Set RAID controller %s logical drive %s SSD caching to %s " .. operation_result,
            req.ControllerId, req.VolumeId, prop_val == 1 and 'enabled' or 'disabled')
    end
    return res
end

function c_ipmi_service.set_logic_drive_bootable(req, ctx)
    local ok, res = pcall(set_logical_drive_property, req, ctx, "SetLogicDriveBootableRsp", "SetBootable")

    if ok then
        local operation_result = res.CompletionCode == comp_code.Success and 'successfully' or 'failed'
        ipmi.ipmi_operation_log(ctx, 'storage',
            "Set RAID controller %s logical drive %s as boot device " .. operation_result,
            req.ControllerId, req.VolumeId)
    end
    return res
end

-- 控制器设置相关
function c_ipmi_service.set_controller_copyback(req, ctx)
    local finish, integrated_data = pcall(integrate_request_data, req)
    if not finish then
        return msg.SetControllerCopybackRsp.new(integrated_data, cm.MANUFACTURE_ID, 0, '')
    else
        req.Data = integrated_data
    end

    local ok, rsp = pcall(get_controller, req)
    if not ok then
        return msg.SetControllerCopybackRsp.new(rsp, cm.MANUFACTURE_ID, 0, '')
    end

    local unpack_data = string.unpack('B', req.Data)
    local retval
    ok, retval = pcall(rpc_service_controller.ctrl_operate, rpc_service_controller, 'SetCopyBackState',
        rsp.Id, ctx, unpack_data)
    retval = ok and comp_code.Success or cm.IPMI_CODE[retval.err_code] or cm.IPMI_CODE_DEFAULT

    local set_result = retval == 0 and "successfully" or "failed"
    local expect_state = unpack_data == 0 and "disabled" or "enabled"
    ipmi.ipmi_operation_log(ctx, 'storage', "Set RAID controller %s Copyback %s %s", req.ControllerId, expect_state,
                            set_result)

    return msg.SetControllerCopybackRsp.new(retval, cm.MANUFACTURE_ID, 0, string.pack('B', rsp.CopyBackState))
end

function c_ipmi_service.set_controller_smarter_copyback(req, ctx)
    local finish, integrated_data = pcall(integrate_request_data, req)
    if not finish then
        return msg.SetControllerSmarterCopybackRsp.new(integrated_data, cm.MANUFACTURE_ID, 0, '')
    else
        req.Data = integrated_data
    end

    local ok, rsp = pcall(get_controller, req)
    if not ok then
        return msg.SetControllerSmarterCopybackRsp.new(rsp, cm.MANUFACTURE_ID, 0, '')
    end

    local unpack_data = string.unpack('B', req.Data)
    local retval
    ok, retval = pcall(rpc_service_controller.ctrl_operate, rpc_service_controller, 'SetSmarterCopyBackState',
        rsp.Id, ctx, unpack_data)
    retval = ok and comp_code.Success or cm.IPMI_CODE[retval.err_code] or cm.IPMI_CODE_DEFAULT

    local set_result = retval == 0 and "successfully" or "failed"
    local expect_state = unpack_data == 0 and "disabled" or "enabled"
    ipmi.ipmi_operation_log(ctx, 'storage', "Set RAID controller %s Copyback on SMART error %s %s",
                            req.ControllerId, expect_state, set_result)

    return msg.SetControllerSmarterCopybackRsp.new(retval, cm.MANUFACTURE_ID, 0,
                                                   string.pack('B', rsp.SmarterCopyBackState))
end

function c_ipmi_service.set_controller_restore_settings(req, ctx)
    local ok, rsp = pcall(get_controller, req)
    if not ok then
        return msg.SetControllerRestoreSettingsRsp.new(rsp, cm.MANUFACTURE_ID, 0, '')
    end
    local retval
    ok, retval = pcall(rpc_service_controller.ctrl_operate, rpc_service_controller, 'RestoreDefaultSettings',
        rsp.Id, ctx)
    retval = ok and comp_code.Success or cm.IPMI_CODE[retval.err_code] or cm.IPMI_CODE_DEFAULT
    local set_result = retval == 0 and "successfully" or "failed"
    ipmi.ipmi_operation_log(ctx, 'storage', "Restore RAID controller %s default settings %s", req.ControllerId,
        set_result)

    return msg.SetControllerRestoreSettingsRsp.new(retval, cm.MANUFACTURE_ID, 0, "")
end

function c_ipmi_service.set_controller_jbod(req, ctx)
    local ok, rsp = pcall(get_controller, req)
    if not ok then
        return msg.SetControllerJbodRsp.new(rsp, cm.MANUFACTURE_ID, 0, '')
    end

    local finish, integrated_data = pcall(integrate_request_data, req)
    if not finish then
        return msg.SetControllerJbodRsp.new(integrated_data, cm.MANUFACTURE_ID, 0, '')
    else
        req.Data = integrated_data
    end

    local unpack_data = string.unpack('B', req.Data)
    local retval
    ok, retval = pcall(rpc_service_controller.ctrl_operate, rpc_service_controller, 'SetJBODState',
        rsp.Id, ctx, unpack_data)
    retval = ok and comp_code.Success or cm.IPMI_CODE[retval.err_code] or cm.IPMI_CODE_DEFAULT

    local set_result = retval == 0 and "successfully" or "failed"
    local expect_state = unpack_data == 0 and "disabled" or "enabled"
    ipmi.ipmi_operation_log(ctx, 'storage', "Set RAID controller %s JBOD %s %s", req.ControllerId, expect_state,
                            set_result)

    return msg.SetControllerJbodRsp.new(retval, cm.MANUFACTURE_ID, 0, string.pack('B', rsp.JBODState))
end

local function non_persist_save(obj, property, value)
    if not obj or not obj[property] then
        return
    end
    local ctx = context.copy_context() or context.new()
    context.set_non_persist(ctx)
    context.with_context(ctx, function()
        obj[property] = value
    end)
end

local function controller_property_save(obj, property, value, persist_type)
    if obj[property] == value then
        controller_collection.get_instance().persist:per_save(sqlite3.UPDATE, 't_controller_info',
            {{'DeviceName', obj.DeviceName}}, {[property] = {value = value, persist_type = persist_type}})
        return
    end
    obj[property] = value
end

local function set_controller_outofband_state(ctx, obj, state, saved)
    if not saved then
        non_persist_save(obj, 'OOBSupport', state)
    else
        controller_property_save(obj, 'OOBSupport', state, 'protect_power_off')
    end
    ipmi.ipmi_operation_log(ctx, 'storage', "Set RAID controller %s Out-of-band management %s successfully", 
        obj.Id, state == 1 and 'enabled' or 'disabled')
end

function c_ipmi_service.set_controller_outofband(req, ctx)
    if req.ManufacturerId ~= cm.MANUFACTURE_ID then
        log:error('[Storage] ManufacturerId wrong. ManufacturerId is %d', req.ManufacturerId)
        return msg.SetControllerOutofbandRsp.new(comp_code.InvalidFieldRequest, cm.MANUFACTURE_ID, 0, '')
    end

    local finish, integrated_data = pcall(integrate_request_data, req)
    if not finish then
        return msg.SetControllerOutofbandRsp.new(integrated_data, cm.MANUFACTURE_ID, 0, '')
    else
        req.Data = integrated_data
    end

    local state = string.unpack('B', string.sub(req.Data, 1, 1))
    local saved = false
    -- 保持原始值
    if state == cm.OOB_NOT_SUPPORT_LOSE or state == cm.OOB_SUPPORT_LOSE then -- 掉电丢失
    elseif state == cm.OOB_SUPPORT_SAVE or state == cm.OOB_NO_SUPPORT_SAVE then  -- 掉电不丢失
        state = state - 2  -- 偏移2
        saved = true
    else
        return msg.SetControllerOutofbandRsp.new(comp_code.InvalidFieldRequest, cm.MANUFACTURE_ID, 0, '')
    end

    -- 0xFF - 表示设置所有控制器
    local ctrl
    if req.ControllerId == INVALID_BYTE then
        local controller_set = controller_collection.get_instance():get_all_controllers()
        for _, ctrl in pairs(controller_set) do
            set_controller_outofband_state(ctx, ctrl, state, saved)
        end
    else
        ctrl = controller_collection.get_instance():get_by_controller_id(req.ControllerId)
        if not ctrl then
            return msg.SetControllerOutofbandRsp.new(comp_code.SensorInvalid, cm.MANUFACTURE_ID, 0, '')
        end

        set_controller_outofband_state(ctx, ctrl, state, saved)
    end

    return msg.SetControllerOutofbandRsp.new(0, cm.MANUFACTURE_ID, 0, '')
end

local function get_disk_silk_by_light(drives)
    local rsp_data = ''
    local check_flag = 0
    for _, v in pairs(drives) do
        if v.LocateLed == 1 and v.FaultLed == 0 then
            rsp_data = string.gsub(v.PhysicalLocation, "%s+", "") .. v.Name
            check_flag = check_flag + 1
        end
    end

    return rsp_data, check_flag
end

local function get_disk_silk_by_new_light(drives)
    local data_len = 0
    local check_flag = 0
    local rsp_data = ''
    local actual_flag = 0 -- 实际返回的丝印个数
    local phy_loc = ''
    local len = 0
    for _, v in pairs(drives) do
        if v.LocateLed == 1 and v.FaultLed == 0 then
            check_flag = check_flag + 1
            phy_loc = string.gsub(v.PhysicalLocation, "%s+", "") -- 清除字符串中的空格

            len = #phy_loc + #v.Name
            if data_len + len + 1 <= IPMI_MSG_MAX_PAYLOAD_LEN - COMP_CODE_AND_MANU_LEN - RESERVED_LENGTH then
                actual_flag = actual_flag + 1
                data_len = data_len + len + 1
                rsp_data = rsp_data .. string.pack('B', len) .. phy_loc .. v.Name
            end
        end
    end

    rsp_data = string.pack('B', check_flag) .. string.pack('B', actual_flag) .. rsp_data
    return rsp_data
end

function c_ipmi_service.get_disk_silk(req, ctx)
    if req.ManufacturerId ~= cm.MANUFACTURE_ID then
        log:error('[Storage] ManufacturerId wrong. ManufacturerId is %d', req.ManufacturerId)
        return msg.GetDiskSilkRsp.new(comp_code.InvalidFieldRequest, cm.MANUFACTURE_ID, '')
    end

    local drives_name = drive_collection.get_instance():get_identified_drives()
    local drives = {}
    for _, v in pairs(drives_name) do
        local drive = drive_collection:get_drive(v)
        if drive then
            table.insert(drives, drive)
        end
    end

    local check_flag = 0
    local rsp_data = ''
    if req.Type == GET_SILK_BY_LIGHT_UP then -- 点灯识别
        rsp_data, check_flag = get_disk_silk_by_light(drives)

        if check_flag ~= 1 then
            log:error('find drive object failed')
            return msg.GetDiskSilkRsp.new(comp_code.CommandNotAvailable, cm.MANUFACTURE_ID, '')
        end
    elseif req.Type == GET_SILK_BY_LIGHT_UP_NEW then
        -- 点灯识别格式为： 总亮灯硬盘个数：1 + 实际返回丝印个数：1 + len：1，data + len：1，data+ …
        rsp_data = get_disk_silk_by_new_light(drives)
    else
        return msg.GetDiskSilkRsp.new(comp_code.ParmOutOfRange, cm.MANUFACTURE_ID, '')
    end

    return msg.GetDiskSilkRsp.new(comp_code.Success, cm.MANUFACTURE_ID, rsp_data)
end

function c_ipmi_service.get_raid_controller_temp(req, ctx)
    if req.ManufacturerId ~= cm.MANUFACTURE_ID then
        log:error('[Storage] ManufacturerId wrong. ManufacturerId is %d', req.ManufacturerId)
        return msg.GetRaidControllerTempRsp.new(comp_code.InvalidFieldRequest, cm.MANUFACTURE_ID, 0, 0)
    end

    local ctrl = controller_collection.get_instance():get_by_controller_id(req.Device)
    if not ctrl then
        return msg.GetRaidControllerTempRsp.new(comp_code.SensorInvalid, cm.MANUFACTURE_ID, 0, 0)
    end

    local temp = ctrl.TemperatureCelsius
    if temp then
        return msg.GetRaidControllerTempRsp.new(comp_code.Success, cm.MANUFACTURE_ID, temp, 0)
    else
        -- 控制器温度状态异常
        return msg.GetRaidControllerTempRsp.new(comp_code.CommandNotAvailable, cm.MANUFACTURE_ID, 0, 0)
    end
end

function c_ipmi_service.set_location_indicator_state(req, ctx)
    if req.ManufacturerId ~= cm.MANUFACTURE_ID then
        log:error('[Storage] ManufacturerId wrong. ManufacturerId is %s', req.ManufacturerId)
        return msg.SetLocationIndicatorStateRsp.new(comp_code.ReqDataLenInvalid, cm.MANUFACTURE_ID, 0, '')
    end

    local num_data, _ = string.unpack('B', req.Data)
    if num_data ~= cm.LOCATE and num_data ~= cm.STOP_LOCATE then
        log:error("[Storage] IPMI request data invalid. Request data is %s", num_data)
        return msg.SetLocationIndicatorStateRsp.new(comp_code.ParmOutOfRange, cm.MANUFACTURE_ID, 0, '')
    end

    local ok, retval
    local drive = drive_collection:get_drive_by_id(req.ControllerId, req.DriveId)
    if not drive then
        log:error("[Storage] IPMI get drive failed. ControllerId is %s, DriveId is %s", req.ControllerId, req.DriveId)
        return msg.SetLocationIndicatorStateRsp.new(comp_code.InvalidFieldRequest, cm.MANUFACTURE_ID, 0, '')
    end
    if req.ControllerId == CONTROLLER_ID_DEFAULT then
        -- 通过 smc 点定位灯
        ok = drive:set_locate_led_by_smc(num_data)
    else
        -- 通过 raid 点定位灯
        ok, retval = pcall(rpc_service_drive.drive_operate, rpc_service_drive, 'SetLocationIndicatorState',
            drive.Name, ctx, num_data)
    end
    if ok then
        retval = cm.SM_CODE_SUCCESS
        if num_data == cm.STOP_LOCATE then
            ipmi.ipmi_operation_log(ctx, 'storage', "Stop locating %s successfully", drive.Name)
        else
            ipmi.ipmi_operation_log(ctx, 'storage', "Start locating %s successfully", drive.Name)
        end
    else
        retval = req.ControllerId == CONTROLLER_ID_DEFAULT and cm.INVALID_U8 or retval.err_code
        if num_data == cm.STOP_LOCATE then
            ipmi.ipmi_operation_log(ctx, 'storage', "Stop locating %s failed", drive.Name)
        else
            ipmi.ipmi_operation_log(ctx, 'storage', "Start locating %s failed", drive.Name)
        end
    end
    log:notice('[Storage]IPMI function return value is %s', retval)
    --  CompletionCode强转，取一个字节长度，与v2保持一致

    return msg.SetLocationIndicatorStateRsp.new(cm.IPMI_CODE[retval] or cm.IPMI_CODE_DEFAULT,
        cm.MANUFACTURE_ID, 0, "")
end

function c_ipmi_service.set_firmware_status(req, ctx)
    if req.ManufacturerId ~= cm.MANUFACTURE_ID then
        log:error('[Storage] ManufacturerId wrong. ManufacturerId is %d', req.ManufacturerId)
        return msg.SetFirmwareStatusRsp.new(comp_code.ReqDataLenInvalid, cm.MANUFACTURE_ID, 0, "")
    end
    local drive = drive_collection:get_drive_by_id(req.ControllerId, req.DriveId)
    if not drive then
        log:error("[Storage] IPMI get drive failed. ControllerId is %d, DriveId is %d", req.ControllerId, req.DriveId)
        return msg.SetFirmwareStatusRsp.new(comp_code.InvalidFieldRequest, cm.MANUFACTURE_ID, 0, "")
    end

    local num_data, _ = string.unpack('B', req.Data)
    if num_data ~= cm.FW_UG and num_data ~= cm.FW_ONLINE and
        num_data ~= cm.FW_OFFLINE and num_data ~= cm.FW_SYSTEM then
        log:error("[Storage] IPMI request data invalid. Request data is %d", num_data)
        return msg.SetFirmwareStatusRsp.new(comp_code.ParmOutOfRange, cm.MANUFACTURE_ID, 0, "")
    end

    local ok, retval = pcall(rpc_service_drive.drive_operate, rpc_service_drive, 'SetFirmwareStatus',
        drive.Name, ctx, num_data)
    local fw_state = get_physical_drive_state_str(num_data)
    retval = ok and comp_code.Success or cm.IPMI_CODE[retval.err_code] or cm.IPMI_CODE_DEFAULT
    local result = retval == comp_code.Success and 'successfully' or 'failed'

    ipmi.ipmi_operation_log(ctx, 'storage', "Set physical drive %s state to %s %s", drive.Name, fw_state, result)

    log:notice('[Storage]IPMI function return value is %d', retval)
    return msg.SetFirmwareStatusRsp.new(retval, cm.MANUFACTURE_ID, 0, "")
end

function c_ipmi_service.set_fault_indicator_state(req, ctx)
    if req.ManufacturerId ~= cm.MANUFACTURE_ID then
        log:error('[Storage] ManufacturerId wrong. ManufacturerId is %s', req.ManufacturerId)
        return msg.SetFaultIndicatorStateRsp.new(comp_code.ReqDataLenInvalid, cm.MANUFACTURE_ID, 0)
    end
    local num_data = req.Data:byte(req.WritingLength)
    if num_data ~= cm.FAULT and num_data ~= cm.STOP_FAULT then
        log:error("[Storage] IPMI request data invalid. Request data is %s", num_data)
        return msg.SetFaultIndicatorStateRsp.new(comp_code.ParmOutOfRange, cm.MANUFACTURE_ID, 0)
    end

    -- 通过 smc 点故障灯
    local drive = drive_collection:get_drive_by_id(req.ControllerId, req.DriveId)
    if not drive then
        log:error("[Storage] IPMI get drive failed. ControllerId is %s, DriveId is %s", req.ControllerId, req.DriveId)
        return msg.SetFaultIndicatorStateRsp.new(comp_code.InvalidFieldRequest, cm.MANUFACTURE_ID, 0)
    end
    local ok, retval
    ok = drive:set_fault_led_by_smc(num_data)
    if ok then
        retval = cm.SM_CODE_SUCCESS
        if num_data == cm.STOP_FAULT then 
            ipmi.ipmi_operation_log(ctx, 'storage', "Stop faulting %s successfully", drive.Name)
        else
            ipmi.ipmi_operation_log(ctx, 'storage', "Start faulting %s successfully", drive.Name)
        end
    else
        retval = comp_code.CommandNotAvailable
        if num_data == cm.STOP_FAULT then 
            ipmi.ipmi_operation_log(ctx, 'storage', "Stop faulting %s failed", drive.Name)
        else
            ipmi.ipmi_operation_log(ctx, 'storage', "Start faulting %s failed", drive.Name)
        end
    end
    log:notice('[Storage]IPMI function return value is %s', retval)
    --  CompletionCode强转，取一个字节长度，与v2保持一致
    return msg.SetFaultIndicatorStateRsp.new(cm.IPMI_CODE[retval] or cm.IPMI_CODE_DEFAULT, cm.MANUFACTURE_ID, 0)
end

function c_ipmi_service.set_hotspare_type(req, ctx)
    if req.ManufacturerId ~= cm.MANUFACTURE_ID then
        log:error('[Storage] ManufacturerId wrong. ManufacturerId is %d', req.ManufacturerId)
        return msg.SetHotspareTypeRsp.new(comp_code.ReqDataLenInvalid, cm.MANUFACTURE_ID, 0, "")
    end
    local drive = drive_collection:get_drive_by_id(req.ControllerId, req.DriveId)
    if not drive then
        log:error("[Storage] IPMI get drive failed. ControllerId is %d, DriveId is %d", req.ControllerId, req.DriveId)
        return msg.SetHotspareTypeRsp.new(comp_code.InvalidFieldRequest, cm.MANUFACTURE_ID, 0, "")
    end

    -- Data分割为 driveId 和 VolumeId
    local number_Data, _ = string.unpack('H', req.Data)
    local VolumeId = number_Data >> 8 -- 8 两个字节取第一个字节
    local HotspareType = number_Data & 0xFF -- 取两个字节的第二个字节
    log:notice('Hotspare is %d, VolumeId is %d', HotspareType, VolumeId)
    if HotspareType ~= cm.CANCEL_HOTSPARE and HotspareType ~= cm.GLOBAL_HOTSPARE and
        HotspareType ~= cm.DEDICATED_HOTSPARE then
        return msg.SetHotspareTypeRsp.new(comp_code.ParmOutOfRange, cm.MANUFACTURE_ID, 0, "")
    end

    local ok, retval = pcall(rpc_service_drive.drive_operate, rpc_service_drive, 'SetHotspareType',
        drive.Name, ctx, HotspareType, VolumeId)
    retval = ok and cm.SM_CODE_SUCCESS or cm.IPMI_CODE[retval.err_code] or cm.IPMI_CODE_DEFAULT
    ipmi.ipmi_operation_log(ctx, 'storage', "Set physical drive %s hot spare to %s %s", drive.Name,
        cm.PD_HOT_SPARE_STR[HotspareType] or 'Unknown', retval == cm.SM_CODE_SUCCESS and 'successfully' or 'failed')
    log:notice('[Storage]IPMI function return value is %d', retval)
    return msg.SetHotspareTypeRsp.new(retval, cm.MANUFACTURE_ID, 0, "")
end

local function ipmi_log_with_bios_set_check(ctx)
    if ctx.ChanType == CT_HOST and smbios_status ~= SMBIOS_WRITE_FINISH then
        log:maintenance(log.MLOG_INFO, cm.FC__PUBLIC_OK, "Set raid status successfully")
    else
        ipmi.ipmi_operation_log(ctx, 'storage', "Set raid status successfully")
    end
end

function c_ipmi_service.set_raid_fault_status(req, ctx)
    if req.ManufacturerId ~= cm.MANUFACTURE_ID then
        log:error('[Storage] ManufacturerId wrong. ManufacturerId is %s', req.ManufacturerId)
        return msg.SetRaidFaultStatusRsp.new(comp_code.InvalidFieldRequest, cm.MANUFACTURE_ID)
    end

    if req.DeviceStatus1 ~= 0 then
        log:error('[Storage] Invalid device status1 %s', req.DeviceStatus1)
        return msg.SetRaidFaultStatusRsp.new(comp_code.InvalidFieldRequest, cm.MANUFACTURE_ID)
    end

    local ctrl
    local controller_set = controller_collection.get_instance():get_all_controllers()
    local ctrl_hit = false
    for _, ctrl in pairs(controller_set) do
        if req.Bus == ctrl.DevBus and req.Device == ctrl.DevDevice and req.Function == ctrl.DevFunction then
            ctrl.FaultCodeByBios = req.ErrorCode
            ctrl_hit = true
            ipmi_log_with_bios_set_check(ctx)
            break
        end
    end
    if not ctrl_hit then
        log:error('[Storage] Invalid device BDF %02x:%02x:%02x', req.Bus, req.Device, req.Function)
        return msg.SetRaidFaultStatusRsp.new(comp_code.InvalidFieldRequest, cm.MANUFACTURE_ID)
    end
    return msg.SetRaidFaultStatusRsp.new(comp_code.Success, cm.MANUFACTURE_ID)
end

function c_ipmi_service.get_manufacturer(req, ctx)
    if req.ManufactureId ~= cm.MANUFACTURE_ID then
        log:error('[Storage] ManufacturerId wrong. ManufacturerId is %s', req.ManufactuerId)
        return msg.GetDiskManufacturerRsp.new(comp_code.InvalidFieldRequest, cm.MANUFACTURE_ID, 0, '')
    end
    local device_number = req.DeviceNumber
    local total_count = drive_collection.get_instance():get_count()
    if device_number == 0 or device_number > total_count then
        log:error('[Storage] Invalid device number %s, total_count:%s ', device_number, total_count)
        return msg.GetDiskManufacturerRsp.new(comp_code.ParmOutOfRange, cm.MANUFACTURE_ID, 0, '')
    end

    local manufacturer = drive_collection.get_instance():get_manufacturer(device_number)
    log:notice("manufacturer:%s", manufacturer)
    return msg.GetDiskManufacturerRsp.new(comp_code.Success, cm.MANUFACTURE_ID, 0, manufacturer)
end

local pd_collect_flag = false -- 上一次日志收集是否还在进行中
function c_ipmi_service.set_pd_log_collect(req, ctx)
    if req.ManufacturerId ~= cm.MANUFACTURE_ID then
        log:error('[Storage] ManufacturerId wrong. ManufacturerId is %d', req.ManufacturerId)
        return msg.SetPdLogCollectRsp.new(comp_code.InvalidFieldRequest, cm.MANUFACTURE_ID)
    end
    local drives = drive_collection.get_instance():get_all_drives()
    if next(drives) == nil then
        log:notice("[Storage] Can not find any drives, Set_pd_log_collect command failed!")
        return msg.SetPdLogCollectRsp.new(comp_code.CommandNotAvailable, cm.MANUFACTURE_ID)
    end

    if pd_collect_flag then
        log:notice("[Storage] last drive log collect task is still running")
        return msg.SetPdLogCollectRsp.new(comp_code.Busy, cm.MANUFACTURE_ID)
    end

    skynet.fork(function()
        pd_collect_flag = true
        local ok, ret = pcall(function()
            for _, drive in pairs(drives) do
                drive.on_dump_log:emit()
            end
        end)

        if not ok then
            log:error('collect drive log failed, ret is %s', ret)
        end
        pd_collect_flag = false
    end)
    
    ipmi.ipmi_operation_log(ctx, 'storage', "Collect all drives log start.")
    return msg.SetPdLogCollectRsp.new(comp_code.Success, cm.MANUFACTURE_ID)
end

local DISK_FORM_TYPE = {
    DISK_TYPE_M2 = 0,
    DISK_TYPE_SATA = 1
}
 
local SATA_SPEED_INDEX = {
    SATA_PD_SPEED_UNDEFINED = 0,
    SATA_PD_SPEED_1P5G = 1,
    SATA_PD_SPEED_3G = 2,
    SATA_PD_SPEED_6G = 3,
    SATA_PD_SPEED_12G = 4
}
 
local SPEED_MAP = {
    [1] = SATA_SPEED_INDEX.SATA_PD_SPEED_1P5G,
    [3] = SATA_SPEED_INDEX.SATA_PD_SPEED_3G,
    [6] = SATA_SPEED_INDEX.SATA_PD_SPEED_6G,
    [12] = SATA_SPEED_INDEX.SATA_PD_SPEED_12G
}
 
local function convert_speed_to_enum(key)
    for k, v in pairs(SPEED_MAP) do
        if k == key then
            return v
        end
    end
    return INVALID_BYTE
end

local function trim_ending_spaces(str)
    return string.gsub(str, "%s+$", "")
end

function c_ipmi_service.set_pch_disk_info(req, ctx)
    local drive = nil
    if req.DiskType == DISK_FORM_TYPE.DISK_TYPE_SATA or req.DiskType == DISK_FORM_TYPE.DISK_TYPE_M2 then
        -- 根据 slot获取obj并校验是否在位 先根据name匹配，匹配不到再根据id匹配
        drive = drive_collection.get_instance():get_drive_by_drive_id(req.SlotNumber)
        if not drive then
            drive = drive_collection.get_instance():get_drive_by_id(255, req.SlotNumber)
            if not drive then
                log:error('[Storage] Drive(Id:%s) not exist', req.SlotNumber)
                return msg.SetPCHDiskInfoRsp.new(comp_code.InvalidFieldRequest)
            end
        end
        if drive.Presence ~= 1 or drive.RefControllerId ~= 255 then
            log:error('[Storage]%s not present or not pch drive', drive_name)
            return msg.SetPCHDiskInfoRsp.new(comp_code.InvalidFieldRequest)
        end
    else
        return msg.SetPCHDiskInfoRsp.new(comp_code.InvalidFieldRequest)
    end
    log:notice('[Storage] Disk%d Protocol[%d] Revision[%s] CapacityMB[%d] Model[%s]',
        req.SlotNumber, req.InterfaceType, req.FirmwareVersion, req.CapacityMB, req.Model)
    log:notice('[Storage] SerialNumber[%s] MediaType[%d] NegotiatedSpeedGbs[%d] CapableSpeedGbs[%d]',
        req.SN, req.MediaType, req.NegotitatedspeedGbs, req.CapableSpeedGbs)
    
    drive.Protocol = req.InterfaceType
    drive.Revision = string.sub(req.FirmwareVersion, 1, req.FirmwareVersionLen)
    drive.CapacityMiB = req.CapacityMB
    drive.Model = trim_ending_spaces(string.sub(req.Model, 1, req.ModelLen))
    drive.SerialNumber = trim_ending_spaces(string.sub(req.SN, 1, req.SNLen))
    drive.MediaType = req.MediaType
    drive.NegotiatedSpeedGbs = convert_speed_to_enum(req.NegotitatedspeedGbs)
    drive.CapableSpeedGbs = convert_speed_to_enum(req.CapableSpeedGbs)
    drive.Manufacturer = string.sub(req.Manufacturer, 1, req.ManufacturerLen)
    return msg.SetPCHDiskInfoRsp.new(comp_code.Success)
end

function c_ipmi_service.set_log_auto_collect_config(req, ctx)
    if req.ManufacturerId ~= cm.MANUFACTURE_ID then
        log:error('[Storage] ManufacturerId wrong. ManufacturerId is %d', req.ManufacturerId)
        return msg.SetLogAutoCollectConfigRsp.new(comp_code.InvalidFieldRequest, cm.MANUFACTURE_ID)
    end

    local data = req.Data
    local log_auto_collect_enable = string.sub( data, 1, 1)
    local log_auto_collect_interval = string.sub( data, 2, #data)

    if log_auto_collect_enable == "1" then
        log_auto_collect_enable = true
    elseif log_auto_collect_enable == "0" then
        log_auto_collect_enable = false
    else
        log:error('[Storage] LogAutoCollectEnable wrong. LogAutoCollectEnable is %d', log_auto_collect_enable)
        return msg.SetLogAutoCollectConfigRsp.new(comp_code.ReqDataLenInvalid, cm.MANUFACTURE_ID)
    end

    log_auto_collect_interval = tonumber(log_auto_collect_interval) or 0
    if log_auto_collect_interval < 1 or log_auto_collect_interval > 255 then
        log:error('[Storage] LogAutoCollectInterval wrong. LogAutoCollectInterval is %d', log_auto_collect_interval)
        return msg.SetLogAutoCollectConfigRsp.new(comp_code.ReqDataLenInvalid, cm.MANUFACTURE_ID)
    end

    c_drives_object.get_instance().obj.LogAutoCollectEnable = log_auto_collect_enable
    ipmi.ipmi_operation_log(ctx, 'storage', "Set LogAutoCollectEnable to %s", log_auto_collect_enable)
    c_drives_object.get_instance().obj.LogAutoCollectInterval = log_auto_collect_interval
    ipmi.ipmi_operation_log(ctx, 'storage', "Set LogAutoCollectInterval to %s", log_auto_collect_interval)
    return msg.SetLogAutoCollectConfigRsp.new(comp_code.Success, cm.MANUFACTURE_ID)
end
 
function c_ipmi_service.get_log_auto_collect_config(req, ctx)
    if req.ManufacturerId ~= cm.MANUFACTURE_ID then
        log:error('[Storage] ManufacturerId wrong. ManufacturerId is %d', req.ManufacturerId)
        return msg.GetLogAutoCollectConfigRsp.new(comp_code.InvalidFieldRequest, cm.MANUFACTURE_ID, 0, 0, 0)
    end

    local log_auto_collect_enable = c_drives_object.get_instance().obj.LogAutoCollectEnable
    local log_auto_collect_interval = c_drives_object.get_instance().obj.LogAutoCollectInterval

    if log_auto_collect_enable then
        log_auto_collect_enable = 1
    else
        log_auto_collect_enable = 0
    end
    return msg.GetLogAutoCollectConfigRsp.new(comp_code.Success, cm.MANUFACTURE_ID, 0, 
        log_auto_collect_enable, log_auto_collect_interval)
end

local drive_collect_flag = false -- 上一次日志收集是否还在进行中
function c_ipmi_service.start_drive_collect_log(req, ctx)
    if req.ManufacturerId ~= cm.MANUFACTURE_ID then
        log:error('[Storage] ManufacturerId wrong. ManufacturerId is %d', req.ManufacturerId)
        return msg.StartDriveCollectLogRsp.new(comp_code.InvalidFieldRequest, cm.MANUFACTURE_ID)
    end

    local drives = drive_collection.get_instance():get_all_drives()
    if not next(drives) then
        log:notice("[Storage] Can not find any drives, Set_pd_log_collect command failed!")
        return msg.StartDriveCollectLogRsp.new(comp_code.CommandNotAvailable, cm.MANUFACTURE_ID)
    end

    if drive_collect_flag then
        log:notice("[Storage] last drive log collect task is still running")
        return msg.StartDriveCollectLogRsp.new(comp_code.Busy, cm.MANUFACTURE_ID)
    end

    local drive_id = req.DriveId
    if drive_id == 255 then
        skynet.fork_once(function()
            drive_collect_flag = true
            local ok, ret = pcall(function()
                for _, drive in pairs(drives) do
                    drive.on_dump_log:emit()
                end
            end)

            if not ok then
                log:error('collect drive log failed, ret is %s', ret)
            end
            drive_collect_flag = false
        end)

        ipmi.ipmi_operation_log(ctx, 'storage', "Collect all drives log start.")
        return msg.StartDriveCollectLogRsp.new(comp_code.Success, cm.MANUFACTURE_ID)
    else
        local drive_name = "Disk"..drive_id
        skynet.fork_once(function()
            drive_collect_flag = true
            local ok, ret = pcall(function()
                for _, drive in pairs(drives) do
                    if drive.Name == drive_name then
                        drive.on_dump_log:emit()
                    end
                end
            end)

            if not ok then
                log:error('collect drive log failed, ret is %s', ret)
            end
            drive_collect_flag = false
        end)
        ipmi.ipmi_operation_log(ctx, 'storage', "Collect %s log start.", drive_name)
        return msg.StartDriveCollectLogRsp.new(comp_code.Success, cm.MANUFACTURE_ID)
    end
end

return singleton(c_ipmi_service)