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

local bs = require 'mc.bitstring'
local utils = require 'mc.utils'
local bs_helper = require 'protocol_open.common.bs_helper'

local IANA_IP<const> = 1
local org_oui_byte<const> = '\x00\x80\xC2'

local chassis_id_subtype<const> = {
    CHASSIS_COMPONENT = 1,
    INTERFACE_ALIAS = 2,
    PORT_COMPONENT = 3,
    MAC_ADDRESS = 4,
    NETWORK_ADDRESS = 5,
    INTERFACE_NAME = 6,
    LOCALLY_ASSIGNED = 7
}

local chassis_id_subtype_name_switch<const> = {
    [chassis_id_subtype.CHASSIS_COMPONENT] = 'ChassisComp',
    [chassis_id_subtype.INTERFACE_ALIAS] = 'IfAlias',
    [chassis_id_subtype.PORT_COMPONENT] = 'PortComp',
    [chassis_id_subtype.MAC_ADDRESS] = 'MacAddr',
    [chassis_id_subtype.NETWORK_ADDRESS] = 'NetworkAddr',
    [chassis_id_subtype.INTERFACE_NAME] = 'IfName',
    [chassis_id_subtype.LOCALLY_ASSIGNED] = 'LocalAssign'
}

local port_id_subtype<const> = {
    INTERFACE_ALIAS = 1,
    PORT_COMPONENT = 2,
    MAC_ADDRESS = 3,
    NETWORK_ADDRESS = 4,
    INTERFACE_NAME = 5,
    AGENT_CIRCUIT_ID = 6,
    LOCALLY_ASSIGNED = 7
}

local port_id_subtype_name_switch<const> = {
    [port_id_subtype.INTERFACE_ALIAS] = 'IfAlias',
    [port_id_subtype.PORT_COMPONENT] = 'PortComp',
    [port_id_subtype.MAC_ADDRESS] = 'MacAddr',
    [port_id_subtype.NETWORK_ADDRESS] = 'NetworkAddr',
    [port_id_subtype.INTERFACE_NAME] = 'IfName',
    [port_id_subtype.AGENT_CIRCUIT_ID] = 'AgentId',
    [port_id_subtype.LOCALLY_ASSIGNED] = 'LocalAssign'
}

local IEEE_subtype<const> = {
    PORT_VLAN_ID = 1,
    PORT_AND_PROTOCOL_VLAN_ID = 2,
    VLAN_NAME = 3,
    PROTOCOL_IDENTITY = 4,
    VID_USAGE_DIGEST = 5,
    MANAGEMENT_VID = 6,
    LINK_AGGREGATION = 7
}

local ttl_bs = bs.new([[<<
    ttl:16/big
>>]])

local mac_address_bs = bs.new([[<<
    addr:1/MAC_ADDRESS
>>]], bs_helper)

local ipv4_bs = bs.new([[<<
    addr:1/IPV4
>>]], bs_helper)

local vlan_id_bs = bs.new([[<<
    id:16/big
>>]])

local function chassis_id_tlv_parser(info_string)
    local subtype = string.unpack('I1', info_string:sub(1, 1))
    if not chassis_id_subtype_name_switch[subtype] then
        error(string.format('unknown chassis id sub type received from LLDP. sub_type: %d', subtype))
    end
    local info_string_data = info_string:sub(2)
    local chassis_id = ''

    if subtype == chassis_id_subtype.MAC_ADDRESS then
        chassis_id = mac_address_bs:unpack(info_string_data, true).addr.mac_address
    elseif subtype == chassis_id_subtype.NETWORK_ADDRESS then
        local r = bs.new([[<<
            addr_len:8,
            subtype:8
        >>]]):unpack(info_string_data, true)
        -- 目前只处理IPv4
        if r.subtype == IANA_IP and r.addr_len == 5 then
            chassis_id = ipv4_bs:unpack(info_string_data:sub(3), true).addr.ipv4
        end
    else
        chassis_id = info_string_data
    end
    return {chassis_id = chassis_id, chassis_id_subtype = chassis_id_subtype_name_switch[subtype]}
end

local function port_id_tlv_parser(info_string)
    local subtype = string.unpack('I1', info_string:sub(1, 1))
    if not port_id_subtype_name_switch[subtype] then
        error(string.format('unknown port id sub type received from LLDP. sub_type: %d', subtype))
    end
    local info_string_data = info_string:sub(2)
    local port_id = ''

    if subtype == port_id_subtype.MAC_ADDRESS then
        port_id = mac_address_bs:unpack(info_string_data, true).addr.mac_address
    elseif subtype == port_id_subtype.NETWORK_ADDRESS then
        local r = bs.new([[<<
            addr_len:8,
            subtype:8
        >>]]):unpack(info_string_data, true)
        -- 目前只处理IPv4
        if r.subtype == IANA_IP and r.addr_len == 5 then
            port_id = ipv4_bs:unpack(info_string_data:sub(3), true).addr.ipv4
        end
    else
        port_id = info_string_data
    end
    return {port_id = port_id, port_id_subtype = port_id_subtype_name_switch[subtype]}
end

local function ttl_tlv_parser(info_string)
    return ttl_bs:unpack(info_string, true)
end

local function system_name_tlv_parser(info_string)
    if #info_string > 512 then
        info_string = info_string:sub(1, 512)
    end
    return {system_name = info_string}
end

local function org_specific_tlv_parser(info_string)
    if info_string:sub(1, 3) ~= org_oui_byte then
        return -- 如果org_oui不是00 80 C2不解析
    end
    local subtype = string.unpack('I1', info_string:sub(4, 4))
    if subtype == IEEE_subtype.PORT_VLAN_ID then
        local r = vlan_id_bs:unpack(info_string:sub(5, 6), true)
        if r.id == 0 then
            r.id = 0xFFFF -- 遵循v2处理，当vlan id为0时设成无效值
        end
        return {vlan_id = r.id}
    elseif subtype <= IEEE_subtype.LINK_AGGREGATION then
        return -- IEEE现有的类型不处理，也不更新vlan id
    end
end

local tlv_type<const> = {
    CHASSIS_ID_TLV = 1,
    PORT_ID_TLV = 2,
    TTL_TLV = 3,
    SYSTEM_NAME_TLV = 5,
    ORG_SPECIFIC_TLV = 127
}

local tlv_parser_switch<const> = {
    [tlv_type.CHASSIS_ID_TLV] = chassis_id_tlv_parser,
    [tlv_type.PORT_ID_TLV] = port_id_tlv_parser,
    [tlv_type.TTL_TLV] = ttl_tlv_parser,
    [tlv_type.SYSTEM_NAME_TLV] = system_name_tlv_parser,
    [tlv_type.ORG_SPECIFIC_TLV] = org_specific_tlv_parser
}

local function lldp_tlv_parser(tlv)
    if type(tlv) ~= 'table' or not tlv.tlv_type or not tlv.tlv_info_string then
        error('invalid tlv input')
    end

    -- 只支持已有的tlv结构，其他的返回nil
    if tlv_parser_switch[tlv.tlv_type] then
        return tlv_parser_switch[tlv.tlv_type](tlv.tlv_info_string)
    end
end

return lldp_tlv_parser
