--[[ portal.lua    zsk  2012-9-2 ]]
--[[ portal.lua    zwm  2015-09-23 ]]
--[[ Enhanced for Wireshark 4.x, full attribute parsing ]]

do
    local p_PORTAL = Proto("portal", "Portal Protocol")

    -- 报文类型映射（对应你截图的 Type）
    local typemap = {
        [1] = "REQ_CHALLENGE",
        [2] = "ACK_CHALLENGE",
        [3] = "REQ_AUTH",
        [4] = "ACK_AUTH",
        [5] = "REQ_LOGOUT",
        [6] = "ACK_LOGOUT",
        [7] = "AFFIRM_ACK_AUTH",
        [8] = "NOTIFY_LOGOUT",
        [9] = "REQ_INFO",
        [10] = "ACK_INFO",
        [11] = "NOTIFY_USER_DISCOVER",
        [12] = "NOTIFY_USER_IP_CHANGE",
        [13] = "AFFIRM_NOTIFY_USER_IP_CHANGE",
        [14] = "ACK_NOTIFY_LOGOUT"
    }

    -- 认证类型映射（对应 Pap/Chap）
    local authtypemap = {
        [0] = "CHAP",
        [1] = "PAP"
    }

    -- 属性类型（用户名/密码等）
    local attrtypemap = {
        [1] = "User-Name",
        [2] = "Password",
        [3] = "Challenge",
        [4] = "Response",
        [5] = "State",
        [6] = "Session-Timeout",
        [7] = "Termination-Action",
        [8] = "Service-Type",
        [9] = "Framed-Protocol",
        [10] = "Framed-IP-Address",
        [11] = "Framed-IP-Netmask",
        [12] = "Framed-Routing",
        [13] = "Filter-Id",
        [14] = "Framed-MTU",
        [15] = "Framed-Compression",
        [16] = "Login-IP-Host",
        [17] = "Login-Service",
        [18] = "Login-TCP-Port",
        [19] = "Reply-Message",
        [20] = "Callback-Number",
        [21] = "Callback-Id",
        [22] = "Framed-Route",
        [23] = "Framed-IPX-Network",
        [24] = "Class",
        [25] = "Vendor-Specific",
        [26] = "Session-Id",
        [27] = "Provider-Name",
        [28] = "Provider-Id",
        [29] = "Account-Session-Id",
        [30] = "Acct-Status-Type",
        [31] = "Acct-Delay-Time",
        [32] = "Acct-Input-Octets",
        [33] = "Acct-Output-Octets",
        [34] = "Acct-Session-Id",
        [35] = "Acct-Authentic",
        [36] = "Acct-Session-Time",
        [37] = "Acct-Input-Packets",
        [38] = "Acct-Output-Packets",
        [39] = "Acct-Terminate-Cause",
        [40] = "Acct-Multi-Session-Id",
        [41] = "Acct-Link-Count",
        [42] = "Acct-Gigawords",
        [43] = "Acct-Tunnel-Connection",
        [44] = "Acct-Tunnel-Packets-Lost",
        [45] = "Acct-Tunnel-Bytes-Lost",
        [46] = "Acct-Tunnel-Packets-Sent",
        [47] = "Acct-Tunnel-Bytes-Sent",
        [48] = "Acct-Tunnel-Packets-Received",
        [49] = "Acct-Tunnel-Bytes-Received",
        [50] = "Acct-Tunnel-Error-Packets",
        [51] = "Acct-Tunnel-Error-Bytes",
        [52] = "Acct-Tunnel-Discard-Packets",
        [53] = "Acct-Tunnel-Discard-Bytes",
        [54] = "Acct-Tunnel-Forward-Packets",
        [55] = "Acct-Tunnel-Forward-Bytes",
        [56] = "Acct-Tunnel-PDUs",
        [57] = "Acct-Tunnel-Errors",
        [58] = "Acct-Tunnel-Discards",
        [59] = "Acct-Tunnel-Forwards",
        [60] = "Acct-Tunnel-Receive-Errors",
        [61] = "Acct-Tunnel-Send-Errors",
        [62] = "Acct-Tunnel-No-Pads",
        [63] = "Acct-Tunnel-No-Pads-Bytes",
        [64] = "Acct-Tunnel-Padding",
        [65] = "Acct-Tunnel-Padding-Bytes",
        [66] = "Acct-Tunnel-Flags",
        [67] = "Acct-Tunnel-Send-Errors",
        [68] = "Acct-Tunnel-Receive-Errors",
        [69] = "Acct-Tunnel-No-Errors",
        [70] = "Acct-Tunnel-No-Errors-Bytes",
        [71] = "Acct-Tunnel-No-Discards",
        [72] = "Acct-Tunnel-No-Discards-Bytes",
        [73] = "Acct-Tunnel-No-Forwards",
        [74] = "Acct-Tunnel-No-Forwards-Bytes",
        [75] = "Acct-Tunnel-No-Send-Errors",
        [76] = "Acct-Tunnel-No-Send-Errors-Bytes",
        [77] = "Acct-Tunnel-No-Receive-Errors",
        [78] = "Acct-Tunnel-No-Receive-Errors-Bytes",
        [79] = "Acct-Tunnel-No-Padding",
        [80] = "Acct-Tunnel-No-Padding-Bytes"
    }

    -- 字段定义（全部用正确的参数）
    local f_version    = ProtoField.uint8("portal.version",   "Version", base.DEC)
    local f_type       = ProtoField.uint8("portal.type",        "Type", base.DEC, typemap)
    local f_authtype   = ProtoField.uint8("portal.authtype",    "Pap/Chap", base.DEC, authtypemap)
    local f_rsvd       = ProtoField.uint8("portal.rsvd",         "Rsvd", base.DEC)
    local f_serialno   = ProtoField.uint16("portal.serialno",    "SerialNo", base.HEX)
    local f_reqid      = ProtoField.uint16("portal.reqid",      "ReqID", base.HEX)
    local f_userip     = ProtoField.ipv4("portal.userip",        "UserIP")
    local f_userport   = ProtoField.uint16("portal.userport",    "UserPort", base.HEX)
    local f_errcode    = ProtoField.uint8("portal.errcode",      "ErrCode", base.DEC)
    local f_attrnum    = ProtoField.uint8("portal.attrnum",      "AttrNum", base.DEC)
    local f_authenticator = ProtoField.bytes("portal.authenticator", "Authenticator")
    local f_attr_type  = ProtoField.uint8("portal.attr.type",    "Attribute Type", base.DEC, attrtypemap)
    local f_attr_len   = ProtoField.uint8("portal.attr.len",     "Attribute Length", base.DEC)
    local f_attr_value = ProtoField.bytes("portal.attr.value",  "Attribute Value")

    p_PORTAL.fields = {
        f_version, f_type, f_authtype, f_rsvd,
        f_serialno, f_reqid, f_userip, f_userport,
        f_errcode, f_attrnum, f_authenticator,
        f_attr_type, f_attr_len, f_attr_value
    }

    -- 主解析函数
    local function p_PORTAL_dissector(buffer, pkt, root)
        local buf_len = buffer:len()
        if buf_len < 16 then return false end

        local portalTree = root:add(p_PORTAL, buffer())

        -- 基础字段解析
        local version = buffer(0,1):uint()
        local type = buffer(1,1):uint()
        local authtype = buffer(2,1):uint()
        local rsvd = buffer(3,1):uint()
        local serialno = buffer(4,2):uint()
        local reqid = buffer(6,2):uint()
        local userip = buffer(8,4)
        local userport = buffer(12,2):uint()
        local errcode = buffer(14,1):uint()
        local attrnum = buffer(15,1):uint()
        local authenticator = buffer(16,16)

        portalTree:add(f_version, buffer(0,1)):append_text(" (" .. version .. ")")
        portalTree:add(f_type, buffer(1,1)):append_text(" (" .. (typemap[type] or "Unknown") .. "("..type..")")
        portalTree:add(f_authtype, buffer(2,1)):append_text(" (" .. (authtypemap[authtype] or "Unknown") .. "("..authtype..")")
        portalTree:add(f_rsvd, buffer(3,1))
        portalTree:add(f_serialno, buffer(4,2))
        portalTree:add(f_reqid, buffer(6,2))
        portalTree:add(f_userip, buffer(8,4))
        portalTree:add(f_userport, buffer(12,2))
        portalTree:add(f_errcode, buffer(14,1))
        portalTree:add(f_attrnum, buffer(15,1))
        portalTree:add(f_authenticator, authenticator)

        -- 属性解析（用户名/密码）
        local offset = 32
        if buf_len > 32 then
            local attrTree = portalTree:add("Attributes:")
            while offset < buf_len do
                local attr_type = buffer(offset,1):uint()
                local attr_len = buffer(offset+1,1):uint()
                if offset + attr_len > buf_len then break end

                local attr_subtree = attrTree:add(f_attr_type, buffer(offset,1))
                attr_subtree:append_text(" (" .. (attrtypemap[attr_type] or "Unknown") .. ")")
                attr_subtree:add(f_attr_len, buffer(offset+1,1))

                if attr_len > 2 then
                    local value_buf = buffer(offset+2, attr_len-2)
                    if attr_type == 1 or attr_type == 2 then -- User-Name / Password
                        attr_subtree:add("Value: " .. value_buf:string())
                    else
                        attr_subtree:add(f_attr_value, value_buf)
                    end
                end

                offset = offset + attr_len
            end
        end

        -- 协议列信息
        pkt.cols.protocol = "Portal"
        pkt.cols.info = string.format("%s (Type:%d, ErrCode:%d)", typemap[type] or "Unknown", type, errcode)

        return true
    end

    function p_PORTAL.dissector(buf, pkt, root)
        if not p_PORTAL_dissector(buf, pkt, root) then
            local data_dis = Dissector.get("data")
            data_dis:call(buf, pkt, root)
        end
    end

    -- 注册到 UDP 2000
    local udp_port = DissectorTable.get("udp.port")
    udp_port:add(2000, p_PORTAL)
end