-- 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 log = require 'mc.logging'
local bs = require 'mc.bitstring'
local skynet = require 'skynet'
local ncsi_protocol_intf = require 'ncsi_protocol_intf'
local ncsi_def = require 'ncsi.ncsi_protocol.ncsi_def'
local ncsi_parameter = require 'ncsi.ncsi_protocol.ncsi_parameter'

local ncsi_utils = {}

local NCSI_ETHERNET_TYPE = 0x88F8
local HEADER_REVISION = 0x01
local NCSI_SELECT_PACKAGE_CHANNEL_ID = 0x1F
local NCSI_DEST_ADDR_MAC = '\xff\xff\xff\xff\xff\xff'
local NCSI_CHANNEL_MAX_ID = 4 -- channel从0开始，最大为3
local NCSI_CHANNEL_MAX_CNT = 4 -- 最多4个channel
local NCSI_PACKAGE_MAX_ID = 8

-- 响应码
local COMMAND_FAILED = 0x0001
local COMMAND_UNAVAILABLE = 0x0002
local COMMAND_UNSUPPORTED = 0x0003

-- 原因码
local NOERROR_OR_NOREASON = 0x0000
local INTERFACE_INIT_REQUIRED = 0x0001
local PARAMETER_INVALID = 0x0002
local CHANNEL_NOT_READY = 0x0003
local PACKAGE_NOT_READY = 0x0004
local INVALID_PAYLOAD_LENGTH = 0x0005
local MAC_ADDRESS_IS_ZEOR = 0x0008
local UNSUPPORTED_COMMAND_TYPE = 0x7FFF

ncsi_utils.src_addr = '\xA8\x50\x81\xA9\xBF\xE8'

local crc_table = {
    [0] = 0x00000000, [1] = 0x77073096, [2] = 0xee0e612c, [3] = 0x990951ba,
    [4] = 0x076dc419, [5] = 0x706af48f, [6] = 0xe963a535, [7] = 0x9e6495a3,
    [8] = 0x0edb8832, [9] = 0x79dcb8a4, [10] = 0xe0d5e91e, [11] = 0x97d2d988,
    [12] = 0x09b64c2b, [13] = 0x7eb17cbd, [14] = 0xe7b82d07, [15] = 0x90bf1d91,
    [16] = 0x1db71064, [17] = 0x6ab020f2, [18] = 0xf3b97148, [19] = 0x84be41de,
    [20] = 0x1adad47d, [21] = 0x6ddde4eb, [22] = 0xf4d4b551, [23] = 0x83d385c7,
    [24] = 0x136c9856, [25] = 0x646ba8c0, [26] = 0xfd62f97a, [27] = 0x8a65c9ec,
    [28] = 0x14015c4f, [29] = 0x63066cd9, [30] = 0xfa0f3d63, [31] = 0x8d080df5,
    [32] = 0x3b6e20c8, [33] = 0x4c69105e, [34] = 0xd56041e4, [35] = 0xa2677172,
    [36] = 0x3c03e4d1, [37] = 0x4b04d447, [38] = 0xd20d85fd, [39] = 0xa50ab56b,
    [40] = 0x35b5a8fa, [41] = 0x42b2986c, [42] = 0xdbbbc9d6, [43] = 0xacbcf940,
    [44] = 0x32d86ce3, [45] = 0x45df5c75, [46] = 0xdcd60dcf, [47] = 0xabd13d59,
    [48] = 0x26d930ac, [49] = 0x51de003a, [50] = 0xc8d75180, [51] = 0xbfd06116,
    [52] = 0x21b4f4b5, [53] = 0x56b3c423, [54] = 0xcfba9599, [55] = 0xb8bda50f,
    [56] = 0x2802b89e, [57] = 0x5f058808, [58] = 0xc60cd9b2, [59] = 0xb10be924,
    [60] = 0x2f6f7c87, [61] = 0x58684c11, [62] = 0xc1611dab, [63] = 0xb6662d3d,
    [64] = 0x76dc4190, [65] = 0x01db7106, [66] = 0x98d220bc, [67] = 0xefd5102a,
    [68] = 0x71b18589, [69] = 0x06b6b51f, [70] = 0x9fbfe4a5, [71] = 0xe8b8d433,
    [72] = 0x7807c9a2, [73] = 0x0f00f934, [74] = 0x9609a88e, [75] = 0xe10e9818,
    [76] = 0x7f6a0dbb, [77] = 0x086d3d2d, [78] = 0x91646c97, [79] = 0xe6635c01,
    [80] = 0x6b6b51f4, [81] = 0x1c6c6162, [82] = 0x856530d8, [83] = 0xf262004e,
    [84] = 0x6c0695ed, [85] = 0x1b01a57b, [86] = 0x8208f4c1, [87] = 0xf50fc457,
    [88] = 0x65b0d9c6, [89] = 0x12b7e950, [90] = 0x8bbeb8ea, [91] = 0xfcb9887c,
    [92] = 0x62dd1ddf, [93] = 0x15da2d49, [94] = 0x8cd37cf3, [95] = 0xfbd44c65,
    [96] = 0x4db26158, [97] = 0x3ab551ce, [98] = 0xa3bc0074, [99] = 0xd4bb30e2,
    [100] = 0x4adfa541, [101] = 0x3dd895d7, [102] = 0xa4d1c46d, [103] = 0xd3d6f4fb,
    [104] = 0x4369e96a, [105] = 0x346ed9fc, [106] = 0xad678846, [107] = 0xda60b8d0,
    [108] = 0x44042d73, [109] = 0x33031de5, [110] = 0xaa0a4c5f, [111] = 0xdd0d7cc9,
    [112] = 0x5005713c, [113] = 0x270241aa, [114] = 0xbe0b1010, [115] = 0xc90c2086,
    [116] = 0x5768b525, [117] = 0x206f85b3, [118] = 0xb966d409, [119] = 0xce61e49f,
    [120] = 0x5edef90e, [121] = 0x29d9c998, [122] = 0xb0d09822, [123] = 0xc7d7a8b4,
    [124] = 0x59b33d17, [125] = 0x2eb40d81, [126] = 0xb7bd5c3b, [127] = 0xc0ba6cad,
    [128] = 0xedb88320, [129] = 0x9abfb3b6, [130] = 0x03b6e20c, [131] = 0x74b1d29a,
    [132] = 0xead54739, [133] = 0x9dd277af, [134] = 0x04db2615, [135] = 0x73dc1683,
    [136] = 0xe3630b12, [137] = 0x94643b84, [138] = 0x0d6d6a3e, [139] = 0x7a6a5aa8,
    [140] = 0xe40ecf0b, [141] = 0x9309ff9d, [142] = 0x0a00ae27, [143] = 0x7d079eb1,
    [144] = 0xf00f9344, [145] = 0x8708a3d2, [146] = 0x1e01f268, [147] = 0x6906c2fe,
    [148] = 0xf762575d, [149] = 0x806567cb, [150] = 0x196c3671, [151] = 0x6e6b06e7,
    [152] = 0xfed41b76, [153] = 0x89d32be0, [154] = 0x10da7a5a, [155] = 0x67dd4acc,
    [156] = 0xf9b9df6f, [157] = 0x8ebeeff9, [158] = 0x17b7be43, [159] = 0x60b08ed5,
    [160] = 0xd6d6a3e8, [161] = 0xa1d1937e, [162] = 0x38d8c2c4, [163] = 0x4fdff252,
    [164] = 0xd1bb67f1, [165] = 0xa6bc5767, [166] = 0x3fb506dd, [167] = 0x48b2364b,
    [168] = 0xd80d2bda, [169] = 0xaf0a1b4c, [170] = 0x36034af6, [171] = 0x41047a60,
    [172] = 0xdf60efc3, [173] = 0xa867df55, [174] = 0x316e8eef, [175] = 0x4669be79,
    [176] = 0xcb61b38c, [177] = 0xbc66831a, [178] = 0x256fd2a0, [179] = 0x5268e236,
    [180] = 0xcc0c7795, [181] = 0xbb0b4703, [182] = 0x220216b9, [183] = 0x5505262f,
    [184] = 0xc5ba3bbe, [185] = 0xb2bd0b28, [186] = 0x2bb45a92, [187] = 0x5cb36a04,
    [188] = 0xc2d7ffa7, [189] = 0xb5d0cf31, [190] = 0x2cd99e8b, [191] = 0x5bdeae1d,
    [192] = 0x9b64c2b0, [193] = 0xec63f226, [194] = 0x756aa39c, [195] = 0x026d930a,
    [196] = 0x9c0906a9, [197] = 0xeb0e363f, [198] = 0x72076785, [199] = 0x05005713,
    [200] = 0x95bf4a82, [201] = 0xe2b87a14, [202] = 0x7bb12bae, [203] = 0x0cb61b38,
    [204] = 0x92d28e9b, [205] = 0xe5d5be0d, [206] = 0x7cdcefb7, [207] = 0x0bdbdf21,
    [208] = 0x86d3d2d4, [209] = 0xf1d4e242, [210] = 0x68ddb3f8, [211] = 0x1fda836e,
    [212] = 0x81be16cd, [213] = 0xf6b9265b, [214] = 0x6fb077e1, [215] = 0x18b74777,
    [216] = 0x88085ae6, [217] = 0xff0f6a70, [218] = 0x66063bca, [219] = 0x11010b5c,
    [220] = 0x8f659eff, [221] = 0xf862ae69, [222] = 0x616bffd3, [223] = 0x166ccf45,
    [224] = 0xa00ae278, [225] = 0xd70dd2ee, [226] = 0x4e048354, [227] = 0x3903b3c2,
    [228] = 0xa7672661, [229] = 0xd06016f7, [230] = 0x4969474d, [231] = 0x3e6e77db,
    [232] = 0xaed16a4a, [233] = 0xd9d65adc, [234] = 0x40df0b66, [235] = 0x37d83bf0,
    [236] = 0xa9bcae53, [237] = 0xdebb9ec5, [238] = 0x47b2cf7f, [239] = 0x30b5ffe9,
    [240] = 0xbdbdf21c, [241] = 0xcabac28a, [242] = 0x53b39330, [243] = 0x24b4a3a6,
    [244] = 0xbad03605, [245] = 0xcdd70693, [246] = 0x54de5729, [247] = 0x23d967bf,
    [248] = 0xb3667a2e, [249] = 0xc4614ab8, [250] = 0x5d681b02, [251] = 0x2a6f2b94,
    [252] = 0xb40bbe37, [253] = 0xc30c8ea1, [254] = 0x5a05df1b, [255] = 0x2d02ef8d
}

local g_trust_channel_cnt_netcard = {
    {vid = 0x15b3, did = 0x1017, svid = 0x19e5, sdid = 0xd15a} -- CX5网卡
}

local ncsi_packet_template = [[<<
    frame_head:1/ethernet_header,
    packet_head:1/ctl_packet_header,
    payload:1130/string
>>]]

ncsi_utils.ncsi_packet_bs = bs.new(ncsi_packet_template,
    {ethernet_header = ncsi_def.ethernet_header_bs, ctl_packet_header = ncsi_def.ctl_packet_header_bs})

local function short_by_big_endian(num)
    return (num >> 8 | num << 8) & 0xffff
end

-- NCSI命令头公共部分配置
function ncsi_utils.ncsi_cmd_common_config(req_packet)
    req_packet.frame_head.des_addr = NCSI_DEST_ADDR_MAC
    req_packet.frame_head.src_addr = ncsi_utils.src_addr
    req_packet.frame_head.ether_type = short_by_big_endian(NCSI_ETHERNET_TYPE)
    req_packet.packet_head.mc_id = ncsi_def.NCSI_MC_ID
    req_packet.packet_head.header_revision = HEADER_REVISION
    req_packet.packet_head.iid = ncsi_parameter.get_instance():get_ncsi_parameter().iid
    req_packet.packet_head.reserved = 0
    req_packet.packet_head.reserved1 = 0
    req_packet.packet_head.reserved2 = 0
    req_packet.packet_head.reserved3 = 0
end

function ncsi_utils.get_checksum(req_packet, len)
    local req_data = ncsi_utils.ncsi_packet_bs:pack(req_packet)
    -- check_sum计算包含ncsi的头和payload
    local check_sum_data = string.sub(req_data, ncsi_def.ETHERNET_HEAD_LEN + 1, ncsi_def.ETHERNET_HEAD_LEN + len)
    local check_sum = ncsi_protocol_intf.get_ncsi_check_sum(check_sum_data, len)
    return check_sum
end

function ncsi_utils.get_crc32(req_packet, len)
    local req_data = ncsi_utils.ncsi_packet_bs:pack(req_packet)
    local crc_data = string.sub(req_data, ncsi_def.ETHERNET_HEAD_LEN + 1, ncsi_def.ETHERNET_HEAD_LEN + len)
    -- 计算 CRC-32 值
    local crc = 0xFFFFFFFF
    for i = 1, len do
        crc = crc_table[(crc ~ crc_data:byte(i)) & 0xFF] ~ (crc >> 8)
    end
    return crc ~ 0xFFFFFFFF
end

local function is_trust_channel_cnt_netcard()
    local ncsi_para = ncsi_parameter.get_instance():get_ncsi_parameter()
    for _, v in pairs(g_trust_channel_cnt_netcard) do
        if v.vid == ncsi_para.pcie_device_ids.vid and v.did == ncsi_para.pcie_device_ids.did and
            v.svid == ncsi_para.pcie_device_ids.svid and v.sdid == ncsi_para.pcie_device_ids.sdid then
            return true
        end
    end
    return false
end

local function is_valid_channel_id(channel_id)
    if not is_trust_channel_cnt_netcard then
        return true
    end
    local channel_cnt = ncsi_parameter.get_instance():get_ncsi_parameter().channel_cnt
    if channel_cnt == nil then
        channel_cnt = NCSI_CHANNEL_MAX_CNT
    end
    if channel_id ~= NCSI_SELECT_PACKAGE_CHANNEL_ID and channel_cnt > 0 and channel_cnt <= NCSI_CHANNEL_MAX_CNT and
        channel_id >= channel_cnt then
        log:error('channel id is out of range, channel_id is %s, channel cnt is %s', channel_id, channel_cnt)
        return false
    end
    return true
end

local function check_package_channel_id(package_id, channel_id)
    if channel_id ~= NCSI_SELECT_PACKAGE_CHANNEL_ID and
        (package_id > NCSI_PACKAGE_MAX_ID or channel_id > NCSI_CHANNEL_MAX_ID) then
        return false
    end
    return true
end

local function write_ncsi_cmd(package_id, channel_id, req_packet, eth_name, ncsi_cmd_table)
    if not check_package_channel_id(package_id, channel_id) then
        return ncsi_def.NCSI_FAIL
    end
    if not ncsi_cmd_table[req_packet.packet_head.packet_type] then
        return ncsi_def.NCSI_FAIL
    end
    return ncsi_cmd_table[req_packet.packet_head.packet_type](req_packet, eth_name)
end

local function check_rsp_valid(req_packet, rsp)
    if req_packet.packet_head.package_id ~= rsp.packet_head.package_id or
        req_packet.packet_head.channel_id ~= rsp.packet_head.channel_id or
        (req_packet.packet_head.packet_type | 0x80) ~= rsp.packet_head.packet_type or
        req_packet.packet_head.iid ~= rsp.packet_head.iid then
        return false
    end
    return true
end

local function read_ncsi_rsp(req_packet, ncsi_cmd_table)
    local rsp_data = ncsi_parameter.get_instance():get_ncsi_parameter().recv_buf
    if #rsp_data == 0 then
        return ncsi_def.NCSI_FAIL
    end
    if #rsp_data < ncsi_def.PACKET_ALL_LEN then
        rsp_data = rsp_data .. string.rep('\x00', ncsi_def.PACKET_ALL_LEN - #rsp_data)
    end
    local rsp = ncsi_utils.ncsi_packet_bs:unpack(rsp_data, true)
    if not check_rsp_valid(req_packet, rsp) then
        return ncsi_def.NCSI_FAIL
    end
    if not ncsi_cmd_table[rsp.packet_head.packet_type] then
        return ncsi_def.NCSI_FAIL
    end
    return ncsi_cmd_table[rsp.packet_head.packet_type](rsp)
end

local function set_uc_iid()
    local ncsi_para = ncsi_parameter.get_instance():get_ncsi_parameter()
    if ncsi_para.iid == 0xff then
        ncsi_para.iid = 1
    else
        ncsi_para.iid = ncsi_para.iid + 1
    end
end

function ncsi_utils.ncsi_cmd_ctrl(package_id, channel_id, req_packet, eth_name, ncsi_cmd_table)
    if not is_valid_channel_id(channel_id) then
        return ncsi_def.NCSI_FAIL
    end
    local ret
    for _ = 1, 3 do
        ret = write_ncsi_cmd(package_id, channel_id, req_packet, eth_name, ncsi_cmd_table)
        if ret ~= ncsi_def.NCSI_SUCCESS then
            goto continue
        end
        for _ = 1, 5 do
            skynet.sleep(3)
            ret = read_ncsi_rsp(req_packet, ncsi_cmd_table)
            if ret == ncsi_def.NCSI_SUCCESS then
                set_uc_iid()
                return ncsi_def.NCSI_SUCCESS
            end
        end
        skynet.sleep(10)
        set_uc_iid()
        ::continue::
    end
    return ncsi_def.NCSI_FAIL
end

-- 解析响应码
local last_unknown_resp_code = 0

function ncsi_utils.common_respcode_parse(rsp_code)
    local resp_messages = {
        [COMMAND_FAILED] = 'Valid command could not be processed or ' ..
            'failed to complete correctly.',
        [COMMAND_UNAVAILABLE] = 'Command is temporarily unavailable for execution ' ..
            'because the controller is in a transient state or busy condition',
        [COMMAND_UNSUPPORTED] = 'Command is not supported by the implementation.'
    }

    local message = resp_messages[rsp_code]
    if message then
        log:error(message)
        return
    end

    if rsp_code == last_unknown_resp_code then
        return
    end
    last_unknown_resp_code = rsp_code
    log:error('Unknown response code: 0x%02X', rsp_code)
end

-- 解析原因码
local last_unknown_reason_code = 0

function ncsi_utils.common_reasoncode_parse(reason_code)
    local reason_messages = {
        [NOERROR_OR_NOREASON] = 'No additional reason code information is being provided.',
        [INTERFACE_INIT_REQUIRED] = 'Channel is in the Initial State.',
        [PARAMETER_INVALID] = 'Received parameter value is outside of the acceptable values ' ..
            'for that parameter.',
        [CHANNEL_NOT_READY] = 'Channel is in a transient state in which it is unable ' ..
            'to process commands normally.',
        [PACKAGE_NOT_READY] = 'Package and channels within the package are in a transient state ' ..
            'in which normal command processing cannot be done.',
        [INVALID_PAYLOAD_LENGTH] = 'Payload length in the command is incorrect for the given command.',
        [UNSUPPORTED_COMMAND_TYPE] = 'Unknown or Unsupported Command Type.',
        [MAC_ADDRESS_IS_ZEOR] = 'Set MAC Address command is received with the MAC address set to 0'
    }

    local message = reason_messages[reason_code]
    if message then
        log:error(message)
        return
    end

    if reason_code == last_unknown_reason_code then
        return
    end
    last_unknown_reason_code = reason_code
    log:error('Unknown reason code: 0x%04X', reason_code)
end

-- 创建自定义命令处理表
function ncsi_utils.create_custom_cmd_table(base_cmd_table, cmd_type, write_func, ...)
    local params = {...}

    -- 直接在函数内复制表
    local custom_cmd_table = {}
    for k, v in pairs(base_cmd_table) do
        custom_cmd_table[k] = v
    end

    custom_cmd_table[cmd_type] = function(req, eth)
        if #params == 0 then
            return write_func(req, eth)
        else
            return write_func(req, eth, table.unpack(params))
        end
    end

    return custom_cmd_table
end

-- 创建自定义响应处理表
function ncsi_utils.create_custom_rsp_table(base_cmd_table, cmd_rsp_type, read_func, ...)
    local params = {...}

    -- 直接在函数内复制表
    local custom_cmd_table = {}
    for k, v in pairs(base_cmd_table) do
        custom_cmd_table[k] = v
    end

    custom_cmd_table[cmd_rsp_type] = function(rsp)
        if #params == 0 then
            return read_func(rsp)
        else
            return read_func(rsp, table.unpack(params))
        end
    end

    return custom_cmd_table
end

return ncsi_utils