-- 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 bs = require 'mc.bitstring'
local log = require 'mc.logging'
local core = require 'network.core'
local ncsi_def = require 'ncsi.ncsi_protocol.ncsi_def'
local ncsi_utils = require 'ncsi.ncsi_protocol.ncsi_utils'
local ncsi_protocol_intf = require 'ncsi_protocol_intf'
local ncsi_packet = require 'ncsi.ncsi_protocol.ncsi_packet'

local ncsi_aen = {}

-- 存储外部注入的回调函数
local channel_init_callback = nil

local AEN_ENABLE_REQ = 0x08
local AEN_ENABLE_RSP = 0x88
local AEN_ENABLE_REQ_LEN = 8
local AEN_ENABLE = 1
local AEN_ENABLE_PKT_SIZE = 34

-- AEN TYPE
local CONFIGURATION_REQUIRED = 0x01
local OEM_NIC_CONFIGURATION_REINIT = 0x80

local AEN_PACKET_TYPE = 0xff
local AEN_PACKET_IID = 0

local aen_enable_req_bs = bs.new([[<<
    reserved1:3/string,
    mc_id:8,
    oem_ctl:16,
    reserved:8,
    link_status:1,
    config_req:1,
    drv_change:1,
    reserved2:5,
    check_sum:32,
    data:18/string,
    fcs:32
>>]])

local aen_packet_rsp_bs = bs.new([[<<
    reserved1:3/string,
    aen_type:8,
    optional_aen_data:32,
    check_sum:32
>>]])

local function fill_aen_enable_payload(req_packet, enable_flag)
    -- 定义公共的payload数据结构
    local payload_data = {
        reserved1 = string.rep('\0', 3),
        mc_id = 0,
        oem_ctl = 0,
        reserved = 0,
        link_status = enable_flag,
        config_req = enable_flag,
        drv_change = enable_flag,
        reserved2 = 0,
        check_sum = 0,  -- 初始值
        data = string.rep('\0', 18),
        fcs = 0         -- 初始值
    }

    -- 首先创建一个check_sum为0的初始payload
    req_packet.payload = aen_enable_req_bs:pack(payload_data)

    -- 计算校验和和CRC32
    local check_sum = ncsi_utils.get_checksum(req_packet, ncsi_def.PACKET_HEAD_LEN + AEN_ENABLE_REQ_LEN)
    local crc32 = ncsi_utils.get_crc32(req_packet, ncsi_def.PACKET_HEAD_LEN + AEN_ENABLE_PKT_SIZE - 4)

    -- 更新checksum和FCS字段
    payload_data.check_sum = core.htonl(check_sum)
    payload_data.fcs = core.htonl(crc32)

    -- 返回最终的payload
    return aen_enable_req_bs:pack(payload_data)
end

local function write_aen_enable_req(req_packet, eth_name, enable_flag)
    ncsi_utils.ncsi_cmd_common_config(req_packet)
    req_packet.packet_head.payload_len_hi = (AEN_ENABLE_REQ_LEN >> 8) & 0x0f
    req_packet.packet_head.payload_len_lo = AEN_ENABLE_REQ_LEN & 0xff
    req_packet.payload =  fill_aen_enable_payload(req_packet, enable_flag)

    local req_data = ncsi_utils.ncsi_packet_bs:pack(req_packet)
    return ncsi_protocol_intf.send_ncsi_cmd(req_data,
        AEN_ENABLE_PKT_SIZE + ncsi_def.ETHERNET_HEAD_LEN + ncsi_def.PACKET_HEAD_LEN, eth_name)
end

local function read_aen_enable_rsp(rsp)
    return ncsi_packet.read_common_rsp(rsp, 'aen enable')
end

-- Command table
local aen_enable_table = {
    [AEN_ENABLE_REQ] = write_aen_enable_req,
    [AEN_ENABLE_RSP] = read_aen_enable_rsp
}

-- 注册通道初始化回调函数
function ncsi_aen.register_channel_init_callback(callback_func)
    channel_init_callback = callback_func
end

function ncsi_aen.ncsi_aen_enable(package_id, channel_id, eth_name)
    local req_packet = ncsi_packet.create_request_packet(package_id, channel_id, AEN_ENABLE_REQ)

    local custom_cmd_table = ncsi_utils.create_custom_cmd_table(
        aen_enable_table, AEN_ENABLE_REQ, write_aen_enable_req, AEN_ENABLE
    )

    local ret = ncsi_utils.ncsi_cmd_ctrl(package_id, channel_id, req_packet, eth_name, custom_cmd_table)
    if ret ~= ncsi_def.NCSI_SUCCESS then
        log:error('ncsi cmd ctrl enable aen failed, package_id = %s, channel_id = %s, eth_name = %s',
            package_id, channel_id, eth_name)
    end
    return ret
end

local ncsi_aen_pkt_template = [[<<
    frame_head:1/ethernet_header,
    packet_head:1/ctl_packet_header,
    aen_packet_rsp:1/aen_packet
>>]]

local ncsi_aen_bs = bs.new(ncsi_aen_pkt_template, {
    ethernet_header = ncsi_def.ethernet_header_bs,
    ctl_packet_header = ncsi_def.ctl_packet_header_bs,
    aen_packet = aen_packet_rsp_bs
})

function ncsi_aen.ncsi_aen_packet_proc(data, eth_name)
    local rsp = ncsi_aen_bs:unpack(data, true)
    if not rsp then
        log:error('Failed to unpack AEN packet data')
        return false, 0
    end
    if rsp.packet_head.packet_type ~= AEN_PACKET_TYPE or rsp.packet_head.iid ~= AEN_PACKET_IID or
        rsp.packet_head.mc_id ~= ncsi_def.NCSI_MC_ID then
        return false, 0
    end

    log:notice('process ncsi aen packet')
    local aen_type = rsp.aen_packet_rsp.aen_type
    if aen_type == CONFIGURATION_REQUIRED then
        if channel_init_callback then
            channel_init_callback(rsp.packet_head.package_id, rsp.packet_head.channel_id, eth_name)
        end
        return true, 0
    end

    if aen_type == OEM_NIC_CONFIGURATION_REINIT then
        log:error('network_adapter, receive aen oem msg, need to reinit ncsi')
        return true, 1
    end

    return true, 0
end

return ncsi_aen