// Copyright 2020 The GoPacket Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be found
// in the LICENSE file in the root of the source tree.

package layers

import (
	"encoding/binary"
	"fmt"

	"github.com/gopacket/gopacket"
)

const (
	// RFC 2865 3.  Packet Format
	// `The minimum length is 20 and maximum length is 4096.`
	radiusMinimumRecordSizeInBytes int = 20
	radiusMaximumRecordSizeInBytes int = 4096

	// RFC 2865 5.  Attributes
	// `The Length field is one octet, and indicates the length of this Attribute including the Type, Length and Value fields.`
	// `The Value field is zero or more octets and contains information specific to the Attribute.`
	radiusAttributesMinimumRecordSizeInBytes int = 2
)

// RADIUS represents a Remote Authentication Dial In User Service layer.
type RADIUS struct {
	BaseLayer

	Code          RADIUSCode
	Identifier    RADIUSIdentifier
	Length        RADIUSLength
	Authenticator RADIUSAuthenticator
	Attributes    []RADIUSAttribute
}

// RADIUSCode represents packet type.
type RADIUSCode uint8

// constants that define RADIUSCode.
const (
	RADIUSCodeAccessRequest      RADIUSCode = 1   // RFC2865 3.  Packet Format
	RADIUSCodeAccessAccept       RADIUSCode = 2   // RFC2865 3.  Packet Format
	RADIUSCodeAccessReject       RADIUSCode = 3   // RFC2865 3.  Packet Format
	RADIUSCodeAccountingRequest  RADIUSCode = 4   // RFC2865 3.  Packet Format
	RADIUSCodeAccountingResponse RADIUSCode = 5   // RFC2865 3.  Packet Format
	RADIUSCodeAccessChallenge    RADIUSCode = 11  // RFC2865 3.  Packet Format
	RADIUSCodeStatusServer       RADIUSCode = 12  // RFC2865 3.  Packet Format (experimental)
	RADIUSCodeStatusClient       RADIUSCode = 13  // RFC2865 3.  Packet Format (experimental)
	RADIUSCodeReserved           RADIUSCode = 255 // RFC2865 3.  Packet Format
)

// String returns a string version of a RADIUSCode.
func (t RADIUSCode) String() (s string) {
	switch t {
	case RADIUSCodeAccessRequest:
		s = "Access-Request"
	case RADIUSCodeAccessAccept:
		s = "Access-Accept"
	case RADIUSCodeAccessReject:
		s = "Access-Reject"
	case RADIUSCodeAccountingRequest:
		s = "Accounting-Request"
	case RADIUSCodeAccountingResponse:
		s = "Accounting-Response"
	case RADIUSCodeAccessChallenge:
		s = "Access-Challenge"
	case RADIUSCodeStatusServer:
		s = "Status-Server"
	case RADIUSCodeStatusClient:
		s = "Status-Client"
	case RADIUSCodeReserved:
		s = "Reserved"
	default:
		s = fmt.Sprintf("Unknown(%d)", t)
	}
	return
}

// RADIUSIdentifier represents packet identifier.
type RADIUSIdentifier uint8

// RADIUSLength represents packet length.
type RADIUSLength uint16

// RADIUSAuthenticator represents authenticator.
type RADIUSAuthenticator [16]byte

// RADIUSAttribute represents attributes.
type RADIUSAttribute struct {
	Type   RADIUSAttributeType
	Length RADIUSAttributeLength
	Value  RADIUSAttributeValue
}

// RADIUSAttributeType represents attribute type.
type RADIUSAttributeType uint8

// constants that define RADIUSAttributeType.
const (
	RADIUSAttributeTypeUserName               RADIUSAttributeType = 1  // RFC2865  5.1.  User-Name
	RADIUSAttributeTypeUserPassword           RADIUSAttributeType = 2  // RFC2865  5.2.  User-Password
	RADIUSAttributeTypeCHAPPassword           RADIUSAttributeType = 3  // RFC2865  5.3.  CHAP-Password
	RADIUSAttributeTypeNASIPAddress           RADIUSAttributeType = 4  // RFC2865  5.4.  NAS-IP-Address
	RADIUSAttributeTypeNASPort                RADIUSAttributeType = 5  // RFC2865  5.5.  NAS-Port
	RADIUSAttributeTypeServiceType            RADIUSAttributeType = 6  // RFC2865  5.6.  Service-Type
	RADIUSAttributeTypeFramedProtocol         RADIUSAttributeType = 7  // RFC2865  5.7.  Framed-Protocol
	RADIUSAttributeTypeFramedIPAddress        RADIUSAttributeType = 8  // RFC2865  5.8.  Framed-IP-Address
	RADIUSAttributeTypeFramedIPNetmask        RADIUSAttributeType = 9  // RFC2865  5.9.  Framed-IP-Netmask
	RADIUSAttributeTypeFramedRouting          RADIUSAttributeType = 10 // RFC2865 5.10.  Framed-Routing
	RADIUSAttributeTypeFilterId               RADIUSAttributeType = 11 // RFC2865 5.11.  Filter-Id
	RADIUSAttributeTypeFramedMTU              RADIUSAttributeType = 12 // RFC2865 5.12.  Framed-MTU
	RADIUSAttributeTypeFramedCompression      RADIUSAttributeType = 13 // RFC2865 5.13.  Framed-Compression
	RADIUSAttributeTypeLoginIPHost            RADIUSAttributeType = 14 // RFC2865 5.14.  Login-IP-Host
	RADIUSAttributeTypeLoginService           RADIUSAttributeType = 15 // RFC2865 5.15.  Login-Service
	RADIUSAttributeTypeLoginTCPPort           RADIUSAttributeType = 16 // RFC2865 5.16.  Login-TCP-Port
	RADIUSAttributeTypeReplyMessage           RADIUSAttributeType = 18 // RFC2865 5.18.  Reply-Message
	RADIUSAttributeTypeCallbackNumber         RADIUSAttributeType = 19 // RFC2865 5.19.  Callback-Number
	RADIUSAttributeTypeCallbackId             RADIUSAttributeType = 20 // RFC2865 5.20.  Callback-Id
	RADIUSAttributeTypeFramedRoute            RADIUSAttributeType = 22 // RFC2865 5.22.  Framed-Route
	RADIUSAttributeTypeFramedIPXNetwork       RADIUSAttributeType = 23 // RFC2865 5.23.  Framed-IPX-Network
	RADIUSAttributeTypeState                  RADIUSAttributeType = 24 // RFC2865 5.24.  State
	RADIUSAttributeTypeClass                  RADIUSAttributeType = 25 // RFC2865 5.25.  Class
	RADIUSAttributeTypeVendorSpecific         RADIUSAttributeType = 26 // RFC2865 5.26.  Vendor-Specific
	RADIUSAttributeTypeSessionTimeout         RADIUSAttributeType = 27 // RFC2865 5.27.  Session-Timeout
	RADIUSAttributeTypeIdleTimeout            RADIUSAttributeType = 28 // RFC2865 5.28.  Idle-Timeout
	RADIUSAttributeTypeTerminationAction      RADIUSAttributeType = 29 // RFC2865 5.29.  Termination-Action
	RADIUSAttributeTypeCalledStationId        RADIUSAttributeType = 30 // RFC2865 5.30.  Called-Station-Id
	RADIUSAttributeTypeCallingStationId       RADIUSAttributeType = 31 // RFC2865 5.31.  Calling-Station-Id
	RADIUSAttributeTypeNASIdentifier          RADIUSAttributeType = 32 // RFC2865 5.32.  NAS-Identifier
	RADIUSAttributeTypeProxyState             RADIUSAttributeType = 33 // RFC2865 5.33.  Proxy-State
	RADIUSAttributeTypeLoginLATService        RADIUSAttributeType = 34 // RFC2865 5.34.  Login-LAT-Service
	RADIUSAttributeTypeLoginLATNode           RADIUSAttributeType = 35 // RFC2865 5.35.  Login-LAT-Node
	RADIUSAttributeTypeLoginLATGroup          RADIUSAttributeType = 36 // RFC2865 5.36.  Login-LAT-Group
	RADIUSAttributeTypeFramedAppleTalkLink    RADIUSAttributeType = 37 // RFC2865 5.37.  Framed-AppleTalk-Link
	RADIUSAttributeTypeFramedAppleTalkNetwork RADIUSAttributeType = 38 // RFC2865 5.38.  Framed-AppleTalk-Network
	RADIUSAttributeTypeFramedAppleTalkZone    RADIUSAttributeType = 39 // RFC2865 5.39.  Framed-AppleTalk-Zone
	RADIUSAttributeTypeAcctStatusType         RADIUSAttributeType = 40 // RFC2866  5.1.  Acct-Status-Type
	RADIUSAttributeTypeAcctDelayTime          RADIUSAttributeType = 41 // RFC2866  5.2.  Acct-Delay-Time
	RADIUSAttributeTypeAcctInputOctets        RADIUSAttributeType = 42 // RFC2866  5.3.  Acct-Input-Octets
	RADIUSAttributeTypeAcctOutputOctets       RADIUSAttributeType = 43 // RFC2866  5.4.  Acct-Output-Octets
	RADIUSAttributeTypeAcctSessionId          RADIUSAttributeType = 44 // RFC2866  5.5.  Acct-Session-Id
	RADIUSAttributeTypeAcctAuthentic          RADIUSAttributeType = 45 // RFC2866  5.6.  Acct-Authentic
	RADIUSAttributeTypeAcctSessionTime        RADIUSAttributeType = 46 // RFC2866  5.7.  Acct-Session-Time
	RADIUSAttributeTypeAcctInputPackets       RADIUSAttributeType = 47 // RFC2866  5.8.  Acct-Input-Packets
	RADIUSAttributeTypeAcctOutputPackets      RADIUSAttributeType = 48 // RFC2866  5.9.  Acct-Output-Packets
	RADIUSAttributeTypeAcctTerminateCause     RADIUSAttributeType = 49 // RFC2866 5.10.  Acct-Terminate-Cause
	RADIUSAttributeTypeAcctMultiSessionId     RADIUSAttributeType = 50 // RFC2866 5.11.  Acct-Multi-Session-Id
	RADIUSAttributeTypeAcctLinkCount          RADIUSAttributeType = 51 // RFC2866 5.12.  Acct-Link-Count
	RADIUSAttributeTypeAcctInputGigawords     RADIUSAttributeType = 52 // RFC2869  5.1.  Acct-Input-Gigawords
	RADIUSAttributeTypeAcctOutputGigawords    RADIUSAttributeType = 53 // RFC2869  5.2.  Acct-Output-Gigawords
	RADIUSAttributeTypeEventTimestamp         RADIUSAttributeType = 55 // RFC2869  5.3.  Event-Timestamp
	RADIUSAttributeTypeCHAPChallenge          RADIUSAttributeType = 60 // RFC2865 5.40.  CHAP-Challenge
	RADIUSAttributeTypeNASPortType            RADIUSAttributeType = 61 // RFC2865 5.41.  NAS-Port-Type
	RADIUSAttributeTypePortLimit              RADIUSAttributeType = 62 // RFC2865 5.42.  Port-Limit
	RADIUSAttributeTypeLoginLATPort           RADIUSAttributeType = 63 // RFC2865 5.43.  Login-LAT-Port
	RADIUSAttributeTypeTunnelType             RADIUSAttributeType = 64 // RFC2868  3.1.  Tunnel-Type
	RADIUSAttributeTypeTunnelMediumType       RADIUSAttributeType = 65 // RFC2868  3.2.  Tunnel-Medium-Type
	RADIUSAttributeTypeTunnelClientEndpoint   RADIUSAttributeType = 66 // RFC2868  3.3.  Tunnel-Client-Endpoint
	RADIUSAttributeTypeTunnelServerEndpoint   RADIUSAttributeType = 67 // RFC2868  3.4.  Tunnel-Server-Endpoint
	RADIUSAttributeTypeAcctTunnelConnection   RADIUSAttributeType = 68 // RFC2867  4.1.  Acct-Tunnel-Connection
	RADIUSAttributeTypeTunnelPassword         RADIUSAttributeType = 69 // RFC2868  3.5.  Tunnel-Password
	RADIUSAttributeTypeARAPPassword           RADIUSAttributeType = 70 // RFC2869  5.4.  ARAP-Password
	RADIUSAttributeTypeARAPFeatures           RADIUSAttributeType = 71 // RFC2869  5.5.  ARAP-Features
	RADIUSAttributeTypeARAPZoneAccess         RADIUSAttributeType = 72 // RFC2869  5.6.  ARAP-Zone-Access
	RADIUSAttributeTypeARAPSecurity           RADIUSAttributeType = 73 // RFC2869  5.7.  ARAP-Security
	RADIUSAttributeTypeARAPSecurityData       RADIUSAttributeType = 74 // RFC2869  5.8.  ARAP-Security-Data
	RADIUSAttributeTypePasswordRetry          RADIUSAttributeType = 75 // RFC2869  5.9.  Password-Retry
	RADIUSAttributeTypePrompt                 RADIUSAttributeType = 76 // RFC2869 5.10.  Prompt
	RADIUSAttributeTypeConnectInfo            RADIUSAttributeType = 77 // RFC2869 5.11.  Connect-Info
	RADIUSAttributeTypeConfigurationToken     RADIUSAttributeType = 78 // RFC2869 5.12.  Configuration-Token
	RADIUSAttributeTypeEAPMessage             RADIUSAttributeType = 79 // RFC2869 5.13.  EAP-Message
	RADIUSAttributeTypeMessageAuthenticator   RADIUSAttributeType = 80 // RFC2869 5.14.  Message-Authenticator
	RADIUSAttributeTypeTunnelPrivateGroupID   RADIUSAttributeType = 81 // RFC2868  3.6.  Tunnel-Private-Group-ID
	RADIUSAttributeTypeTunnelAssignmentID     RADIUSAttributeType = 82 // RFC2868  3.7.  Tunnel-Assignment-ID
	RADIUSAttributeTypeTunnelPreference       RADIUSAttributeType = 83 // RFC2868  3.8.  Tunnel-Preference
	RADIUSAttributeTypeARAPChallengeResponse  RADIUSAttributeType = 84 // RFC2869 5.15.  ARAP-Challenge-Response
	RADIUSAttributeTypeAcctInterimInterval    RADIUSAttributeType = 85 // RFC2869 5.16.  Acct-Interim-Interval
	RADIUSAttributeTypeAcctTunnelPacketsLost  RADIUSAttributeType = 86 // RFC2867  4.2.  Acct-Tunnel-Packets-Lost
	RADIUSAttributeTypeNASPortId              RADIUSAttributeType = 87 // RFC2869 5.17.  NAS-Port-Id
	RADIUSAttributeTypeFramedPool             RADIUSAttributeType = 88 // RFC2869 5.18.  Framed-Pool
	RADIUSAttributeTypeTunnelClientAuthID     RADIUSAttributeType = 90 // RFC2868  3.9.  Tunnel-Client-Auth-ID
	RADIUSAttributeTypeTunnelServerAuthID     RADIUSAttributeType = 91 // RFC2868 3.10.  Tunnel-Server-Auth-ID
)

// RADIUSAttributeType represents attribute length.
type RADIUSAttributeLength uint8

// RADIUSAttributeType represents attribute value.
type RADIUSAttributeValue []byte

// String returns a string version of a RADIUSAttributeType.
func (t RADIUSAttributeType) String() (s string) {
	switch t {
	case RADIUSAttributeTypeUserName:
		s = "User-Name"
	case RADIUSAttributeTypeUserPassword:
		s = "User-Password"
	case RADIUSAttributeTypeCHAPPassword:
		s = "CHAP-Password"
	case RADIUSAttributeTypeNASIPAddress:
		s = "NAS-IP-Address"
	case RADIUSAttributeTypeNASPort:
		s = "NAS-Port"
	case RADIUSAttributeTypeServiceType:
		s = "Service-Type"
	case RADIUSAttributeTypeFramedProtocol:
		s = "Framed-Protocol"
	case RADIUSAttributeTypeFramedIPAddress:
		s = "Framed-IP-Address"
	case RADIUSAttributeTypeFramedIPNetmask:
		s = "Framed-IP-Netmask"
	case RADIUSAttributeTypeFramedRouting:
		s = "Framed-Routing"
	case RADIUSAttributeTypeFilterId:
		s = "Filter-Id"
	case RADIUSAttributeTypeFramedMTU:
		s = "Framed-MTU"
	case RADIUSAttributeTypeFramedCompression:
		s = "Framed-Compression"
	case RADIUSAttributeTypeLoginIPHost:
		s = "Login-IP-Host"
	case RADIUSAttributeTypeLoginService:
		s = "Login-Service"
	case RADIUSAttributeTypeLoginTCPPort:
		s = "Login-TCP-Port"
	case RADIUSAttributeTypeReplyMessage:
		s = "Reply-Message"
	case RADIUSAttributeTypeCallbackNumber:
		s = "Callback-Number"
	case RADIUSAttributeTypeCallbackId:
		s = "Callback-Id"
	case RADIUSAttributeTypeFramedRoute:
		s = "Framed-Route"
	case RADIUSAttributeTypeFramedIPXNetwork:
		s = "Framed-IPX-Network"
	case RADIUSAttributeTypeState:
		s = "State"
	case RADIUSAttributeTypeClass:
		s = "Class"
	case RADIUSAttributeTypeVendorSpecific:
		s = "Vendor-Specific"
	case RADIUSAttributeTypeSessionTimeout:
		s = "Session-Timeout"
	case RADIUSAttributeTypeIdleTimeout:
		s = "Idle-Timeout"
	case RADIUSAttributeTypeTerminationAction:
		s = "Termination-Action"
	case RADIUSAttributeTypeCalledStationId:
		s = "Called-Station-Id"
	case RADIUSAttributeTypeCallingStationId:
		s = "Calling-Station-Id"
	case RADIUSAttributeTypeNASIdentifier:
		s = "NAS-Identifier"
	case RADIUSAttributeTypeProxyState:
		s = "Proxy-State"
	case RADIUSAttributeTypeLoginLATService:
		s = "Login-LAT-Service"
	case RADIUSAttributeTypeLoginLATNode:
		s = "Login-LAT-Node"
	case RADIUSAttributeTypeLoginLATGroup:
		s = "Login-LAT-Group"
	case RADIUSAttributeTypeFramedAppleTalkLink:
		s = "Framed-AppleTalk-Link"
	case RADIUSAttributeTypeFramedAppleTalkNetwork:
		s = "Framed-AppleTalk-Network"
	case RADIUSAttributeTypeFramedAppleTalkZone:
		s = "Framed-AppleTalk-Zone"
	case RADIUSAttributeTypeAcctStatusType:
		s = "Acct-Status-Type"
	case RADIUSAttributeTypeAcctDelayTime:
		s = "Acct-Delay-Time"
	case RADIUSAttributeTypeAcctInputOctets:
		s = "Acct-Input-Octets"
	case RADIUSAttributeTypeAcctOutputOctets:
		s = "Acct-Output-Octets"
	case RADIUSAttributeTypeAcctSessionId:
		s = "Acct-Session-Id"
	case RADIUSAttributeTypeAcctAuthentic:
		s = "Acct-Authentic"
	case RADIUSAttributeTypeAcctSessionTime:
		s = "Acct-Session-Time"
	case RADIUSAttributeTypeAcctInputPackets:
		s = "Acct-Input-Packets"
	case RADIUSAttributeTypeAcctOutputPackets:
		s = "Acct-Output-Packets"
	case RADIUSAttributeTypeAcctTerminateCause:
		s = "Acct-Terminate-Cause"
	case RADIUSAttributeTypeAcctMultiSessionId:
		s = "Acct-Multi-Session-Id"
	case RADIUSAttributeTypeAcctLinkCount:
		s = "Acct-Link-Count"
	case RADIUSAttributeTypeAcctInputGigawords:
		s = "Acct-Input-Gigawords"
	case RADIUSAttributeTypeAcctOutputGigawords:
		s = "Acct-Output-Gigawords"
	case RADIUSAttributeTypeEventTimestamp:
		s = "Event-Timestamp"
	case RADIUSAttributeTypeCHAPChallenge:
		s = "CHAP-Challenge"
	case RADIUSAttributeTypeNASPortType:
		s = "NAS-Port-Type"
	case RADIUSAttributeTypePortLimit:
		s = "Port-Limit"
	case RADIUSAttributeTypeLoginLATPort:
		s = "Login-LAT-Port"
	case RADIUSAttributeTypeTunnelType:
		s = "Tunnel-Type"
	case RADIUSAttributeTypeTunnelMediumType:
		s = "Tunnel-Medium-Type"
	case RADIUSAttributeTypeTunnelClientEndpoint:
		s = "Tunnel-Client-Endpoint"
	case RADIUSAttributeTypeTunnelServerEndpoint:
		s = "Tunnel-Server-Endpoint"
	case RADIUSAttributeTypeAcctTunnelConnection:
		s = "Acct-Tunnel-Connection"
	case RADIUSAttributeTypeTunnelPassword:
		s = "Tunnel-Password"
	case RADIUSAttributeTypeARAPPassword:
		s = "ARAP-Password"
	case RADIUSAttributeTypeARAPFeatures:
		s = "ARAP-Features"
	case RADIUSAttributeTypeARAPZoneAccess:
		s = "ARAP-Zone-Access"
	case RADIUSAttributeTypeARAPSecurity:
		s = "ARAP-Security"
	case RADIUSAttributeTypeARAPSecurityData:
		s = "ARAP-Security-Data"
	case RADIUSAttributeTypePasswordRetry:
		s = "Password-Retry"
	case RADIUSAttributeTypePrompt:
		s = "Prompt"
	case RADIUSAttributeTypeConnectInfo:
		s = "Connect-Info"
	case RADIUSAttributeTypeConfigurationToken:
		s = "Configuration-Token"
	case RADIUSAttributeTypeEAPMessage:
		s = "EAP-Message"
	case RADIUSAttributeTypeMessageAuthenticator:
		s = "Message-Authenticator"
	case RADIUSAttributeTypeTunnelPrivateGroupID:
		s = "Tunnel-Private-Group-ID"
	case RADIUSAttributeTypeTunnelAssignmentID:
		s = "Tunnel-Assignment-ID"
	case RADIUSAttributeTypeTunnelPreference:
		s = "Tunnel-Preference"
	case RADIUSAttributeTypeARAPChallengeResponse:
		s = "ARAP-Challenge-Response"
	case RADIUSAttributeTypeAcctInterimInterval:
		s = "Acct-Interim-Interval"
	case RADIUSAttributeTypeAcctTunnelPacketsLost:
		s = "Acct-Tunnel-Packets-Lost"
	case RADIUSAttributeTypeNASPortId:
		s = "NAS-Port-Id"
	case RADIUSAttributeTypeFramedPool:
		s = "Framed-Pool"
	case RADIUSAttributeTypeTunnelClientAuthID:
		s = "Tunnel-Client-Auth-ID"
	case RADIUSAttributeTypeTunnelServerAuthID:
		s = "Tunnel-Server-Auth-ID"
	default:
		s = fmt.Sprintf("Unknown(%d)", t)
	}
	return
}

// Len returns the length of a RADIUS packet.
func (radius *RADIUS) Len() (int, error) {
	n := radiusMinimumRecordSizeInBytes
	for _, v := range radius.Attributes {
		alen, err := attributeValueLength(v.Value)
		if err != nil {
			return 0, err
		}
		n += int(alen) + 2 // Added Type and Length
	}
	return n, nil
}

// LayerType returns LayerTypeRADIUS.
func (radius *RADIUS) LayerType() gopacket.LayerType {
	return LayerTypeRADIUS
}

// DecodeFromBytes decodes the given bytes into this layer.
func (radius *RADIUS) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error {
	if len(data) > radiusMaximumRecordSizeInBytes {
		df.SetTruncated()
		return fmt.Errorf("RADIUS length %d too big", len(data))
	}

	if len(data) < radiusMinimumRecordSizeInBytes {
		df.SetTruncated()
		return fmt.Errorf("RADIUS length %d too short", len(data))
	}

	radius.BaseLayer = BaseLayer{Contents: data}

	radius.Code = RADIUSCode(data[0])
	radius.Identifier = RADIUSIdentifier(data[1])
	radius.Length = RADIUSLength(binary.BigEndian.Uint16(data[2:4]))

	if int(radius.Length) > radiusMaximumRecordSizeInBytes {
		df.SetTruncated()
		return fmt.Errorf("RADIUS length %d too big", radius.Length)
	}

	if int(radius.Length) < radiusMinimumRecordSizeInBytes {
		df.SetTruncated()
		return fmt.Errorf("RADIUS length %d too short", radius.Length)
	}

	// RFC 2865 3.  Packet Format
	// `If the packet is shorter than the Length field indicates, it MUST be silently discarded.`
	if int(radius.Length) > len(data) {
		df.SetTruncated()
		return fmt.Errorf("RADIUS length %d too big", radius.Length)
	}

	// RFC 2865 3.  Packet Format
	// `Octets outside the range of the Length field MUST be treated as padding and ignored on reception.`
	if int(radius.Length) < len(data) {
		df.SetTruncated()
		data = data[:radius.Length]
	}

	copy(radius.Authenticator[:], data[4:20])

	if len(data) == radiusMinimumRecordSizeInBytes {
		return nil
	}

	pos := radiusMinimumRecordSizeInBytes
	for {
		if len(data) == pos {
			break
		}

		if len(data[pos:]) < radiusAttributesMinimumRecordSizeInBytes {
			df.SetTruncated()
			return fmt.Errorf("RADIUS attributes length %d too short", len(data[pos:]))
		}

		attr := RADIUSAttribute{}
		attr.Type = RADIUSAttributeType(data[pos])
		attr.Length = RADIUSAttributeLength(data[pos+1])

		if int(attr.Length) > len(data[pos:]) {
			df.SetTruncated()
			return fmt.Errorf("RADIUS attributes length %d too big", attr.Length)
		}

		if int(attr.Length) < radiusAttributesMinimumRecordSizeInBytes {
			df.SetTruncated()
			return fmt.Errorf("RADIUS attributes length %d too short", attr.Length)
		}

		if int(attr.Length) > radiusAttributesMinimumRecordSizeInBytes {
			attr.Value = make([]byte, attr.Length-2)
			copy(attr.Value[:], data[pos+2:pos+int(attr.Length)])
			radius.Attributes = append(radius.Attributes, attr)
		}

		pos += int(attr.Length)
	}

	for _, v := range radius.Attributes {
		if v.Type == RADIUSAttributeTypeEAPMessage {
			radius.BaseLayer.Payload = append(radius.BaseLayer.Payload, v.Value...)
		}
	}

	return nil
}

// SerializeTo writes the serialized form of this layer into the
// SerializationBuffer, implementing gopacket.SerializableLayer.
// See the docs for gopacket.SerializableLayer for more info.
func (radius *RADIUS) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error {
	plen, err := radius.Len()
	if err != nil {
		return err
	}

	if opts.FixLengths {
		radius.Length = RADIUSLength(plen)
	}

	data, err := b.PrependBytes(plen)
	if err != nil {
		return err
	}

	data[0] = byte(radius.Code)
	data[1] = byte(radius.Identifier)
	binary.BigEndian.PutUint16(data[2:], uint16(radius.Length))
	copy(data[4:20], radius.Authenticator[:])

	pos := radiusMinimumRecordSizeInBytes
	for _, v := range radius.Attributes {
		if opts.FixLengths {
			v.Length, err = attributeValueLength(v.Value)
			if err != nil {
				return err
			}
		}

		data[pos] = byte(v.Type)
		data[pos+1] = byte(v.Length)
		copy(data[pos+2:], v.Value[:])

		pos += len(v.Value) + 2 // Added Type and Length
	}

	return nil
}

// CanDecode returns the set of layer types that this DecodingLayer can decode.
func (radius *RADIUS) CanDecode() gopacket.LayerClass {
	return LayerTypeRADIUS
}

// NextLayerType returns the layer type contained by this DecodingLayer.
func (radius *RADIUS) NextLayerType() gopacket.LayerType {
	if len(radius.BaseLayer.Payload) > 0 {
		return LayerTypeEAP
	} else {
		return gopacket.LayerTypeZero
	}
}

// Payload returns the EAP Type-Data for EAP-Message attributes.
func (radius *RADIUS) Payload() []byte {
	return radius.BaseLayer.Payload
}

func decodeRADIUS(data []byte, p gopacket.PacketBuilder) error {
	radius := &RADIUS{}
	err := radius.DecodeFromBytes(data, p)
	if err != nil {
		return err
	}
	p.AddLayer(radius)
	p.SetApplicationLayer(radius)
	next := radius.NextLayerType()
	if next == gopacket.LayerTypeZero {
		return nil
	}
	return p.NextDecoder(next)
}

func attributeValueLength(v []byte) (RADIUSAttributeLength, error) {
	n := len(v)
	if n > 255 {
		return 0, fmt.Errorf("RADIUS attribute value length %d too long", n)
	} else {
		return RADIUSAttributeLength(n), nil
	}
}
