-- 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.

-- Description: test the external IPMI SEL interfaces of sensor.
local skynet = require 'skynet'
local log = require 'mc.logging'
local bs = require 'mc.bitstring'
local ipmi = require 'sensor.ipmi.ipmi'
local common = require 'test_sensor_common'

local ipmi_interface = {}
ipmi_interface.__index = ipmi_interface

function ipmi_interface.test_set_sensor_event_receiver(bus)
    log:info('== Testing set sensor event receiver ...')

    -- ipmi command: 'ipmitool raw 0x04 0x00 <addr> <lun>'
    local netfn = 0x04
    local cmd = 0x00

    -- 测试偶数合法地址
    local req = ipmi.SetSensorEventReceiver.req.new(0x20, 0x00)
    local payload = bs.new(ipmi.SetSensorEventReceiver.decode):pack(req)
    local ok, ret = common.call_ipmi(bus, netfn, cmd, payload)
    assert(ok)
    local rsp = bs.new(ipmi.SetSensorEventReceiver.encode):unpack(ret)
    assert(rsp.CompletionCode == 0x00, 'actual: ' .. rsp.CompletionCode)

    -- 测试奇数非法地址
    req = ipmi.SetSensorEventReceiver.req.new(0x21, 0x00)
    payload = bs.new(ipmi.SetSensorEventReceiver.decode):pack(req)
    ok, ret = common.call_ipmi(bus, netfn, cmd, payload)
    assert(ok)
    rsp = bs.new(ipmi.SetSensorEventReceiver.encode):unpack(ret)
    assert(rsp.CompletionCode ~= 0x00, 'actual: ' .. rsp.CompletionCode)
end

function ipmi_interface.test_get_sensor_event_receiver(bus)
    log:info('== Testing get sensor event receiver ...')

    -- ipmi command: 'ipmitool raw 0x04 0x01'
    local netfn = 0x04
    local cmd = 0x01

    local payload = ''
    local ok, ret = common.call_ipmi(bus, netfn, cmd, payload)
    assert(ok)
    local rsp = bs.new(ipmi.GetSensorEventReceiver.encode):unpack(ret)
    assert(rsp.CompletionCode == 0x00, 'actual: ' .. rsp.CompletionCode)
    assert(rsp.Addr == 0x20, 'actual: ' .. rsp.Addr)
    assert(rsp.Lun == 0x00, 'actual: ' .. rsp.Lun)
end

function ipmi_interface.test_report_sensor_event(bus)
    log:info('== Testing report sensor event ...')

    -- ipmi command: 'ipmitool raw 0x04 0x02 <event>'
    -- event: '0x04 0x01 0x30 0x01 0x09 0xff 0xff'
    local netfn = 0x04
    local cmd = 0x02

    local sc = string.char
    local datas = {sc(0x04), sc(0x01), sc(0x30), sc(0x01), sc(0x09), sc(0xff), sc(0xff)}
    local req = ipmi.ReportSensorEvent.req.new(table.concat(datas, ''))
    local payload = bs.new(ipmi.ReportSensorEvent.decode):pack(req)
    local ok, ret = common.call_ipmi(bus, netfn, cmd, payload)
    assert(ok)
    local rsp = bs.new(ipmi.ReportSensorEvent.encode):unpack(ret)
    assert(rsp.CompletionCode == 0x00, 'actual: ' .. rsp.CompletionCode)
end

function ipmi_interface.test_get_oem_system_sel(bus)
    log:info('== Testing get oem system event ...')

    -- ipmi command: 'ipmitool raw 0x30 0x91 0xdb 0x07 0x00 0x09 <record_id>'
    local netfn = 0x30
    local cmd = 0x91

    local req = ipmi.OEMGetSystemSel.req.new(0xDB0700, 0)
    local payload = bs.new(ipmi.OEMGetSystemSel.decode):pack(req)
    local ok, ret = common.call_ipmi(bus, netfn, cmd, payload)
    assert(ok)
    local rsp = bs.new(ipmi.OEMGetSystemSel.encode):unpack(ret)
    if not rsp then
        -- 说明当前测试环境中无SEL
        rsp = {CompletionCode = ret:byte(1)}
        assert(rsp.CompletionCode == 0xCB, 'actual: ' .. rsp.CompletionCode)
    else
        assert(rsp.CompletionCode == 0x00, 'actual: ' .. rsp.CompletionCode)
    end
end

function ipmi_interface.test_get_sel_info(bus)
    log:info('== Testing get sel info ...')

    -- ipmi command: 'ipmitool raw 0x0A 0x40'
    local netfn = 0x0A
    local cmd = 0x40

    local payload = ''
    local ok, ret = common.call_ipmi(bus, netfn, cmd, payload)
    assert(ok)
    local rsp = bs.new(ipmi.GetSelInfo.encode):unpack(ret)
    assert(rsp.CompletionCode == 0x00, 'actual: ' .. rsp.CompletionCode)
    assert(rsp.SelVersion == 0x51, 'actual: ' .. rsp.SelVersion)
end

function ipmi_interface.test_get_sel_alloc_info(bus)
    log:info('== Testing get sel alloc info ...')

    -- ipmi command: 'ipmitool raw 0x0A 0x41'
    local netfn = 0x0A
    local cmd = 0x41

    local payload = ''
    local ok, ret = common.call_ipmi(bus, netfn, cmd, payload)
    assert(ok)
    local rsp = bs.new(ipmi.GetSelAllocInfo.encode):unpack(ret)
    assert(rsp.CompletionCode == 0x00, 'actual: ' .. rsp.CompletionCode)
    assert(rsp.MaxSize == 16, 'actual: ' .. rsp.MaxSize)
end

local sel_reserve_id = 0
function ipmi_interface.test_get_sel_reserve_id(bus)
    log:info('== Testing get sel reserve id ...')

    -- ipmi command: 'ipmitool raw 0x0A 0x42'
    local netfn = 0x0A
    local cmd = 0x42

    local payload = ''
    local ok, ret = common.call_ipmi(bus, netfn, cmd, payload)
    assert(ok)
    local rsp = bs.new(ipmi.GetSelReserveId.encode):unpack(ret)
    assert(rsp.CompletionCode == 0x00, 'actual: ' .. rsp.CompletionCode)
    sel_reserve_id = (rsp.ReserveIdH << 8) | rsp.ReserveIdL
end

function ipmi_interface.test_get_sel_entry(bus)
    log:info('== Testing get sel entry ...')

    -- ipmi command: 'ipmitool raw 0x0A 0x43 <reserve_id> <record_id> <offset> <length>'
    local netfn = 0x0A
    local cmd = 0x43

    local req = ipmi.GetSelEntry.req.new(0, 0, 1, 0, 0, 0xFF)
    local payload = bs.new(ipmi.GetSelEntry.decode):pack(req)
    local ok, ret = common.call_ipmi(bus, netfn, cmd, payload)
    assert(ok)
    local rsp = bs.new(ipmi.GetSelEntry.encode):unpack(ret)
    if not rsp then
        -- 说明当前测试环境中无SEL
        rsp = {CompletionCode = ret:byte(1)}
        assert(rsp.CompletionCode == 0xCB, 'actual: ' .. rsp.CompletionCode)
    else
        assert(rsp.CompletionCode == 0x00, 'actual: ' .. rsp.CompletionCode)
    end
end

function ipmi_interface.test_clear_sel(bus)
    log:info('== Testing clear sel ...')

    -- ipmi command: 'ipmitool raw 0x0A 0x47 <reserve_id> <CLR> <operation>'
    local netfn = 0x0A
    local cmd = 0x47

    local sb = string.byte
    local rid_l = sel_reserve_id & 0xFF
    local rid_h = (sel_reserve_id >> 8) & 0xFF
    local req = ipmi.ClearSel.req.new(rid_l, rid_h, sb('C'), sb('L'), sb('R'), 0xAA)
    local payload = bs.new(ipmi.ClearSel.decode):pack(req)
    local ok, ret = common.call_ipmi(bus, netfn, cmd, payload)
    assert(ok)
    local rsp = bs.new(ipmi.ClearSel.encode):unpack(ret)
    assert(rsp.CompletionCode == 0x00, 'actual: ' .. rsp.CompletionCode)
    assert(rsp.Status == 1, 'actual: ' .. rsp.Status)
end

function ipmi_interface.test_add_sel(bus)
    log:info('== Testing add sel ...')

    -- ipmi command: 'ipmitool raw 0x0A 0x44 <rid> <type> <ts> <gid> <ver> <stype> <sno> <et> <ed> <datas>'
    -- rid: 2个字节，SEL的记录标识，写死为 00 00
    -- type: 1个字节，SEL的类型，写死为 02
    -- ts: 4个字节，SEL的时间戳，写死为 00 00 00 00
    -- gid: 2个字节，SEL的生成标识，写死为 00 41
    -- ver: 1个字节，SEL的版本号
    -- stype: 1个字节，SEL对应传感器的类型
    -- sno: 1个字节，SEL对应传感器的编号
    -- et: 1个字节，SEL对应的事件类型
    -- ed: 1个字节，SEL对应的方向
    -- datas: 3个字节，SEL对应的数据
    local netfn = 0x0A
    local cmd = 0x44

    -- SEL 的元数据：04 0d 18 6f 00 ff ff
    local datas = table.concat({string.char(0), string.char(0xFF), string.char(0xFF)}, '')
    local req = ipmi.AddSelEntry.req.new(0, 2, 0, 0x41, 0x04, 0x0D, 0x18, 0x6F, 0x01, datas)
    local payload = bs.new(ipmi.AddSelEntry.decode):pack(req)
    local ok, ret = common.call_ipmi(bus, netfn, cmd, payload)
    assert(ok)
    local rsp = bs.new(ipmi.AddSelEntry.encode):unpack(ret)
    assert(rsp.CompletionCode == 0x00, 'actual: ' .. rsp.CompletionCode)
end

function ipmi_interface.test_set_bios_data(bus)
    log:info('== Testing set bios data ...')

    local check_prop = function(prop, expected)
        require 'sensor.json_types.IPMIEvent'
        local mdb = require 'mc.mdb'
        local path = '/bmc/kepler/Systems/1/EventEntries/BootError'
        local intf = 'bmc.kepler.Systems.IPMIEvent'
        local obj = mdb.get_object(bus, path, intf)
        assert(obj[prop] == expected, 'expected: ' .. expected .. ', actual: ' .. obj[prop])
        skynet.sleep(10) -- 设置的属性有持久化，因此这地方等待 100 ms
    end

    -- ipmi command: 'ipmitool raw 0x30 0x92 0xdb 0x07 0x00 0x05 0x0F <dir> <status> <datas>'
    -- dir: 1个字节，当前SEL的方向，0 为 assert，1 为 deassert
    -- status: 1个字节，当前SEL的类型状态，0 - 5 之间的值
    -- datas: 不定长度，当前SEL的其他数据，目前没啥用，暂时不写
    local netfn = 0x30
    local cmd = 0x92

    -- 设置 boot error 为 assert，status 是 0
    local req = ipmi.SetBIOSEventData.req.new(0x0007db, 0, 0, '')
    local payload = bs.new(ipmi.SetBIOSEventData.decode):pack(req)
    local ok, ret = common.call_ipmi(bus, netfn, cmd, payload)
    assert(ok)
    local rsp = bs.new(ipmi.SetBIOSEventData.encode):unpack(ret)
    assert(rsp.CompletionCode == 0x00, 'actual: ' .. rsp.CompletionCode)
    assert(rsp.ManuId == 0x0007DB, 'actual: ' .. rsp.ManuId)
    check_prop('EventData', 0)

    -- 设置boot error 为 assert，status 是 2
    req = ipmi.SetBIOSEventData.req.new(0x0007db, 0, 2, 'CDR')
    payload = bs.new(ipmi.SetBIOSEventData.decode):pack(req)
    ok, ret = common.call_ipmi(bus, netfn, cmd, payload)
    assert(ok)
    rsp = bs.new(ipmi.SetBIOSEventData.encode):unpack(ret)
    assert(rsp.CompletionCode == 0x00, 'actual: ' .. rsp.CompletionCode)
    assert(rsp.ManuId == 0x0007DB, 'actual: ' .. rsp.ManuId)
    check_prop('EventData', 2)
end

function ipmi_interface.test_entry(bus)
    log:info('================ test sel ipmi interface start ================')

    -- ipmi raw interface
    ipmi_interface.test_set_sensor_event_receiver(bus)
    ipmi_interface.test_get_sensor_event_receiver(bus)
    ipmi_interface.test_report_sensor_event(bus)
    ipmi_interface.test_get_oem_system_sel(bus)
    ipmi_interface.test_get_sel_info(bus)
    ipmi_interface.test_get_sel_alloc_info(bus)
    ipmi_interface.test_get_sel_reserve_id(bus)
    ipmi_interface.test_get_sel_entry(bus)
    ipmi_interface.test_clear_sel(bus)
    ipmi_interface.test_add_sel(bus)
    ipmi_interface.test_set_bios_data(bus)

    log:info('================ test sel ipmi interface complete ================')
end

return ipmi_interface