-- 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 log = require 'mc.logging'
local chassis_def = require 'def.chassis_def'
local chassis_object = require 'chassis_object'
local uid_state = chassis_def.UIDLEDSTATE
local initiator = require 'mc.initiator'
local dbus = require 'sd_bus.org_freedesktop_dbus'
local mdb = require 'mc.mdb'
local skynet_queue = require 'skynet.queue'
local tasks = require "mc.orm.tasks"
local signal = require "mc.signal"
local msg = require 'chassis.ipmi.ipmi_message'
local ipmi = require 'ipmi'
local cc = ipmi.types.Cc
local ctx = require 'mc.context'
local s_pack = string.pack
local factory = require 'factory'
local client = require 'chassis.client'
local cjson = require 'cjson'
local common_utils = require 'common_tools.common_utils'

local match_rule = dbus.MatchRule
local add = dbus.ObjMgrInterfacesAdded
local del = dbus.ObjMgrInterfacesRemoved
local CHASSIS_COVER = chassis_def.COVER_CODE
local UPDATE_DEVICE_MAX_CNT_DELAY = 3000

local chassis_service = {}
chassis_service.__index = chassis_service

function chassis_service.new(bus, led_service, db)
    local self = setmetatable({
        chassis_collection = {},
        bus = bus,
        led_service = led_service,
        led_object = {},
        queue = skynet_queue(),
        uid_btn_sig = signal.new()
    }, chassis_service)
    self.db = db
    self:init()
    return self
end

function chassis_service:init()
    self.uid_btn_sig:on(function(is_push)
        if is_push then
            local chassis_obj = self.chassis_collection[1]
            chassis_obj:set_prop('UidButtonPressed', 1)
            self:dft_test_uid()
            self:led_reverse()
        end
    end)
end

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

    return initiator
end

local function update_location_db(value, obj, db)
    local name = obj:get_prop('Name')
    local trim_value = common_utils.trim(value)
    local chassis_db = db.Chassis({Name = name})
    if chassis_db then
        chassis_db['Location'] = trim_value
        chassis_db:save()
    else
        db.Chassis({Name = name, Location = trim_value}):save()
    end
end

local function register_listen_callback(self, obj)
    obj.chassis_object.property_changed:on(function (prop, value, _)
        if prop == 'Location' then
            update_location_db(value, obj, self.db)
        end
        if prop == 'UidButtonScanner' and value ~= 0 then
            self:set_uid_button()
        end
    end)
end

function chassis_service:add_object(obj, position)
    local chassis_obj = chassis_object.new_for_csr(obj, position)
    table.insert(self.chassis_collection, chassis_obj)
    skynet.fork(function()
        skynet.sleep(UPDATE_DEVICE_MAX_CNT_DELAY) -- 启动阶段等待30s后更新最大组件个数
        self:update_device_max_count(chassis_obj)
    end)
end

function chassis_service:on_delete_object(position)
    for _, chassis_obj in pairs(self.chassis_collection) do
        if chassis_obj.position == position then
            table.remove(self.chassis_collection)
        end
    end
end

function chassis_service:on_add_object_complete(position)
    for _, chassis_obj in pairs(self.chassis_collection) do
        if chassis_obj.position == position then
            register_listen_callback(self, chassis_obj)
            chassis_obj:property_change()
        end
    end
end

-- 开箱检测状态更新
function chassis_service:update_intrusion_status()
    for _, chassis_obj in pairs(self.chassis_collection) do
        local intrusion_ac_on = chassis_obj:get_intrusion_ac_on()
        local intrusion_ac_off = chassis_obj:get_intrusion_ac_off()
        local cover_status = chassis_obj:get_cover_status()
        local last_intrusion_ac_on = chassis_obj:get_last_intrusion_ac_on()
        local is_changed = false
        -- AC下电开箱事件告警
        if intrusion_ac_off == CHASSIS_COVER.OPENED then
            log:notice('Open the chassis cover(AC Off), intrusion_ac_off: %s, cover_status: %s,intrusion_ac_on: %s',
                intrusion_ac_off, cover_status, intrusion_ac_on)
            chassis_obj:set_intrusion_flag(CHASSIS_COVER.OPENED_VALUE)
        end
        -- AC下电关箱事件记录
        if intrusion_ac_off == CHASSIS_COVER.OPENED and cover_status == CHASSIS_COVER.CLOSED then
            log:notice('Close the chassis cover(AC Off), intrusion_ac_off: %s, cover_status: %s, intrusion_ac_on: %s',
                intrusion_ac_off, cover_status, intrusion_ac_on)
            chassis_obj:set_intrusion_flag(CHASSIS_COVER.CLOSED_VALUE)
            chassis_obj:set_intrusion_ac_off_clear(CHASSIS_COVER.OPEN_AC_CLEAR)
            log:operation(get_op_initiator(), 'Chassis', 'Close the chassis cover(AC Off)')
        end

        -- AC上电开箱事件记录
        if intrusion_ac_on == CHASSIS_COVER.OPENED and last_intrusion_ac_on == CHASSIS_COVER.CLOSED then
            log:notice('Open the chassis cover(AC On), intrusion_ac_off: %s, cover_status: %s,intrusion_ac_on: %s',
                intrusion_ac_off, cover_status, intrusion_ac_on)
            log:operation(get_op_initiator(), 'Chassis', 'Open the chassis cover(AC On)')
            chassis_obj:set_intrusion_flag(CHASSIS_COVER.OPENED_VALUE)
            is_changed = true
        end
        -- AC上电关箱事件记录
        if intrusion_ac_on == CHASSIS_COVER.OPENED and cover_status == CHASSIS_COVER.CLOSED then
            log:notice('Close the chassis cover(AC On), intrusion_ac_off: %s, cover_status: %s,intrusion_ac_on: %s',
                intrusion_ac_off, cover_status, intrusion_ac_on)
            log:operation(get_op_initiator(), 'Chassis', 'Close the chassis cover(AC On)')
            chassis_obj:set_intrusion_flag(CHASSIS_COVER.CLOSED_VALUE)
            chassis_obj:set_intrusion_ac_on_clear(CHASSIS_COVER.OPEN_DC_CLEAR) -- 清除寄存器开箱事件命令字
            intrusion_ac_on = CHASSIS_COVER.CLOSED
            is_changed = true
        end

        if is_changed then
            chassis_obj:set_last_intrusion_ac_on(intrusion_ac_on)
        end
    end
end

-- 检测Button的变化
function chassis_service:uid_button_monitor()
    tasks.get_instance():next_tick(function()
        local chassis_obj = self.chassis_collection[1]
        -- 这里循环十次，每次间隔5s，保证在监听接管之前可以把按下事件清除
        for _ = 1, 10 do
            local value = chassis_obj:get_prop('UidButtonScanner')
            if value == 1 then
                self:set_uid_button()
                break
            end
            tasks.sleep_ms(5000)
        end
    end)
end

function chassis_service:set_uid_button()
    if self.uid_btn_task then
        return
    end
    self.uid_btn_sig:emit(true)

    local reset_count = 0
    local chassis_obj = self.chassis_collection[1]
    self.uid_btn_task = tasks.get_instance():new_task("uid btn check task"):loop(function()
        reset_count = reset_count + 1
        chassis_obj:set_prop('UidButtonAccessor', 0)
        chassis_obj:set_prop('UidButtonPressed', 0)
        tasks.sleep_ms(reset_count < 50 and 400 or 5000)
        if chassis_obj:get_prop('UidButtonScanner') == 0 then
            self.uid_btn_task:stop()
            self.uid_btn_task = nil
            return
        end
    end):set_timeout_ms(0)

    self.uid_btn_task.on_task_exit:on(function()
        self.uid_btn_sig:emit(false)
    end)
end

function chassis_service:add_event(param)
    local record
    local event_obj = client:GetEventsEventsObject()
    if not event_obj then
        log:error('[chassis]get events object failed')
        return false
    end

    local ok, err = pcall(function()
        record = event_obj:AddEvent_PACKED(ctx.new(), param):unpack()
    end)
    if not ok then
        log:error('[chassis]add event(%s) fail, %s', record, err)
        return false
    end
    log:notice('[chassis]add event(%s) successfully', record)
    return true
end

local function retry_add_event(self, param)
    for _ = 1,10 do
        local ok = self:add_event(param)
        if ok then
            return true
        end
        skynet.sleep(500)
    end
    return false
end

function chassis_service:long_press_uid_check_task()
    skynet.fork(function ()
        skynet.sleep(1000) -- 等待10s保证Scanner对象加载成功
        local chassis_obj = self.chassis_collection[1]
        if chassis_obj:get_prop('UidButtonLongScanner') == 1 then
            log:notice("UID button long pressed")
            log:operation(
                initiator.new('PANEL', 'N/A', '127.0.0.1'),
                'Chassis',
                'Press uid button to uid led on successfully'
            )
            local params = {}
            params[#params + 1] = { Key = 'State', Value = 'true'}
            params[#params + 1] = { Key = 'ComponentName', Value = 'button'}
            params[#params + 1] = { Key = 'EventKeyId', Value = 'Button.ButtonUIDButtonLongPressed'}
            params[#params + 1] = { Key = 'MessageArgs', Value = cjson.encode({})}
            params[#params + 1] = { Key = 'SystemId', Value = ''}
            params[#params + 1] = { Key = 'ManagerId', Value = ''}
            params[#params + 1] = { Key = 'ChassisId', Value = ''}
            params[#params + 1] = { Key = 'NodeId', Value = ''}
            retry_add_event(self, params)
            -- 长按事件上报后清除寄存器值与事件
            chassis_obj:set_prop('UidButtonLongAccessor', 0)
            params[1] = { Key = 'State', Value = 'false'}
            retry_add_event(self, params)
        end
    end)
end

function chassis_service:dft_test_uid()
    local chassis_obj = self.chassis_collection[1]

    if chassis_obj:get_prop('UidButtonTestFlag') == true or
        chassis_obj:get_prop('UidButtonTestResult') == 255 then
        chassis_obj:set_prop('UidButtonTestStatus', 0)
        chassis_obj:set_prop('UidButtonTestResult', 0)
    end
end

function chassis_service:led_reverse()
    self.led_object = self.led_service:get_led_obj()
    if not self.led_object then
        return
    end
    -- UID状态翻转
    if self.led_object:get_led_state() == uid_state.LED_OFF then
        self.led_service:set_led(0xFF)
        log:operation(
            initiator.new('PANEL', 'N/A', '127.0.0.1'),
            'Chassis',
            'Press uid button to uid led on successfully'
        )
    else
        self.led_service:set_led(0)
        log:operation(
            initiator.new('PANEL', 'N/A', '127.0.0.1'),
            'Chassis',
            'Press uid button to uid led off successfully'
        )
    end
end

function chassis_service:get_chassis_obj()
    for _, chassis_obj in pairs(self.chassis_collection) do
        return chassis_obj
    end
end

function chassis_service:update_device_info(device_name, device_path, device_interface)
    local chassis_obj = self:get_chassis_obj()
    local ok, rsp
    for _ = 1, 10 do
        ok, rsp = pcall(mdb.get_sub_objects, self.bus, device_path, device_interface)
        if ok and rsp then
            break
        end
        skynet.sleep(50)
    end
    if not ok then
        log:error('[chassis]get %s object list fail, error: %s', device_name, rsp)
        return
    end
    local count = 0
    for _, _ in pairs(rsp) do
        count = count + 1
    end
    chassis_obj:update_device(device_name, count)
end

local device_info_with_current = {
    {
        device_name = 'CPU',
        device_path = '/bmc/kepler/Systems/1/Processors/CPU',
        device_interface =
        'bmc.kepler.Systems.Processor'
    },
    {
        device_name = 'NPU',
        device_path = '/bmc/kepler/Systems/1/Processors/NPU',
        device_interface =
        'bmc.kepler.Systems.Processor'
    },
    {
        device_name = 'Memory',
        device_path = '/bmc/kepler/Systems/1/Memory',
        device_interface =
        'bmc.kepler.Systems.Memory'
    },
    {
        device_name = 'Psu',
        device_path = '/bmc/kepler/Systems/1/PowerMgmt',
        device_interface =
        'bmc.kepler.Systems.PowerMgmt.OnePower'
    },
    {
        device_name = 'Fan',
        device_path = '/bmc/kepler/Systems/1/Thermal/Fans',
        device_interface =
        'bmc.kepler.Systems.Fan'
    },
    {
        device_name = 'Disk',
        device_path = '/bmc/kepler/Systems/1/Storage/Drives',
        device_interface =
        'bmc.kepler.Systems.Storage.Drive'
    }
}

-- 非依赖连接器统计最大数量的设备
local device_name_without_connector = {
    ['CPU'] = 1,
    ['NPU'] = 1,
    ['Memory'] = 1,
    ['Fan'] = 1,
    ['SecurityModule'] = 1
}

-- 部分设备的最大为默认值，最大数量默认为0代表不支持此设备
local device_defaulf_max_num = {
    { device_name = 'SecurityModule', count_type = 'device_num', default_max_num = 1 }
}

function chassis_service:register_device_info()
    for _, device_info in pairs(device_info_with_current) do
        local device_name = device_info.device_name
        local slot_name = 'add_' .. device_name .. '_slot'
        local add_sig = match_rule.signal(add.name):with_interface(add.interface):with_path_namespace(
            device_info.device_path)
        self[slot_name] = self.bus:match(add_sig, function(msg)
            local _, interfaces_and_properties = msg:read()
            if not interfaces_and_properties[device_info.device_interface] then
                return
            end

            self.queue(function()
                log:debug('[chassis]monitor device(%s) path add', device_info.device_name)
                self:update_device_info(device_info.device_name, device_info.device_path,
                    device_info.device_interface)
            end)
        end)
        slot_name = 'del_' .. device_name .. '_slot'
        local del_sig = match_rule.signal(del.name):with_interface(del.interface):with_path_namespace(
            device_info.device_path)
        self[slot_name] = self.bus:match(del_sig, function(msg)
            local _, interfaces = msg:read('oas')
            local has_interface = false
            for _, interface in ipairs(interfaces) do
                if interface == device_info.device_interface then
                    has_interface = true
                    break
                end
            end
            if not has_interface then
                return
            end

            self.queue(function()
                log:debug('[chassis]monitor device(%s) path del', device_info.device_name)
                self:update_device_info(device_info.device_name, device_info.device_path,
                    device_info.device_interface)
            end)
        end)
    end
end

local function set_all_device_max_count(chassis_obj)
    for _, default_device_info in pairs(device_defaulf_max_num) do
        chassis_obj:set_device_max_count_and_slot(default_device_info['device_name'],
            default_device_info['default_max_num'])
    end
end
 
local function update_connector_info(connector_list, chassis_obj)
    chassis_obj:init_connector()
    chassis_obj:init_slots()
    -- 通过统计实时连接器数量来计算组件的最大数量
    for _, obj in pairs(connector_list) do
        if device_name_without_connector[obj.Type] then
            goto continue
        end
        chassis_obj:update_connector_max_count(obj.Type)
        chassis_obj:update_slot(obj.Type, obj.Slot)
        ::continue::
    end
    chassis_obj:update_connector_with_subcomponent()
    chassis_obj:update_connector()
end

function chassis_service:update_device_max_count(chassis_obj)
    set_all_device_max_count(chassis_obj)
    skynet.sleep(50)
    local update_num = function()
        skynet.sleep(50)
        local ok, rsp
        for _ = 1, 5 do
            ok, rsp = pcall(mdb.get_sub_objects, self.bus, '/bmc/kepler/Connector', 'bmc.kepler.Connector')
            if ok and rsp then
                break
            end
            skynet.sleep(10)
        end
        if not ok then
            log:error('[chassis]get Connector object list fail, error: %s', rsp)
            return
        end
        log:debug('[chassis]monitor connector path add start')
        update_connector_info(rsp, chassis_obj)
        log:debug('[chassis]monitor connector path add finish')
    end

    local add_sig = match_rule.signal(add.name):with_interface(add.interface):with_path_namespace(
        '/bmc/kepler/Connector')
    self['add_connector_slot'] = self.bus:match(add_sig, function(msg)
        self.queue(update_num)
    end)
    local del_sig = match_rule.signal(del.name):with_interface(del.interface):with_path_namespace(
        '/bmc/kepler/Connector')
    self['del_connector_slot'] = self.bus:match(del_sig, function(msg)
        self.queue(update_num)
    end)
    self.queue(update_num)

    -- 通过统计资源树对象数量来计算组件的最大数量
    skynet.sleep(200)
    self:register_device_info()
    self.queue(function()
        log:notice('[chassis]get device info start')
        for _, device_info in pairs(device_info_with_current) do
            self:update_device_info(device_info.device_name, device_info.device_path, device_info.device_interface)
        end
        log:notice('[chassis]get device info finish')
    end)
end

local POWER_STRATEGY_ALWAYS_POWER_ON <const> = 'AlwaysPowerOn'
local POWER_STRATEGY_ALWAYS_POWER_OFF <const> = 'AlwaysPowerOff'
local POWER_STRATEGY_LAST_STATE <const> = 'LastState'

-- 判断服务器类型是机架类型还是刀片类型
local function is_servertype_blade()
    local chassis_obj = factory.chassis_service.chassis_collection[1]
    -- 认为X系列计算节点、OSCA前插板、OSCA后插板都为刀片类型
    if chassis_obj.ChassisType == chassis_def.CHASSIS_TYPE.X_SERIAL or
        chassis_obj.ChassisType == chassis_def.CHASSIS_TYPE.BLADE or
        chassis_obj.ChassisType == chassis_def.CHASSIS_TYPE.SWITCH or
        chassis_obj.ChassisType == chassis_def.CHASSIS_TYPE.CMC then
        return chassis_def.SERVER_TYPE.SERVER_TYPE_BLADE
    end
    -- 其他类型归类为机架类型，能够获取到风扇、电源信息等
    return chassis_def.SERVER_TYPE.SERVER_TYPE_CHASSIS
end

local function get_misc_chassis_state()
    -- identify默认是一直开启的
    local result = 0x40
    local uid_led = factory.led_service.uid_obj.uidled_info
    if uid_led then
        if uid_led.State == chassis_def.UIDLEDSTATE.LED_OFF then
            result = result | chassis_def.CHASSIS_IDENTIFY_STATE.OFF
        elseif uid_led.State == chassis_def.UIDLEDSTATE.LED_ON then
            result = result | chassis_def.CHASSIS_IDENTIFY_STATE.ON
        elseif uid_led.State == chassis_def.UIDLEDSTATE.LED_BLINK then
            result = result | chassis_def.CHASSIS_IDENTIFY_STATE.TIMED
        else
            log:error("identify_state: %d is invalid", uid_led.State)
            return chassis_def.RET.ERR
        end
    else
        log:error("can not find uid led object")
        return chassis_def.RET.ERR
    end

    -- 获取风扇是否故障
    if is_servertype_blade() ~= chassis_def.SERVER_TYPE.SERVER_TYPE_BLADE then
        client:ForeachFanObjects(function (obj)
            if obj.FanHealth ~= 0 then
                result = result | 0x08
            end
        end)
    else
        log:notice('Blade server no need to get fan status')
    end

    -- 获取硬盘是否故障
    client:ForeachDriveStatusObjects(function (obj)
        if obj.Health ~= 0 then
            result = result | 0x04
        end
    end)
    return chassis_def.RET.OK, result
end

function chassis_service:ipmi_get_power_status(req, ctx)
    local rsp = msg.GetPowerCmdRsp.new(cc.Success, 0, 0, 0, 0)
    local power_state = 0
    local panel_button_state = 0x10  -- 允许屏蔽电源按钮的值固定为1，允许屏蔽前面板电源按钮
    local fructrl = self:get_fructrl_by_systemid(ctx.HostId or 1)
    if not fructrl then
        rsp.CompletionCode = cc.DataNotAvailable
        return rsp
    end
    if fructrl.PowerOnStrategy == POWER_STRATEGY_ALWAYS_POWER_ON then
        power_state = power_state | 0x40
    elseif fructrl.PowerOnStrategy == POWER_STRATEGY_ALWAYS_POWER_OFF then
        power_state = power_state | 0x00
    elseif fructrl.PowerOnStrategy == POWER_STRATEGY_LAST_STATE then
        power_state = power_state | 0x20
    else
        power_state = power_state | 0x60
    end
    if fructrl.PowerState == 'ON' or fructrl.PowerState == 'OFFING' then
        power_state = power_state | 0x01
    end
    if not fructrl.PanelPowerButtonEnabled then
        panel_button_state = panel_button_state | 0x01
    end
    power_state = power_state | ((fructrl.PowerCtrlFault & 0x01) << 4)
    rsp.LastPowerEvent = fructrl.LastPowerEvent

    -- 检查所有OnePower对象Health属性是否为0，如果存在电源异常，则将power fault设置为1
    client:ForeachStatusObjects(function (obj)
        if obj.Health ~= 0 then
            power_state = power_state | 0x08
        end
    end)
    rsp.CurrentPowerState = power_state

    local ret_val, misc_chassis_state = get_misc_chassis_state()
    if ret_val == chassis_def.RET.ERR then
        rsp.CompletionCode = cc.ResponseError
        return rsp
    end
    if misc_chassis_state then
        rsp.MiscChassisState = misc_chassis_state
    end
    rsp.FrontPanelButton = panel_button_state

    return rsp
end

local DIMENSION_PATH <const> = "/bmc/kepler/Chassis/1/Dimension"
local DIMENSION_INTERFACE <const> = "bmc.kepler.Chassis.Dimension"
local DEFAULT_HEIGHTU <const> = 0
function chassis_service:ipmi_get_chassis_height(req, ctx)
    local read_offset = req.ReadOffset
    local len = req.ReadLen
    local ok, obj = pcall(mdb.get_object, self.bus, DIMENSION_PATH, DIMENSION_INTERFACE)
    local rsp = msg.GetChassisHeightRsp.new()
    rsp.CompletionCode = cc.Success
    rsp.ManufactureId = 0x0007db
    rsp.EndFlag = 0
    if read_offset >= 2 or len > 2 or read_offset + len >2 then
        log:error('ReadOffset or ReadLen is invalid!')
        rsp.CompletionCode = cc.ParmOutOfRange
        rsp.Data = s_pack("B" , 0xff)
        return rsp
    end
    local a = DEFAULT_HEIGHTU
    local b = DEFAULT_HEIGHTU
    if ok then
        a,b = math.modf(obj.HeightU)
        b = b == 0.5 and 5 or 0
    else
        log:error('get_chassis_height failed.')
        rsp.CompletionCode = cc.DataNotAvailable
        rsp.Data = s_pack("B" , 0xff)
        return rsp
    end
    local x = s_pack("B" , a)
    local y = s_pack("B" , b)
    rsp.Data = x .. y
    rsp.Data = string.sub(rsp.Data, read_offset + 1, len + read_offset)
    return rsp
end

function chassis_service:get_fructrl_by_systemid(systemid)
    for _, obj in pairs(client:GetFruCtrlObjects()) do
        if obj.extra_params.SystemId == systemid then
            return obj
        end
    end

    return nil
end

function chassis_service:ipmi_set_panel_button_enabled(req, ctx)
    local rsp = msg.SetPanelButtonEnabledRsp.new()

    -- 获取FRU控制对象
    local fru_ctrl = self:get_fructrl_by_systemid(ctx.HostId or 1)
    if not fru_ctrl then
        rsp.CompletionCode = cc.DataNotAvailable
        return rsp
    end
    local ok = pcall(function()
        -- 设置FRU控制对象的PanelPowerButtonEnabled属性
        -- FRU控制对象的PanelPowerButtonEnabled属性是表示按钮是否生效
        -- req.State & 0x01 == 0 表示不屏蔽前面板按钮，按钮有效
        --所以这种情况下，FruCtrl对象的PanelPowerButtonEnabled属性为true，反之PanelPowerButtonEnabled属性为false
        fru_ctrl.PanelPowerButtonEnabled = (req.State & 0x01) == 0
    end)
    if not ok then
        rsp.CompletionCode = cc.DataNotAvailable
        return rsp
    end 
    rsp.CompletionCode = cc.Success
    return rsp
end

return chassis_service
