-- 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 bios_enum = require 'bios.types.enums'
local smbios_object = require 'pojo.smbios_object'
local bios_firmware_enum = require 'domain.bios_firmware.defs'
local log = require 'mc.logging'
local msg = require 'bios.ipmi.ipmi_message'
local singleton = require 'mc.singleton'
local bios_factory = require 'factory.bios_factory'
local s_pack = string.pack
local s_unpack = string.unpack
local bs = require 'mc.bitstring'
local bs_util = require 'util.base_util'
local ipmi = require 'ipmi'
local comp_code = ipmi.types.Cc
local file_sec = require 'utils.file'
local base_messages = require 'messages.base'
local import_export_eng = require 'handler.export_import_engine'
local skynet = require 'skynet'
local class = require 'mc.class'
local utils = require 'mc.utils'
local pfr_service = require 'service.pfr_service'
local utils_core = require 'utils.core'
local reset_lock = require 'infrastructure.reset_lock'
local prop_def = require "macros.property_def"
local smbios_handler = require 'bios.smbios'
local clsmgmt = require 'mc.class_mgnt'
local smbios_service = class()

local E_OK = 1
local E_FAILED = -1
local BIOS_FLAG_IDLE = 0x00
local BIOS_FLAG_DOING = 0x01
local SMBIOS_MAXDATA_LEN = 237
-- 130 * 1024
local SMBIOS_DATA_SIZE = 133120
local smbios_prop = {"Version", "SKUNumber", "Family"}

function smbios_service:ctor(db)
    self.db = db
    self.smbios_object_collection = {}
    self.data_operator_collection = {}
    self.export_data_collection = {}
    self.smbios_handler_collection = {}
end

function smbios_service:delete_object(system_id)
    local smbios_obj = self:get_obj(system_id)
    if not smbios_obj then
        return
    end
    clsmgmt('SmBios'):remove(smbios_obj)
    self.smbios_object_collection[system_id] = nil
end

function smbios_service:add_obj(sys_id, smbios_gen_obj)
    if self.smbios_object_collection[tonumber(sys_id)] then
        return
    end

    self.smbios_object_collection[tonumber(sys_id)] = smbios_object.new(self.db, tonumber(sys_id), smbios_gen_obj)
    self.smbios_handler_collection[tonumber(sys_id)] =
        smbios_handler.new(tonumber(sys_id), bs_util.get_data_path(sys_id, prop_def.SMBIOS_CONF_FILE))
end

function smbios_service:register_object(bus)
    self.smbios_object:get_smbios_obj():register(bus)
end

function smbios_service:enable_multihost()
    self.multihost = true
end

function smbios_service:get_smbios_obj(sys_id)
    return self.smbios_object_collection[sys_id and tonumber(sys_id) or 1]
end

local export_list = {
    ['Version'] = 'Version',
    ['SKUNumber'] = 'SKUNumber',
    ['Family'] = 'Family'
}
function smbios_service:export(system_id)
    return import_export_eng.export_sevice_cfg(export_list, self, system_id)
end

function smbios_service:get_prop(prop, sys_id)
    local smbios_obj = self:get_smbios_obj(sys_id)
    if not smbios_obj then
        return
    end

    return smbios_obj:get_prop(prop)
end

function smbios_service:set_smbios_prop(prop, value, sys_id)
    local smbios_obj = self:get_smbios_obj(sys_id)
    if not smbios_obj then
        return
    end

    smbios_obj:set_prop(prop, value)
end

function smbios_service:get_manu_id()
    -- DB 07 00 -> 00 07 DB
    local manu_id = 0xDB0700
    local bin_id = s_pack("I3", manu_id)
    local res = s_unpack(">I3", bin_id)
    return res
end

function smbios_service:get_bios_id()
    return 0
end

function smbios_service:update_smbios_status(smbios_status, sys_id)
    local smbios_obj = self:get_smbios_obj(sys_id)
    if not smbios_obj then
        log:error("Get system %s SmBios object handle failed.", sys_id)
        return E_FAILED
    end

    smbios_obj:update_smbios_status(smbios_status)

    if smbios_status == bios_enum.SmbiosWriteStage.SMBIOS_WRITE_FINISH then
        log:running(log.RLOG_INFO,'Update SMBios Finished')
        local smbios_update_flag = smbios_obj:get_smbios_updated_flag()
        if smbios_update_flag == bios_enum.SmBiosUpdatedFlag.NO_ONCE_UPDATE_SMBIOS:value() then
            smbios_obj:update_smbios_updated_flag(bios_enum.SmBiosUpdatedFlag.UPDATED_SMBIOS_AT_LEASE_ONCE)
        end
        skynet.fork(function()
            self:update_firmware_date(sys_id)
        end)
    end

    local bios_service = bios_factory.get_service('bios_service')
    if not bios_service then
        log:error("update_smbios_status: bios_service is nil!")
        return
    end

    bios_service:save_backup_bios_version(sys_id)
end

function smbios_service:update_firmware_date(sys_id)
    -- 触发BIOS版本日期更新
    smbios_handler = self.smbios_handler_collection[sys_id]
    local bios_service = bios_factory.get_service('bios_service')
    if smbios_handler and bios_service then
        -- 0：SMBiosInfoType.BIOSInformation
        smbios_handler:update(0)
        bios_service:update_firmware_date(sys_id, smbios_handler)
    end
end

function smbios_service:get_smbios_file_name(sys_id)
    local smbios_obj = self:get_smbios_obj(sys_id)
    if not smbios_obj then
        return
    end
    return smbios_obj:get_smbios_file_name()
end

function smbios_service:get_smbios_file_change(sys_id)
    local smbios_obj = self:get_smbios_obj(sys_id)
    if not smbios_obj then
        return
    end
    return smbios_obj:get_smbios_file_change()
end

function smbios_service:update_smbios_file_change(sys_id, file_change_tmp)
    local smbios_obj = self:get_smbios_obj(sys_id)
    if not smbios_obj then
        return
    end
    smbios_obj:update_smbios_file_change(file_change_tmp)
end

function smbios_service:get_data_operator(sys_id)
    if not self.data_operator_collection[sys_id] then
        self.data_operator_collection[sys_id] = {
            data_flag = BIOS_FLAG_IDLE,
            data_offset = 0x00,
            data_len = 0x00,
            data_buf = nil
        }
    end

    return self.data_operator_collection[sys_id]
end

function smbios_service:smbios_write_prepare(req, ctx)
    local sys_id = ctx.HostId or 1
    local manu_id = self:get_manu_id(sys_id)

    local bios_service = bios_factory.get_service('bios_service')
    if not bios_service then
        log:error("smbios_write_prepare: bios_service is nil!")
        local data = msg.WriteSmbiosDataRsp.new(bios_enum.SmbiosErrCode.SMBIOS_ERR_INVALID_FIELD:value(), manu_id, '')
        return data
    end

    self:update_smbios_status(bios_enum.SmbiosWriteStage.SMBIOS_WRITE_PREPARE, sys_id)
    bios_service:update_bios_startup_state(bios_enum.BiosStartupState.BIOS_STARTUP_SMBIOS_WRITE_PREPARE, sys_id)

    if not req then
        log:error("smbios_write_prepare:system %s request_msg is null!", sys_id)
        local data = msg.WriteSmbiosDataRsp.new(bios_enum.SmbiosErrCode.SMBIOS_ERR_INVALID_FIELD:value(), manu_id, '')
        return data
    end

    local data_operator = self:get_data_operator(sys_id)

    data_operator.data_flag = BIOS_FLAG_DOING
    data_operator.data_offset = 0

    data_operator.data_buf = ''

    local data = msg.WriteSmbiosDataRsp.new(bios_enum.SmbiosErrCode.SMBIOS_ERR_NORMALLY:value(), manu_id, '')

    return data
end

local function resolve_data(len, data)
    local bin_id = s_pack("I" .. len, data)
    local res = s_unpack(">I" .. len, bin_id)
    return res
end

function smbios_service:write_data(ctx, info)
    local sys_id = ctx.HostId or 1
    local manu_id = self:get_manu_id(sys_id)
    local data_operator = self:get_data_operator(sys_id)

    if data_operator.data_flag ~= BIOS_FLAG_DOING then
        log:error("smbios_write_data: system %s DataFlag:%u(ShouldBe:%u) is invalid!", sys_id,
            data_operator.data_flag, BIOS_FLAG_DOING)
        local data = msg.WriteSmbiosDataRsp.new(bios_enum.SmbiosErrCode.SMBIOS_ERR_INVALID_STATUS:value(), manu_id, '')
        return data
    end

    local smbios_offset = info.OffsetToWrite
    local offset = data_operator.data_offset
    if offset ~= smbios_offset then
        log:error("smbios_write_data: system %s DataOffset:%u(ShouldBe:%u) is invalid!", sys_id, smbios_offset, offset)
        local data = msg.WriteSmbiosDataRsp.new(bios_enum.SmbiosErrCode.SMBIOS_ERR_WRONG_OFFSET:value(),
            manu_id, tostring(resolve_data(4, offset)))
        return data
    end

    local data_real_len = #info.FileData
    if data_real_len == 0 or data_real_len > SMBIOS_MAXDATA_LEN or ((offset + data_real_len) > SMBIOS_DATA_SIZE) then
        log:error("smbios_write_data: system %s RealLen:%u(Max:%u,DataOffset:%u) is invalid!", sys_id,
            data_real_len, SMBIOS_MAXDATA_LEN, offset)
        local data = msg.WriteSmbiosDataRsp.new(bios_enum.SmbiosErrCode.SMBIOS_ERR_IV_LEN:value(), manu_id, '')
        return data
    end

    local file_data = info.FileData
    local chk_sum = bs_util.calc_checksum(file_data)
    local req_chk_sum = info.DataChecksum
    if req_chk_sum ~= chk_sum then
        log:error("smbios_write_data: system %s CheckSum(CalcSum:%d,RxSum:%d) is error!", sys_id, chk_sum, req_chk_sum)
        local data = msg.WriteSmbiosDataRsp.new(bios_enum.SmbiosErrCode.SMBIOS_ERR_INVALID_CHKSUM:value(), manu_id, '')
        return data
    end

    log:debug("Offset:%d,DataLen:%d!", offset, data_real_len)

    if not data_operator.data_buf then
        log:error("smbios_write_data: system %s The data_addr of GlobalVarible(g_smbios_wr_data_cfg) is null!", sys_id)
        local data = msg.WriteSmbiosDataRsp.new(bios_enum.SmbiosErrCode.SMBIOS_ERR_INVALID_FIELD:value(), manu_id, '')
        return data
    end

    data_operator.data_buf = data_operator.data_buf .. file_data

    data_operator.data_offset = data_operator.data_offset + data_real_len

    local data = msg.WriteSmbiosDataRsp.new(bios_enum.SmbiosErrCode.SMBIOS_ERR_NORMALLY:value(), manu_id, '')
    return data
end

local function get_smbios_tail(src_data)
    local smbios_tail = bs.new('<<DataChecksum, OffsetToWrite:4/unit:8, FileData/string>>')
    local info = smbios_tail:unpack(src_data)
    return info
end

function smbios_service:smbios_write_data(req, ctx)
    local sys_id = ctx.HostId or 1
    local manu_id = self:get_manu_id(sys_id)

    if not req then
        log:error("smbios_write_data: system %s Param pointer is null!", sys_id)
        local data = msg.WriteSmbiosDataRsp.new(bios_enum.SmbiosErrCode.SMBIOS_ERR_INVALID_FIELD:value(), manu_id, '')
        return data
    end

    local src_data = req.Srcdata
    if (not src_data) or (#src_data < 5) then
        log:error("smbios_write_data: system %s src_data is NULL!", sys_id)
        local data = msg.WriteSmbiosDataRsp.new(bios_enum.SmbiosErrCode.SMBIOS_ERR_INVALID_FIELD:value(), manu_id, '')
        return data
    end

    local info = get_smbios_tail(src_data)
    local offset = info.OffsetToWrite
    if offset == 0 then
        local bios_service = bios_factory.get_service('bios_service')
        if not bios_service then
            log:error("smbios_write_data: bios_service is nil!")
            local data = msg.WriteSmbiosDataRsp.new(bios_enum.SmbiosErrCode.SMBIOS_ERR_INVALID_FIELD:value(),
                manu_id, '')
            return data
        end

        self:update_smbios_status(bios_enum.SmbiosWriteStage.SMBIOS_WRITE_DATA, sys_id)
        bios_service:update_bios_startup_state(bios_enum.BiosStartupState.BIOS_STARTUP_SMBIOS_WRITE_DATA, sys_id)
    end

    return self:write_data(ctx, info)
end

function smbios_service:clear_data_operate_res(sys_id)
    local data_operator = self.data_operator_collection[sys_id]
    if not data_operator then
        log:error("smbios: system %s clear_data_operate_res: data_operate_addr is NULL!", sys_id)
        return
    end

    data_operator.data_flag = BIOS_FLAG_IDLE
    data_operator.data_len = 0
    data_operator.data_offset = 0

    if data_operator.data_buf then
        data_operator.data_buf = nil
    end
end

local function get_finish_tail(src_data)
    local smbios_tail = bs.new('<<DataChecksum, others/string>>')
    local info = smbios_tail:unpack(src_data)
    return info
end

function smbios_service:update_smbios_file(sys_id, data_buf)
    local manu_id = self:get_manu_id(sys_id)
    local file_name = self:get_smbios_file_name(sys_id)
    local file, err = file_sec.open_s(file_name, "w+b")
    if not file then
        log:error("update_smbios_file: system %s open smbios file fail!, err: %s", sys_id, err)
        self:clear_data_operate_res(sys_id)
        return msg.WriteSmbiosDataRsp.new(bios_enum.SmbiosErrCode.SMBIOS_ERR_FM_FAIL:value(), manu_id, '')
    end

    file:write(data_buf)
    file:flush()
    file:close()
    utils_core.chmod_s(file_name, utils.S_IRUSR | utils.S_IWUSR)

    log:notice("update_smbios_file: write file len(%s), system_id(%s)!", #data_buf, sys_id)

    local file_change = self:get_smbios_file_change(sys_id)
    if not file_change then
        log:error("update_smbios_file: system %s get_smbios_file_change fail!", sys_id)
        self:clear_data_operate_res(sys_id)
        return msg.WriteSmbiosDataRsp.new(bios_enum.SmbiosErrCode.SMBIOS_ERR_INVALID_STATUS:value(), manu_id, '')
    end

    local file_change_tmp
    if file_change == bios_enum.FileChange.SMBIOS_FILE_ALREADY_CHANGE then
        file_change_tmp = bios_enum.FileChange.SMBIOS_FILE_NO_CHANGE
    else
        file_change_tmp = bios_enum.FileChange.SMBIOS_FILE_ALREADY_CHANGE
    end

    self:update_smbios_file_change(sys_id, file_change_tmp)

    log:debug("update_smbios_file: recv smbios data from bios starting ok")
    self:clear_data_operate_res(sys_id)

    return msg.WriteSmbiosDataRsp.new(bios_enum.SmbiosErrCode.SMBIOS_ERR_NORMALLY:value(), manu_id, '')
end

function smbios_service:smbios_write_finish(req, ctx)
    local sys_id = ctx.HostId or 1
    local manu_id = self:get_manu_id(sys_id)

    local bios_service = bios_factory.get_service('bios_service')
    if not bios_service then
        log:error("[bios]smbios_write_finish: service is nil!")
        self:clear_data_operate_res(sys_id)
        local data = msg.WriteSmbiosDataRsp.new(bios_enum.SmbiosErrCode.SMBIOS_ERR_INVALID_FIELD:value(), manu_id, '')
        return data
    end

    local src_data = req.Srcdata
    if (not src_data) or (#src_data < 1) then
        log:error("smbios_write_finish: system %s src_data is NULL!", sys_id)
        self:clear_data_operate_res(sys_id)
        local data = msg.WriteSmbiosDataRsp.new(bios_enum.SmbiosErrCode.SMBIOS_ERR_INVALID_FIELD:value(), manu_id, '')
        return data
    end

    local info = get_finish_tail(src_data)

    local data_operator = self:get_data_operator(sys_id)
    local data_buf = data_operator.data_buf
    if not data_buf then
        log:error("smbios_write_finish: system %s data_buf is empty!", sys_id)
        local data = msg.WriteSmbiosDataRsp.new(bios_enum.SmbiosErrCode.SMBIOS_ERR_INVALID_FIELD:value(), manu_id, '')
        return data
    end

    local chk_sum = bs_util.calc_checksum(data_buf)
    local req_chk_sum = info.DataChecksum
    if req_chk_sum ~= chk_sum then
        log:error("smbios_write_finish: system %s CheckSum(CalcSum:%d,RxSum:%d) is error!",
            sys_id, chk_sum, req_chk_sum)
        self:clear_data_operate_res(sys_id)
        local data = msg.WriteSmbiosDataRsp.new(bios_enum.SmbiosErrCode.SMBIOS_ERR_INVALID_CHKSUM:value(), manu_id, '')
        return data
    end

    skynet.fork_once(function()
        skynet.sleep(10)
        pfr_service.get_instance():backup_hpm(sys_id)
        local upgrade_ser = bios_factory.get_service('upgrade_service')
        local snapshot = upgrade_ser:get_package_snapshot(sys_id)
        snapshot:set_firmware_effective_status(bios_firmware_enum.FirmwareEffectiveStatus.Effective) -- 生效完成
        pcall(function()
            local reset_lock_instance = reset_lock.get_instance()
            reset_lock_instance:unlock(sys_id)
        end)
        log:notice("smbios_write_finish: system %s set firmware effective status to Effective", sys_id)
    end)
    local res = self:update_smbios_file(sys_id, data_buf)
    self:update_smbios_status(bios_enum.SmbiosWriteStage.SMBIOS_WRITE_FINISH, sys_id)
    bios_service:update_bios_startup_state(bios_enum.BiosStartupState.BIOS_STARTUP_SMBIOS_WRITE_FINISH, sys_id)
    return res
end

function smbios_service:default_write(sys_id)
    local data = msg.WriteSmbiosDataRsp.new(bios_enum.SmbiosErrCode.SMBIOS_ERR_INVALID_STATUS:value(),
        self:get_manu_id(sys_id), '')
    return data
end

function smbios_service:write_smbios(req, ctx)
    local sys_id = ctx.HostId or 1
    local operation = req.Operation
    if operation == bios_enum.SmbiosWriteStage.SMBIOS_WRITE_PREPARE:value() then
        return self:smbios_write_prepare(req, ctx)
    elseif operation == bios_enum.SmbiosWriteStage.SMBIOS_WRITE_DATA:value() then
        return self:smbios_write_data(req, ctx)
    elseif operation == bios_enum.SmbiosWriteStage.SMBIOS_WRITE_FINISH:value() then
        return self:smbios_write_finish(req, ctx)
    else
        return self:default_write(sys_id)
    end
end

function smbios_service.judge_req(req, ctx, manu_id)
    if not req then
        log:error("smbios: Param pointer is null!")
        local data = msg.WriteSmbiosDataRsp.new(bios_enum.SmbiosErrCode.SMBIOS_ERR_INVALID_FIELD:value(), manu_id, '')
        return data
    end

    if not req.ManufactureId or not req.BiosId or not req.Operation then
        log:error("smbios: judge_req get_ipmi_field fail!")
        local data = msg.WriteSmbiosDataRsp.new(bios_enum.SmbiosErrCode.SMBIOS_ERR_IV_LEN:value(), manu_id, '')
        return data
    end

    return nil
end

function smbios_service.judge_manu_id_valid(req, manu_id)
    if not manu_id then
        log:error("smbios: judge_manu_id_valid manufacture_id is NULL!")
        return E_FAILED
    end

    if req.ManufactureId ~= manu_id then
        log:error("smbios: judge_manu_id_valid: manufacture_id:%u(ShouldBe:%u) is invalid!",
            req.ManufactureId, manu_id)
        return E_FAILED
    end

    return E_OK
end

function smbios_service.judge_bios_id(req, ctx, bios_id, manu_id, multihost)
    if multihost then
        return
    end
    local req_bios_id = req.BiosId
    if req_bios_id > bios_id then
        log:error("smbios: BiosId:%d(MaxId:%d) is invalid!", req_bios_id, bios_id)
        local data = msg.WriteSmbiosDataRsp.new(bios_enum.SmbiosErrCode.SMBIOS_ERR_INVALID_STATUS:value(), manu_id, '')
        return data
    end

    return nil
end

function smbios_service.validate(req, ctx, bios_id, manu_id, multihost)
    local data = smbios_service.judge_req(req, ctx, manu_id)
    if data then
        return data
    end

    data = smbios_service.judge_bios_id(req, ctx, bios_id, manu_id, multihost)
    if data then
        return data
    end

    local err_code = smbios_service.judge_manu_id_valid(req, manu_id)
    if err_code == E_FAILED then
        data = msg.WriteSmbiosDataRsp.new(bios_enum.SmbiosErrCode.SMBIOS_ERR_INVALID_CMD:value(), manu_id, '')
        return data
    end

    return nil
end

function smbios_service:write_smbios_data(req, ctx)
    local sys_id = ctx.HostId or 1
    local bios_id = self:get_bios_id(sys_id)
    local manu_id = self:get_manu_id(sys_id)

    local obj = self:get_smbios_obj(sys_id)
    if not req or not obj then
        log:error('smbios_service:write_smbios_data fail, system id(%s) invalid.', sys_id)
        return msg.WriteSmbiosDataRsp.new(bios_enum.SmbiosErrCode.SMBIOS_ERR_INVALID_FIELD:value(), manu_id, '')
    end

    local data = smbios_service.validate(req, ctx, bios_id, manu_id, self.multihost)
    if data then
        return data
    end

    return self:write_smbios(req, ctx)
end

-- 响应获取smbios信息的ipmi的返回值
local function response_get_smbios_info(complete_code, manu_id, data)
    local format = '<<InformationLength, Information/string>>'
    local bin_data = ''
    if data ~= '' then
        bin_data = bs_util.struct_to_binary(data, format)
    end

    return msg.GetSmBiosInfoRsp.new(complete_code, manu_id, bin_data)
end

function smbios_service.check_get_smbios_info_parameters(req, ctx, bios_id, manu_id, multihost)
    if not req or not req.ManufactureId or not req.BiosId or not req.InformationType then
        log:error("smbios: invalid ipmi field")
        return comp_code.InvalidCommand
    end

    if not multihost and req.BiosId > bios_id then
        log:error("smbios: BiosId:%d(MaxId:%d) is invalid!", req.BiosId, bios_id)
        return comp_code.ParmOutOfRange
    end

    if req.ManufactureId ~= manu_id then
        log:error("smbios: manufacture_id:%u(Should Be:%u) is invalid!", req.ManufactureId, manu_id)
        return comp_code.InvalidFieldRequest
    end

    if req.InformationType < 1 or req.InformationType > #smbios_prop then
        log:error("smbios: InformationType:%u is invalid!", req.InformationType)
        return comp_code.ParmOutOfRange
    end

    return comp_code.Success
end

function smbios_service:get_smbios_info(req, ctx)
    local sys_id = ctx.HostId or 1
    local bios_id = self:get_bios_id(sys_id)
    local manu_id = self:get_manu_id(sys_id)

    local obj = self:get_smbios_obj(sys_id)
    if not req or not obj then
        log:error('smbios_service:get_smbios_info fail, system id(%s) invalid.', sys_id)
        return response_get_smbios_info(comp_code.ParmOutOfRange, manu_id, '')
    end

    local err_code = smbios_service.check_get_smbios_info_parameters(req, ctx, bios_id, manu_id, self.multihost)
    if err_code ~= comp_code.Success then
        return response_get_smbios_info(err_code, manu_id, '')
    end

    local resp_data = {}
    resp_data.Information = obj:get_prop(smbios_prop[req.InformationType])
    if resp_data.Information and type(resp_data.Information) == "string" then
        resp_data.InformationLength = #resp_data.Information
    end
    return response_get_smbios_info(comp_code.Success, manu_id, resp_data)
end

function smbios_service.check_set_smbios_info_parameters(req, ctx, bios_id, manu_id, multihost)
    if not req or not req.ManufactureId or not req.BiosId or not req.InformationType or
        not req.InformationLength or not req.Information then
        log:error("smbios: invalid ipmi field")
        ipmi.ipmi_operation_log(ctx, 'BIOS', "Set smbios info failed")
        return comp_code.InvalidCommand
    end

    if req.ManufactureId ~= manu_id then
        log:error("smbios: manufacture_id:%u(Should Be:%u) is invalid!", req.ManufactureId, manu_id)
        ipmi.ipmi_operation_log(ctx, 'BIOS', "Set smbios info failed")
        return comp_code.InvalidFieldRequest
    end

    if req.InformationType < 1 or req.InformationType > #smbios_prop then
        log:error("smbios: InformationType:%u is invalid!", req.InformationType)
        ipmi.ipmi_operation_log(ctx, 'BIOS', "Set smbios info failed")
        return comp_code.ParmOutOfRange
    end

    if req.InformationLength > 48 or req.InformationLength ~= #req.Information then
        log:error("smbios: Information length error, InformationLen = %u, actual Information Len = %d",
            req.InformationLength, #req.Information)
        ipmi.ipmi_operation_log(ctx, 'BIOS', "Set smbios %s failed", string.lower(smbios_prop[req.InformationType]))
        return comp_code.ParmOutOfRange
    end

    if not multihost and req.BiosId > bios_id then
        log:error("smbios: BiosId:%d(MaxId:%d) is invalid!", req.BiosId, bios_id)
        ipmi.ipmi_operation_log(ctx, 'BIOS', "Set smbios %s failed", string.lower(smbios_prop[req.InformationType]))
        return comp_code.ParmOutOfRange
    end

    return comp_code.Success
end

function smbios_service:set_smbios_info(req, ctx)
    local sys_id = ctx.HostId or 1
    local bios_id = self:get_bios_id(sys_id)
    local manu_id = self:get_manu_id(sys_id)

    local obj = self:get_smbios_obj(sys_id)
    if not req or not obj then
        log:error('smbios_service:set_smbios_info fail, system id(%s) invalid.', sys_id)
        return msg.SetSmBiosInfoRsp.new(comp_code.ParmOutOfRange, manu_id)
    end

    local err_code = smbios_service.check_set_smbios_info_parameters(req, ctx, bios_id, manu_id, self.multihost)
    if err_code ~= comp_code.Success then
        return msg.SetSmBiosInfoRsp.new(err_code, manu_id)
    end

    obj:set_prop(smbios_prop[req.InformationType], req.Information)
    ipmi.ipmi_operation_log(ctx, 'BIOS', "Set smbios %s(%s) successfully",
        string.lower(smbios_prop[req.InformationType]), req.Information)
    return msg.SetSmBiosInfoRsp.new(comp_code.Success, manu_id)
end

function smbios_service:set_smbios_version(ctx, version, sys_id)
    self:deal_smbios_info(ctx, "Version", version, "version", sys_id)
end

function smbios_service:set_smbios_skunumber(ctx, skunumber, sys_id)
    self:deal_smbios_info(ctx, "SKUNumber", skunumber, "skunumber", sys_id)
end

function smbios_service:set_smbios_family(ctx, family, sys_id)
    self:deal_smbios_info(ctx, "Family", family, "family", sys_id)
end

function smbios_service:deal_smbios_info(ctx, prop_name, prop_val, log_name, sys_id)
    if prop_val == nil or type(prop_val) ~= "string" then
        log:error('[bios] import bios config: smbios %s invalid', log_name)
        bs_util.record_operation(ctx, sys_id, string.format("Set smbios %s failed", log_name))
        error(base_messages.PropertyValueFormatError(prop_name, prop_val))
    end
    if #prop_val > 48 then
        log:error('[bios] import bios config: smbios %s len out of range', log_name)
        bs_util.record_operation(ctx, sys_id, string.format("Set smbios %s failed", log_name))
        error(base_messages.InternalError)
    end
    self:set_smbios_prop(prop_name, prop_val, sys_id)
    bs_util.record_operation(ctx, sys_id, string.format("Set smbios %s(%s) successfully", log_name, prop_val))
end

function smbios_service:get_obj(sys_id)
    local smbios_obj = self:get_smbios_obj(sys_id)
    if not smbios_obj then
        return
    end
    return smbios_obj.smbios_info
end

function smbios_service:collect_json_file(path, system_id)
    local obj = self:get_smbios_obj(system_id)
    if obj then
        obj:collect_json_file(path)
    end

end

function smbios_service:get_dump_info(system_id)
    local obj = self:get_smbios_obj(system_id)
    if obj then
        return obj:get_dump_info()
    end
end

local prop_map = {
    ['Version'] = smbios_service.set_smbios_version,
    ['SKUNumber'] = smbios_service.set_smbios_skunumber,
    ['Family'] = smbios_service.set_smbios_family
}

function smbios_service:import(ctx, obj, system_id)
    if not self:get_smbios_obj(system_id) then
        bs_util.record_operation(ctx, system_id, "Failed to set Smbios Info")
        return
    end
    import_export_eng.import_sevice_cfg(ctx, obj, self, "SmBios", prop_map, system_id)
end

return singleton(smbios_service)