-- 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 skynet = require 'skynet'
local c_object = require 'mc.orm.object'
local mc_utils = require 'mc.utils'
local singleton = require 'mc.singleton'
local log = require 'mc.logging'
local signal = require "mc.signal"
local file_sec = require 'utils.file'
local utils_core = require 'utils.core'
local vos = require 'utils.vos'
local chassis_def = require 'def.chassis_def'
local ipmi_msg = require 'chassis.ipmi.ipmi_message'
local ipmi = require 'ipmi'
local cc = ipmi.types.Cc
local custom_messages = require 'messages.custom'
local ipmi_enums = require "ipmi.enums"
local factory = require 'factory'
local tb_tool = require 'common_tools.table_tool'
local led_object = require 'led_object'
local uid_state = chassis_def.UIDLEDSTATE
local uid_mode = chassis_def.UIDLEDMODE
local reg_value = chassis_def.REGISTERVAlUE
local led_color = chassis_def.LEDCOLOR

local lamptest_task_count = 0
-- 健康灯状态：慢闪对应寄存器值5，快闪对应寄存器值7，常亮对应寄存器255
local SLOW_FLASHING<const> = 5
local FAST_FLASHING<const> = 7
local STEADY_ON<const> = 255
local CHASSIS_NAME<const> = 'Chassis'
local IDENTIFY_DEFAULT_TIMEOUT<const> = 15
local LED_ID<const> = {
    HEALTH = 2, -- 健康灯
    UID = 4 -- UID灯
}

local led_service = {}
led_service.__index = led_service

function led_service.new(bus)
    return setmetatable({
        uidled_service_collection = {},
        uid_obj = nil, -- UID灯对象
        sys_obj = nil, -- 健康灯对象
        identify_interval = 0,
        identify_mode = 0,
        last_countdown = 0,
        sys_obj_ready = signal.new(),
        bus = bus
    }, led_service)
end

function led_service:add_object(obj)
    local led_obj = led_object.new_for_csr(obj)
    table.insert(self.uidled_service_collection, led_obj)
    log:notice('[chassis]led_obj_id:%s', obj.Id)
    if obj.Id == LED_ID.UID then
        self.uid_obj = led_obj
    elseif obj.Id == LED_ID.HEALTH then
        self.sys_obj = led_obj
        self.sys_obj_ready:emit()
        log:notice('sys_obj_ready_emit')
    end
end

function led_service:get_led_obj()
    return self.uid_obj
end

function led_service:record_led_info(fp)
    local content = "********** PME LED Information **********"
    for _, led in pairs(self.uidled_service_collection) do
        local led_obj = led.uidled_info
        content = string.format("%s\r\nLED Name               : %s\r\n", content, led_obj.Name)
        local mode_str = chassis_def.LED_MODE_STR[led_obj.Mode]
        mode_str = (not mode_str and "unknown" or mode_str)
        content = string.format('%sLED Mode               : %s\r\n', content, mode_str)
        if led_obj.State == chassis_def.UIDLEDSTATE.LED_OFF then
            content = content .. 'LED State              : OFF\r\n'
        elseif led_obj.State == chassis_def.UIDLEDSTATE.LED_ON then
            content = content .. 'LED State              : ON\r\n'
        else
            content = content .. 'LED State              : BLINKING\r\n'
            content = string.format('%sOff Duration           : %d ms\r\n',
                content, led_obj.OffDuration * 10)
            content = string.format('%sOn Duration            : %d ms\r\n',
                content, led_obj.OnDuration * 10)
        end
        local color_str = chassis_def.LED_COLOR_STR[led_obj.Capability]
        color_str = color_str or 'reserved'
        content = string.format('%sLED Color              : %s\r\n', content, color_str)
        local capa_str = ''
        for i = 0, tb_tool.get_nums(chassis_def.LED_COLOR_STR) - 1 do
            if led_obj.ColorCapabilities & (1 << i) ~= 0 then
                capa_str = capa_str .. chassis_def.LED_COLOR_STR[i] .. ' '
            end
        end
        content = string.format('%sLED Color Capabilities : %s\r\n', content, capa_str)
        local default_lcs_color = chassis_def.LED_COLOR_STR[led_obj.DefaultLCSColor] or 'reserved'
        content = string.format('%sDefault LED Color in\r\n' ..
            '      LOCAL control    : %s\r\n', content, default_lcs_color)
        local default_os_color = chassis_def.LED_COLOR_STR[led_obj.DefaultOSColor] or 'reserved'
        content = string.format('%s      OVERRIDE state   : %s\r\n', content, default_os_color)
    end
    fp:write(content)
end

function led_service:led_dump(ctx, path)
    if not vos.get_file_accessible(path) then
        mc_utils.mkdir_with_parents(path, mc_utils.S_IRWXU | mc_utils.S_IRGRP | mc_utils.S_IXGRP)
    end
    local info_path = path .. '/LedInfo'
    local fp, err = file_sec.open_s(info_path, 'w+')
    if not fp then
        log:error("open file failed, err: %s", err)
        return
    end
    mc_utils.safe_close_file(fp, function()
        utils_core.chmod_s(info_path, mc_utils.S_IRUSR | mc_utils.S_IWUSR | mc_utils.S_IRGRP)
        local ok, ret = pcall(function()
            self:record_led_info(fp)
        end)
        if not ok then
            log:error('led_dump failed, err: %s', ret)
        end
    end)
end

function led_service:ipmi_get_led_color_capabilities(req, ctx)
    local rsp = ipmi_msg.GetLedColorCapabilitiesRsp.new(cc.Success, 0, 0, 0, 0)
    for _, led in pairs(self.uidled_service_collection) do
        local led_obj = led.uidled_info
        if led_obj.FruId == req.FruId and led_obj.Id == req.LedId then
            rsp.ColorCapabilities = led_obj.ColorCapabilities
            rsp.DefaultLCSColor = led_obj.DefaultLCSColor
            rsp.DefaultOSColor = led_obj.DefaultOSColor
            return rsp
        end
    end
    rsp.CompletionCode = cc.ParmOutOfRange
    return rsp
end

function led_service:set_uid_led_identify(obj, ctx, state, interval)
    -- 只有uid灯才可以设置identify
    if obj.Id ~= self.uid_obj.uidled_info.Id or obj.FruId ~= self.uid_obj.uidled_info.FruId then
        log:error("Led obj(Id: %s, FruId: %s) cannot set identify", obj.Id, obj.FruId)
        return
    end
    local on_duration
    local off_duration
    if state == uid_state.LED_ON and interval ~= 0  then
        error(custom_messages.UnrequiredProperty('Chassis.ControlIndicatorLED'))
    end
    -- 用户登录web,kvm,ssh时uid闪烁
    if ctx.Requestor == 'bmc.kepler.iam' then
        local mode_map = {
            [uid_state.LED_BLINK] = uid_mode.LED_MODE_BLINK,
            [uid_state.LED_OFF] = uid_mode.LED_MODE_OFF
        }
        if mode_map[state] then
            self.identify_mode = mode_map[state]
            self.identify_interval = 0
            on_duration, off_duration = self.uid_obj:update_led_info(state, chassis_def.UID_CONTROL_RIGHT.BLINK_BY_REMOTE_LOGIN)
        end
        return state, on_duration, off_duration
    end
    if state ~= uid_state.LED_OFF and interval ~= 0 then -- 闪烁
        self.identify_mode = uid_mode.LED_MODE_BLINK
        self.identify_interval = interval * 10
        self:start_countdown()
        state = uid_state.LED_BLINK
        on_duration, off_duration = self:set_led(state)
        if on_duration and off_duration then
            log:operation(ctx:get_initiator(), 'Chassis',
                'Set UID identify blink (%d) seconds successfully', interval)
        end
    elseif state == uid_state.LED_OFF then -- 关闭
        self.identify_mode = uid_mode.LED_MODE_OFF
        self.identify_interval = 0
        on_duration, off_duration = self:set_led(state)
        if on_duration and off_duration then
            log:operation(ctx:get_initiator(), 'Chassis', 'Set UID identify off successfully')
        end
    elseif state == uid_state.LED_ON then -- 点亮
        self.identify_mode = uid_mode.LED_MODE_ON
        self.identify_interval = 0
        on_duration, off_duration = self:set_led(state)
        if on_duration and off_duration then
            log:operation(ctx:get_initiator(), 'Chassis', 'Set UID identify force on successfully')
        end
    else
        log:info('Set UID identify failed, please check value of %d and %d', state, interval)
    end

    if not on_duration or not off_duration then
        log:operation(ctx:get_initiator(), 'Chassis', 'Set UID identify failed')
        error(custom_messages.OperationFailed())
    end

    return state, on_duration, off_duration
end

-- 可扩展
function led_service:set_led(state)
    return self.uid_obj:update_led_info(state)
end

-- 0: 默认         UNKNOWN
-- 1: AC/整机上电  HARD_RESET
-- 2: BMC系统复位  SOFT_RESET
-- 3: 进程重启     SOFT_RESET
function led_service:get_hard_reset(db)
    local chassis_db_obj = db.ChassisTable({Id = 'ChassisInfo'})
    local hard_reset = true
    local reset_flag = chassis_db_obj.BmcHardResetFlag
    log:notice('[chassis]get reset flag:%s', reset_flag)
    if reset_flag ~= 0 then
        hard_reset = false
    else
        hard_reset = true
        -- 刷新数据库值
        chassis_db_obj.BmcHardResetFlag = 1
        chassis_db_obj:save()
    end

    return hard_reset
end

function led_service:wait_hw_finish()
    pcall(function()
        for _ = 1, 5 do
            self.uid_obj:update_led_info(uid_state.LED_OFF)
            skynet.sleep(5)
        end
    end)
end

function led_service:led_start_up_test(db)
    if not self:get_hard_reset(db) then
        log:notice('[chassis] no need test led')
        return
    end
    log:notice('[chassis] start wait hwproxy finish')
    self:wait_hw_finish()
    log:notice('[chassis] start test led')
    for _ = 1, 10 do
        pcall(function()
            self.uid_obj:update_led_info(uid_state.LED_ON)
        end)
        skynet.sleep(30)
        pcall(function()
            self.uid_obj:update_led_info(uid_state.LED_OFF)
        end)
        skynet.sleep(30)
    end
    log:notice('[chassis] finish test led')
end

-- 获取进程重启标记
local function get_process_restart_flag(db)
    local obj = db.ChassisTable({Id = 'ChassisInfo'})
    log:notice("get process restart flag: %s", obj.ProcessRestartFlag)
    return obj.ProcessRestartFlag
end

-- 设置进程重启标记
local function set_process_start_flag(db, flag)
    local obj = db.ChassisTable({Id = 'ChassisInfo'})
    obj.ProcessRestartFlag = flag
    obj:save()
end

function led_service:recover_led_state(led_ctrl_value)
    log:notice("get led_ctrl_value %s when led recover", led_ctrl_value)
    if led_ctrl_value == reg_value.UID_BLINK or led_ctrl_value == reg_value.UID_OFF then
        self.uid_obj:update_led_info(uid_state.LED_OFF)
        return
    end

    if led_ctrl_value == reg_value.UID_ON then
        self.uid_obj:update_led_info(uid_state.LED_ON)
    end
end

function led_service:flash_by_times(times)
    log:notice("[chassis] UID start flash %d times", times)
    local led_ctrl_value = self.uid_obj:get_led_ctrl_value()
    self.uid_obj:update_led_info(uid_state.LED_BLINK)
     -- 一次一秒
    skynet.sleep(times * 100)
    -- 恢复led灯状态
    self:recover_led_state(led_ctrl_value)
end

-- BMC启动时uid闪烁三次
function led_service:bmc_reset_uid_flash(db)
    if get_process_restart_flag(db) ~= 0 then
        log:notice("[chassis] process restart uid not flash.")
        return
    end

    self:flash_by_times(3)
    set_process_start_flag(db, 1)
end

-- 重启后，读取state的值，如果为闪烁状态则关闭UID灯
function led_service:update_led_info()
    local led_ctrl_value = self.uid_obj:get_led_ctrl_value()
    log:notice("get led_ctrl_value %s when led update", led_ctrl_value)
    if led_ctrl_value == reg_value.UID_BLINK then
        self.uid_obj:update_led_info(uid_state.LED_OFF)
    elseif led_ctrl_value == reg_value.UID_ON then
        self.uid_obj:update_led_info(uid_state.LED_ON)
    end
end

function led_service:start_countdown()
    -- 监听闪烁时间的变化
    self.last_countdown_task = skynet.fork_once(function()
        skynet.sleep(self.identify_interval * 10) -- 10 倒计时时间

        -- 如果有更新的countdown任务，解除当前任务
        if self.last_countdown_task ~= coroutine.running() then
            return
        end

        -- uid灯为闪烁状态
        if self.identify_mode == uid_mode.LED_MODE_BLINK then
            self.uid_obj:update_led_info(uid_state.LED_OFF)
        end
    end)
end

local function get_state_by_interval(interval, ctx)
    local state, mode
    if interval == 0 then
        state = uid_state.LED_OFF
        mode = uid_mode.LED_MODE_OFF
        ipmi.ipmi_operation_log(ctx, CHASSIS_NAME, 'Set UID identify (off) successfully')
    else
        state = uid_state.LED_BLINK
        mode = uid_mode.LED_MODE_BLINK
        ipmi.ipmi_operation_log(ctx, CHASSIS_NAME,
            'Set UID identify blink (%d) seconds successfully', interval)
    end
    return state, mode, interval
end

-- ipmi命令：机框识别
function led_service:ipmi_chassis_identify(req, ctx)
    local resp = ipmi_msg.ChassisIdentifyRsp
    if not self.uid_obj then
        ipmi.ipmi_operation_log(ctx, CHASSIS_NAME, 'Set UID identify failed')
        return resp.new(ipmi.types.Cc.UnspecifiedError)
    end
    local state, mode
    local interval = IDENTIFY_DEFAULT_TIMEOUT
    local req_len = #req.SrcData
    if req_len == 0 then
        state = uid_state.LED_BLINK
        mode = uid_mode.LED_MODE_BLINK
        ipmi.ipmi_operation_log(ctx, CHASSIS_NAME,
            'Set UID identify blink (15) seconds successfully')
    -- 第1个字节表示点亮的时间(单位:s)
    elseif req_len == 1 then
        local interval_req = string.unpack('B', req.SrcData)
        state, mode, interval = get_state_by_interval(interval_req, ctx)
    -- 第2个字节表示强制点亮
    elseif req_len == 2 then
        local interval_req, force = string.unpack('BB', req.SrcData)
        if force > 0 then
            state = uid_state.LED_ON
            mode = uid_mode.LED_MODE_ON
            ipmi.ipmi_operation_log(ctx, CHASSIS_NAME, 'Set UID identify (force on) successfully')
        else
            state, mode, interval = get_state_by_interval(interval_req, ctx)
        end
    else
        ipmi.ipmi_operation_log(ctx, CHASSIS_NAME, 'Set UID identify failed')
        return resp.new(ipmi.types.Cc.ReqDataLenInvalid)
    end

    self.identify_mode = mode
    self.identify_interval = interval * 10
    self.uid_obj:update_led_info(state)
    self:start_countdown()

    return resp.new(ipmi.types.Cc.Success)
end

function led_service:get_led_policy()
    local policy_obj = c_object('LedPolicy').collection:find({Id = 1})
    if type(policy_obj) ~= 'table' or policy_obj.Normal == nil or policy_obj.Minor == nil or
        policy_obj.Major == nil or policy_obj.Critical == nil then
        log:error('Get led policy objects failed')
        -- 亮灯策略获取失败则按照默认策略亮灯
        return {
            ['Normal'] = {led_color.GREEN, STEADY_ON},
            ['Minor'] = {led_color.GREEN, STEADY_ON},
            ['Major'] = {led_color.RED, SLOW_FLASHING},
            ['Critical'] = {led_color.RED, FAST_FLASHING}
        }
    end

    return {
        ['Normal'] = {policy_obj.Normal[1] or led_color.GREEN, policy_obj.Normal[2] or STEADY_ON},
        ['Minor'] = {policy_obj.Minor[1] or led_color.GREEN, policy_obj.Minor[2] or STEADY_ON},
        ['Major'] = {policy_obj.Major[1] or led_color.RED, policy_obj.Major[2] or SLOW_FLASHING},
        ['Critical'] = {policy_obj.Critical[1] or led_color.RED, policy_obj.Critical[2] or FAST_FLASHING}
    }
end

function led_service:set_led_by_sys_health(sys_health)
    local led_policy = self:get_led_policy()
    local state = led_policy[sys_health][2]
    local used_color = led_policy[sys_health][1]

    self.sys_obj:update_led_info(state)
    self.sys_obj:set_capability(used_color)
    log:notice("set SysHealLed state success, current state: %d, Capability: %d", state, used_color)
end

local SMM_CHASSIS_TYPE <const> = 0x05
local PICMG_IDENTIFIER <const> = 0x00
local ALL_LEDS <const> = 0xff

local function check_color_efficiency(led_obj, color)
    if color == led_color.NOT_CHANGE or color == led_color.DEFAULT then
        return true
    end
    if color > led_color.WHITE then
        return false
    end
    if (led_obj.ColorCapabilities & (1 << color)) ~= 0 then
        return true
    else
        log:error('color not supported, ColorCapabilities: %d, color: %d', led_obj.ColorCapabilities,
            color)
        return false
    end
end

-- 释放Led控制
local function led_release(led)
    local led_obj = led.uidled_info
    if led_obj.Mode == chassis_def.LED_CONTROL_MODE.OS then
        led_obj.Mode = chassis_def.LED_CONTROL_MODE.LCS
        led_obj.Capability = led_obj.LCSColor
        led:update_led_info(led_obj.LCSState)
    end
    if led.background_mode == chassis_def.LED_CONTROL_MODE.OS then
        led.background_mode = chassis_def.LED_CONTROL_MODE.LCS
    end
end

-- 恢复到后台模式
local function recover_to_background_mode(led)
    local led_obj = led.uidled_info
    local color
    local state
    if led.background_mode == chassis_def.LED_CONTROL_MODE.OS then
        color = led_obj.OSColor
        state = led_obj.OSState
    elseif led.background_mode == chassis_def.LED_CONTROL_MODE.LCS then
        color = led_obj.LCSColor
        state = led_obj.LCSState
    else
        return
    end
    led_obj.Mode = led.background_mode
    led_obj.Capability = color
    led:update_led_info(state)
    led.background_mode = chassis_def.LED_CONTROL_MODE.LCS
end

local function set_led_lamptest(led, color, interval)
    local led_obj = led.uidled_info
    if not check_color_efficiency(led_obj, color) or
        interval <= 0 or interval > 127 then
        return chassis_def.RET.ERR
    end
    skynet.fork(function ()
        if led_obj.Mode <= chassis_def.LED_CONTROL_MODE.LAMPTEST then
            if led_obj.Mode ~= chassis_def.LED_CONTROL_MODE.LAMPTEST then
                led.background_mode = led_obj.Mode
                led_obj.Mode = chassis_def.LED_CONTROL_MODE.LAMPTEST
            end
            if color == led_color.DEFAULT then
                color = led_obj.DefaultOSColor
            end
            if color ~= led_color.NOT_CHANGE then
                led_obj.LampTestColor = color
            end
            led_obj.Capability = color
            led:update_led_info(uid_state.LED_ON)
            led_obj.LampTestDuration = interval
            lamptest_task_count = lamptest_task_count + 1
            local cur_task_index = lamptest_task_count
            for _ = 1, interval do
                if cur_task_index ~= lamptest_task_count then
                    return
                end
                skynet.sleep(10)
            end
            if cur_task_index == lamptest_task_count then
                recover_to_background_mode(led)
            end
        end
    end)
    return chassis_def.RET.OK
end

-- 设置超驰状态
local function led_over_state_set(led, state, os_color)
    local color = os_color
    local led_obj = led.uidled_info
    if state ~= uid_state.LED_OFF and
        not check_color_efficiency(led.uidled_info, color) then
        return
    end
    if color == led_color.DEFAULT then
        color = led_obj.DefaultOSColor
    end
    if color ~= led_color.NOT_CHANGE then
        led_obj.OSColor = color
    end
    led_obj.OSState = state
    if led_obj.Mode <= chassis_def.LED_CONTROL_MODE.OS then
        if led_obj.Mode ~= chassis_def.LED_CONTROL_MODE.OS then
            led.background_mode = led_obj.Mode
            led_obj.Mode = chassis_def.LED_CONTROL_MODE.OS
        end
        led_obj.Capability = color
        led:update_led_info(state)
    elseif led.background_mode <= chassis_def.LED_CONTROL_MODE.OS then
        led.background_mode = chassis_def.LED_CONTROL_MODE.OS
    end
end

local function set_led_by_ipmi(ctx, led, req)
    local led_obj = led.uidled_info
    if not check_color_efficiency(led_obj, req.Color) then
        return chassis_def.RET.ERR
    end
    local target_color = req.Color
    if req.Color == led_color.NOT_CHANGE then
        target_color = led_obj.Capability
    end
    if req.Color == led_color.DEFAULT then
        target_color = led_obj.DefaultLCSColor
    end
    if target_color > led_color.WHITE then
        log:error('led color out of range, color: %d', target_color)
        return chassis_def.RET.ERR
    end
    if req.LedFunc == chassis_def.LED_FUN.LCS then
        ipmi.ipmi_operation_log(ctx, CHASSIS_NAME, 'Set %s to (local control state) successfully',
            led_obj.Name)
        led_release(led)
    elseif req.LedFunc == chassis_def.LED_FUN.OS_OFF then
        ipmi.ipmi_operation_log(ctx, CHASSIS_NAME, 'Set %s to (overstate off) successfully',
            led_obj.Name)
        led_over_state_set(led, uid_state.LED_OFF, target_color)
    elseif req.LedFunc == chassis_def.LED_FUN.OS_ON then
        ipmi.ipmi_operation_log(ctx, CHASSIS_NAME, 'Set %s to (overstate on) color (%s) successfully',
            led_obj.Name, chassis_def.LED_COLOR_STR[target_color])
        led_over_state_set(led, uid_state.LED_ON, target_color)
    elseif req.LedFunc <= 0xfa and req.LedFunc >= 0x01 then
        ipmi.ipmi_operation_log(ctx, CHASSIS_NAME, 'Set %s to (overstate blink) color (%s) successfully',
            led_obj.Name, chassis_def.LED_COLOR_STR[target_color])
        led_over_state_set(led, uid_state.LED_BLINK, target_color)
    elseif req.LedFunc == chassis_def.LED_FUN.LAMPTEST then
        local interval = req.OnDuration
        if set_led_lamptest(led, target_color, interval) ~= chassis_def.RET.OK then
            return chassis_def.RET.ERR
        end
        ipmi.ipmi_operation_log(ctx, CHASSIS_NAME, 'Set %s lamptest to color (%s) (%s) seconds successfully',
            led_obj.Name, chassis_def.LED_COLOR_STR[target_color], interval / 10)
    end
    return chassis_def.RET.OK
end

local function set_single_led_state(led_collection, req, ctx, rsp)
    for _, led in pairs(led_collection) do
        if req.FruId == led.uidled_info.FruId and req.LedId == led.uidled_info.Id then
            if set_led_by_ipmi(ctx, led, req) ~= chassis_def.RET.OK then
                break
            else
                ipmi.ipmi_operation_log(
                    ctx, CHASSIS_NAME, "Set FRU%d led (%d) state successfully", req.FruId, req.LedId)
                return rsp
            end
        end
    end
    ipmi.ipmi_operation_log(ctx, CHASSIS_NAME, "Set FRU%d led (%d) state failed", req.FruId, req.LedId)
    rsp.CompletionCode = ipmi.types.Cc.InvalidFieldRequest
    return rsp
end

local function set_all_led_state(led_collection, req, ctx, rsp)
    for _, led in pairs(led_collection) do
        if led.uidled_info.FruId == req.FruId and not check_color_efficiency(led.uidled_info, req.Color) then
            ipmi.ipmi_operation_log(ctx, CHASSIS_NAME, "Set FRU%d all leds state failed", req.FruId)
            rsp.CompletionCode = ipmi.types.Cc.InvalidFieldRequest
            return rsp
        end
    end
    for _, led in pairs(led_collection) do
        if led.uidled_info.FruId == req.FruId and set_led_by_ipmi(ctx, led, req) ~= chassis_def.RET.OK then
            ipmi.ipmi_operation_log(ctx, CHASSIS_NAME, "Set FRU%d all leds state failed", req.FruId)
            rsp.CompletionCode = ipmi.types.Cc.InvalidFieldRequest
            return rsp
        end
    end
    ipmi.ipmi_operation_log(ctx, CHASSIS_NAME, "Set FRU%d led (%d) state successfully", req.FruId, req.LedId)
    return rsp
end

function led_service:ipmi_set_fru_led_state(req, ctx)
    local rsp = ipmi_msg.SetFruLedStateRsp.new(ipmi.types.Cc.Success, PICMG_IDENTIFIER)
    if not next(self.uidled_service_collection) then
        log:error("get Led objects failed.")
        ipmi.ipmi_operation_log(ctx, CHASSIS_NAME, 'Set led state failed')
        rsp.CompletionCode = ipmi.types.Cc.DataNotAvailable
        return rsp
    end
    if not next(factory.chassis_service.chassis_collection) then
        log:error("get chassis object failed.")
        ipmi.ipmi_operation_log(ctx, CHASSIS_NAME, 'Set led state failed')
        rsp.CompletionCode = ipmi.types.Cc.DataNotAvailable
        return rsp
    end
    if factory.chassis_service.chassis_collection[1].ChassisType == SMM_CHASSIS_TYPE and
        ctx.chan_num == ipmi_enums.ChannelId.CT_IPMB_ETH then
        log:maintenance(log.MLOG_INFO, log.FC__PUBLIC_OK, "Set FRU%d led (%d) state by BMC IPMI msg is not support",
            req.FruId, req.LedId)
        return rsp
    end
    if req.LedId == ALL_LEDS then
        return set_all_led_state(self.uidled_service_collection, req, ctx, rsp)
    end
    return set_single_led_state(self.uidled_service_collection, req, ctx, rsp)
end

return singleton(led_service)
