-- Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
-- 
-- this file licensed under the 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.
-- emulex卡使用此协议获取fru_table信息

local class = require 'mc.class'
local bs = require 'mc.bitstring'
local log = require 'mc.logging'
local pldm_standard = require 'protocol_open.protocol.pldm_standard'
local ctx = require 'mc.context'
local utils = require 'mc.utils'

local pldm_emulex_fru = class(pldm_standard)
local MCTP_PLDM_TYPE_4<const> = 0x04
local MCTP_PLDM_CMD_GET_RECORD_TABLE<const> = 0x02
local MAX_FRU_TABLE_RECORD_NUM<const> = 6
local FRU_RECORD_ID_GENERAL<const> = 1
local FRU_RECORD_ID_CHIP<const> = 3
local FRU_RECORD_ID_PORT0<const> = 4
local FRU_RECORD_ID_PORT1<const> = 5
local FRU_RECORD_ID_PORT2<const> = 6
local FRU_RECORD_ID_PORT3<const> = 7
local MCTP_PLDM_GET_RECORD_TABLE_FIRST_PART<const> = 0x01
local MCTP_PLDM_GET_RECORD_TABLE_NEXT_PART<const> = 0x00
local FRU_TABLE_TRANSFER_FLAG_END<const> = 0x04
local FRU_TABLE_TRANSFER_FLAG_START_END<const> = 0x05
local TLV_TYPE_INDEX_START = 1
local TLV_TYPE_INDEX_END = 1
local TLV_LENGTH_INDEX_START = 2
local TLV_LENGTH_INDEX_END = 2
local TLV_VALUE_INDEX_START = 3

local response_bs<const> = bs.new([[<<
    next_data_transfer_handle:32,
    transfer_flag:8,
    record_id:16,
    record_type:8,
    number_field:8,
    encoding_type:8,
    data/string
>>]])

local request_bs<const> = bs.new([[<<
    data_transfer_handle:32,
    transfer_operation_flag:8
>>]])

local request_params_template<const> = {record_id = true}

-- 每个record对应的最大tlv个数
local record_tlv_num = {
    [FRU_RECORD_ID_GENERAL] = 14,
    [FRU_RECORD_ID_CHIP] = 3,
    [FRU_RECORD_ID_PORT0] = 8,
    [FRU_RECORD_ID_PORT1] = 8,
    [FRU_RECORD_ID_PORT2] = 8,
    [FRU_RECORD_ID_PORT3] = 8
}

-- 初始化结果
local fru_table = {
    [FRU_RECORD_ID_GENERAL] = {},
    [FRU_RECORD_ID_CHIP] = {},
    [FRU_RECORD_ID_PORT0] = {},
    [FRU_RECORD_ID_PORT1] = {},
    [FRU_RECORD_ID_PORT2] = {},
    [FRU_RECORD_ID_PORT3] = {}
}

local function send_fru_single_request(transfer_operation_flag,
                                       data_transfer_handle, self)
    local data = request_bs:pack({
        data_transfer_handle = data_transfer_handle,
        transfer_operation_flag = transfer_operation_flag
    })
    local req_ctx, rsp_ctx, request_bin =
        pldm_emulex_fru.super.construct_request_data(self, {}, {
            pldm_type = MCTP_PLDM_TYPE_4,
            command_code = MCTP_PLDM_CMD_GET_RECORD_TABLE,
            rsp_command_code = MCTP_PLDM_CMD_GET_RECORD_TABLE, -- pldm的rsp command code与req command code一致
            data = data
        })

    local ok, rsp_data_bin = pcall(self.endpoint.Request, self.endpoint,
        ctx.get_context_or_default(), request_bin, 0, req_ctx, rsp_ctx)
    if not ok or not rsp_data_bin then
        log:debug('unable to send pldm request to endpoint: %s, error: %s',
            self.endpoint.TargetPhyAddr, rsp_data_bin)
        return nil
    end
    return rsp_data_bin
end

local function fru_rsp_is_valid(rsp, self)
    if record_tlv_num[rsp.record_id] == nil then
        log:debug('[protocol %s]: get invalid tlv, get record_id is %s',
            self.name, rsp.record_id)
        return false
    end
    if rsp.number_field > record_tlv_num[rsp.record_id] then
        log:debug('[protocol %s]: get tlv num is invalid, get record_id is %s',
            self.name, rsp.record_id)
        return false
    end
    return true
end

local function fill_fru_table(rsp, fru_table)
    local start_index = 0
    local tlv_table
    local tlv_type
    local tlv_length
    local tlv_value
    local data_len = #rsp.data
    -- 按照协议分为type(表示具体的属性)，length，value(长度由length确定的字符串)
    for _ = 1, rsp.number_field do
        if start_index >= data_len then
            log:debug('cur offset:%s exceeds rsp data_len:%s', start_index, data_len)
            break
        end
        tlv_type = rsp.data:sub(start_index + TLV_TYPE_INDEX_START, start_index + TLV_TYPE_INDEX_END):byte()
        tlv_length = rsp.data:sub(start_index + TLV_LENGTH_INDEX_START, start_index + TLV_LENGTH_INDEX_END):byte()
        tlv_value = tlv_length == 0 and '' or rsp.data:sub(start_index + TLV_VALUE_INDEX_START,
            start_index + TLV_VALUE_INDEX_START + tlv_length - 1)
        tlv_table = {type = tlv_type, length = tlv_length, value = tlv_value}
        table.insert(fru_table[rsp.record_id], tlv_table)
        start_index = start_index + 2 + tlv_length -- 2表示TLV的type和length字段加起来大小为2个字节
    end
end

function pldm_emulex_fru:send_request(request)
    local transfer_operation_flag = MCTP_PLDM_GET_RECORD_TABLE_FIRST_PART
    local rsp
    local data_transfer_handle = 0
    if request.record_id ~= 0xff then
        local try_count = 0
        local MAX_TRY_COUNT = 5
        while type(self.fru_table[request.record_id]) ~= 'table' or
            not next(self.fru_table[request.record_id]) do
            try_count = try_count + 1
            if try_count > MAX_TRY_COUNT then
                return nil
            end
            skynet.sleep(3000)
        end
        return self.fru_table[request.record_id]
    end
    -- 每一次获取前清空之前的结果
    self.fru_table = utils.table_copy(fru_table)
    for _ = 1, MAX_FRU_TABLE_RECORD_NUM do

        local rsp_data_bin = send_fru_single_request(transfer_operation_flag, data_transfer_handle, self)
        if rsp_data_bin == nil then
            self.fru_table = utils.table_copy(fru_table)
            return nil
        end
        rsp = response_bs:unpack(rsp_data_bin, true)
        if not fru_rsp_is_valid(rsp, self) then
            log:debug('[protocol %s]: rps is invalid, reset fru table', self.name)
            self.fru_table = utils.table_copy(fru_table)
            return nil
        end
        fill_fru_table(rsp, self.fru_table)
        if rsp.transfer_flag == FRU_TABLE_TRANSFER_FLAG_END or rsp.transfer_flag ==
            FRU_TABLE_TRANSFER_FLAG_START_END then
            return self.fru_table[request.record_id]
        end
        data_transfer_handle = rsp.next_data_transfer_handle
        transfer_operation_flag = MCTP_PLDM_GET_RECORD_TABLE_NEXT_PART
    end
    return self.fru_table[request.record_id]
end

function pldm_emulex_fru:ctor()
    self.name = 'pldm_emulex_fru'
    self.request_params_template = request_params_template
    self.fru_table = utils.table_copy(fru_table)
end

return pldm_emulex_fru
