// Copyright 2012 Google, Inc. 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"
	"errors"
	"fmt"

	"github.com/google/gopacket"
)

// LLDPTLVType is the type of each TLV value in a LinkLayerDiscovery packet.
type LLDPTLVType byte

const (
	LLDPTLVEnd             LLDPTLVType = 0
	LLDPTLVChassisID       LLDPTLVType = 1
	LLDPTLVPortID          LLDPTLVType = 2
	LLDPTLVTTL             LLDPTLVType = 3
	LLDPTLVPortDescription LLDPTLVType = 4
	LLDPTLVSysName         LLDPTLVType = 5
	LLDPTLVSysDescription  LLDPTLVType = 6
	LLDPTLVSysCapabilities LLDPTLVType = 7
	LLDPTLVMgmtAddress     LLDPTLVType = 8
	LLDPTLVOrgSpecific     LLDPTLVType = 127
)

// LinkLayerDiscoveryValue is a TLV value inside a LinkLayerDiscovery packet layer.
type LinkLayerDiscoveryValue struct {
	Type   LLDPTLVType
	Length uint16
	Value  []byte
}

func (c *LinkLayerDiscoveryValue) len() int {
	return 0
}

// LLDPChassisIDSubType specifies the value type for a single LLDPChassisID.ID
type LLDPChassisIDSubType byte

// LLDP Chassis Types
const (
	LLDPChassisIDSubTypeReserved    LLDPChassisIDSubType = 0
	LLDPChassisIDSubTypeChassisComp LLDPChassisIDSubType = 1
	LLDPChassisIDSubtypeIfaceAlias  LLDPChassisIDSubType = 2
	LLDPChassisIDSubTypePortComp    LLDPChassisIDSubType = 3
	LLDPChassisIDSubTypeMACAddr     LLDPChassisIDSubType = 4
	LLDPChassisIDSubTypeNetworkAddr LLDPChassisIDSubType = 5
	LLDPChassisIDSubtypeIfaceName   LLDPChassisIDSubType = 6
	LLDPChassisIDSubTypeLocal       LLDPChassisIDSubType = 7
)

type LLDPChassisID struct {
	Subtype LLDPChassisIDSubType
	ID      []byte
}

func (c *LLDPChassisID) serialize() []byte {

	var buf = make([]byte, c.serializedLen())
	idLen := uint16(LLDPTLVChassisID)<<9 | uint16(len(c.ID)+1) //id should take 7 bits, length should take 9 bits, +1 for subtype
	binary.BigEndian.PutUint16(buf[0:2], idLen)
	buf[2] = byte(c.Subtype)
	copy(buf[3:], c.ID)
	return buf
}

func (c *LLDPChassisID) serializedLen() int {
	return len(c.ID) + 3 // +2 for id and length, +1 for subtype
}

// LLDPPortIDSubType specifies the value type for a single LLDPPortID.ID
type LLDPPortIDSubType byte

// LLDP PortID types
const (
	LLDPPortIDSubtypeReserved       LLDPPortIDSubType = 0
	LLDPPortIDSubtypeIfaceAlias     LLDPPortIDSubType = 1
	LLDPPortIDSubtypePortComp       LLDPPortIDSubType = 2
	LLDPPortIDSubtypeMACAddr        LLDPPortIDSubType = 3
	LLDPPortIDSubtypeNetworkAddr    LLDPPortIDSubType = 4
	LLDPPortIDSubtypeIfaceName      LLDPPortIDSubType = 5
	LLDPPortIDSubtypeAgentCircuitID LLDPPortIDSubType = 6
	LLDPPortIDSubtypeLocal          LLDPPortIDSubType = 7
)

type LLDPPortID struct {
	Subtype LLDPPortIDSubType
	ID      []byte
}

func (c *LLDPPortID) serialize() []byte {

	var buf = make([]byte, c.serializedLen())
	idLen := uint16(LLDPTLVPortID)<<9 | uint16(len(c.ID)+1) //id should take 7 bits, length should take 9 bits, +1 for subtype
	binary.BigEndian.PutUint16(buf[0:2], idLen)
	buf[2] = byte(c.Subtype)
	copy(buf[3:], c.ID)
	return buf
}

func (c *LLDPPortID) serializedLen() int {
	return len(c.ID) + 3 // +2 for id and length, +1 for subtype
}

// LinkLayerDiscovery is a packet layer containing the LinkLayer Discovery Protocol.
// See http:http://standards.ieee.org/getieee802/download/802.1AB-2009.pdf
// ChassisID, PortID and TTL are mandatory TLV's. Other values can be decoded
// with DecodeValues()
type LinkLayerDiscovery struct {
	BaseLayer
	ChassisID LLDPChassisID
	PortID    LLDPPortID
	TTL       uint16
	Values    []LinkLayerDiscoveryValue
}

type IEEEOUI uint32

// http://standards.ieee.org/develop/regauth/oui/oui.txt
const (
	IEEEOUI8021     IEEEOUI = 0x0080c2
	IEEEOUI8023     IEEEOUI = 0x00120f
	IEEEOUI80211    IEEEOUI = 0x000fac
	IEEEOUI8021Qbg  IEEEOUI = 0x0013BF
	IEEEOUICisco2   IEEEOUI = 0x000142
	IEEEOUIMedia    IEEEOUI = 0x0012bb // TR-41
	IEEEOUIProfinet IEEEOUI = 0x000ecf
	IEEEOUIDCBX     IEEEOUI = 0x001b21
)

// LLDPOrgSpecificTLV is an Organisation-specific TLV
type LLDPOrgSpecificTLV struct {
	OUI     IEEEOUI
	SubType uint8
	Info    []byte
}

// LLDPCapabilities Types
const (
	LLDPCapsOther       uint16 = 1 << 0
	LLDPCapsRepeater    uint16 = 1 << 1
	LLDPCapsBridge      uint16 = 1 << 2
	LLDPCapsWLANAP      uint16 = 1 << 3
	LLDPCapsRouter      uint16 = 1 << 4
	LLDPCapsPhone       uint16 = 1 << 5
	LLDPCapsDocSis      uint16 = 1 << 6
	LLDPCapsStationOnly uint16 = 1 << 7
	LLDPCapsCVLAN       uint16 = 1 << 8
	LLDPCapsSVLAN       uint16 = 1 << 9
	LLDPCapsTmpr        uint16 = 1 << 10
)

// LLDPCapabilities represents the capabilities of a device
type LLDPCapabilities struct {
	Other       bool
	Repeater    bool
	Bridge      bool
	WLANAP      bool
	Router      bool
	Phone       bool
	DocSis      bool
	StationOnly bool
	CVLAN       bool
	SVLAN       bool
	TMPR        bool
}

type LLDPSysCapabilities struct {
	SystemCap  LLDPCapabilities
	EnabledCap LLDPCapabilities
}

type IANAAddressFamily byte

// LLDP Management Address Subtypes
// http://www.iana.org/assignments/address-family-numbers/address-family-numbers.xml
const (
	IANAAddressFamilyReserved IANAAddressFamily = 0
	IANAAddressFamilyIPV4     IANAAddressFamily = 1
	IANAAddressFamilyIPV6     IANAAddressFamily = 2
	IANAAddressFamilyNSAP     IANAAddressFamily = 3
	IANAAddressFamilyHDLC     IANAAddressFamily = 4
	IANAAddressFamilyBBN1822  IANAAddressFamily = 5
	IANAAddressFamily802      IANAAddressFamily = 6
	IANAAddressFamilyE163     IANAAddressFamily = 7
	IANAAddressFamilyE164     IANAAddressFamily = 8
	IANAAddressFamilyF69      IANAAddressFamily = 9
	IANAAddressFamilyX121     IANAAddressFamily = 10
	IANAAddressFamilyIPX      IANAAddressFamily = 11
	IANAAddressFamilyAtalk    IANAAddressFamily = 12
	IANAAddressFamilyDecnet   IANAAddressFamily = 13
	IANAAddressFamilyBanyan   IANAAddressFamily = 14
	IANAAddressFamilyE164NSAP IANAAddressFamily = 15
	IANAAddressFamilyDNS      IANAAddressFamily = 16
	IANAAddressFamilyDistname IANAAddressFamily = 17
	IANAAddressFamilyASNumber IANAAddressFamily = 18
	IANAAddressFamilyXTPIPV4  IANAAddressFamily = 19
	IANAAddressFamilyXTPIPV6  IANAAddressFamily = 20
	IANAAddressFamilyXTP      IANAAddressFamily = 21
	IANAAddressFamilyFcWWPN   IANAAddressFamily = 22
	IANAAddressFamilyFcWWNN   IANAAddressFamily = 23
	IANAAddressFamilyGWID     IANAAddressFamily = 24
	IANAAddressFamilyL2VPN    IANAAddressFamily = 25
)

type LLDPInterfaceSubtype byte

// LLDP Interface Subtypes
const (
	LLDPInterfaceSubtypeUnknown LLDPInterfaceSubtype = 1
	LLDPInterfaceSubtypeifIndex LLDPInterfaceSubtype = 2
	LLDPInterfaceSubtypeSysPort LLDPInterfaceSubtype = 3
)

type LLDPMgmtAddress struct {
	Subtype          IANAAddressFamily
	Address          []byte
	InterfaceSubtype LLDPInterfaceSubtype
	InterfaceNumber  uint32
	OID              string
}

// LinkLayerDiscoveryInfo represents the decoded details for a set of LinkLayerDiscoveryValues
// Organisation-specific TLV's can be decoded using the various Decode() methods
type LinkLayerDiscoveryInfo struct {
	BaseLayer
	PortDescription string
	SysName         string
	SysDescription  string
	SysCapabilities LLDPSysCapabilities
	MgmtAddress     LLDPMgmtAddress
	OrgTLVs         []LLDPOrgSpecificTLV      // Private TLVs
	Unknown         []LinkLayerDiscoveryValue // undecoded TLVs
}

/// IEEE 802.1 TLV Subtypes
const (
	LLDP8021SubtypePortVLANID       uint8 = 1
	LLDP8021SubtypeProtocolVLANID   uint8 = 2
	LLDP8021SubtypeVLANName         uint8 = 3
	LLDP8021SubtypeProtocolIdentity uint8 = 4
	LLDP8021SubtypeVDIUsageDigest   uint8 = 5
	LLDP8021SubtypeManagementVID    uint8 = 6
	LLDP8021SubtypeLinkAggregation  uint8 = 7
)

// VLAN Port Protocol ID options
const (
	LLDPProtocolVLANIDCapability byte = 1 << 1
	LLDPProtocolVLANIDStatus     byte = 1 << 2
)

type PortProtocolVLANID struct {
	Supported bool
	Enabled   bool
	ID        uint16
}

type VLANName struct {
	ID   uint16
	Name string
}

type ProtocolIdentity []byte

// LACP options
const (
	LLDPAggregationCapability byte = 1 << 0
	LLDPAggregationStatus     byte = 1 << 1
)

// IEEE 802 Link Aggregation parameters
type LLDPLinkAggregation struct {
	Supported bool
	Enabled   bool
	PortID    uint32
}

// LLDPInfo8021 represents the information carried in 802.1 Org-specific TLVs
type LLDPInfo8021 struct {
	PVID               uint16
	PPVIDs             []PortProtocolVLANID
	VLANNames          []VLANName
	ProtocolIdentities []ProtocolIdentity
	VIDUsageDigest     uint32
	ManagementVID      uint16
	LinkAggregation    LLDPLinkAggregation
}

// IEEE 802.3 TLV Subtypes
const (
	LLDP8023SubtypeMACPHY          uint8 = 1
	LLDP8023SubtypeMDIPower        uint8 = 2
	LLDP8023SubtypeLinkAggregation uint8 = 3
	LLDP8023SubtypeMTU             uint8 = 4
)

// MACPHY options
const (
	LLDPMACPHYCapability byte = 1 << 0
	LLDPMACPHYStatus     byte = 1 << 1
)

// From IANA-MAU-MIB (introduced by RFC 4836) - dot3MauType
const (
	LLDPMAUTypeUnknown         uint16 = 0
	LLDPMAUTypeAUI             uint16 = 1
	LLDPMAUType10Base5         uint16 = 2
	LLDPMAUTypeFOIRL           uint16 = 3
	LLDPMAUType10Base2         uint16 = 4
	LLDPMAUType10BaseT         uint16 = 5
	LLDPMAUType10BaseFP        uint16 = 6
	LLDPMAUType10BaseFB        uint16 = 7
	LLDPMAUType10BaseFL        uint16 = 8
	LLDPMAUType10BROAD36       uint16 = 9
	LLDPMAUType10BaseT_HD      uint16 = 10
	LLDPMAUType10BaseT_FD      uint16 = 11
	LLDPMAUType10BaseFL_HD     uint16 = 12
	LLDPMAUType10BaseFL_FD     uint16 = 13
	LLDPMAUType100BaseT4       uint16 = 14
	LLDPMAUType100BaseTX_HD    uint16 = 15
	LLDPMAUType100BaseTX_FD    uint16 = 16
	LLDPMAUType100BaseFX_HD    uint16 = 17
	LLDPMAUType100BaseFX_FD    uint16 = 18
	LLDPMAUType100BaseT2_HD    uint16 = 19
	LLDPMAUType100BaseT2_FD    uint16 = 20
	LLDPMAUType1000BaseX_HD    uint16 = 21
	LLDPMAUType1000BaseX_FD    uint16 = 22
	LLDPMAUType1000BaseLX_HD   uint16 = 23
	LLDPMAUType1000BaseLX_FD   uint16 = 24
	LLDPMAUType1000BaseSX_HD   uint16 = 25
	LLDPMAUType1000BaseSX_FD   uint16 = 26
	LLDPMAUType1000BaseCX_HD   uint16 = 27
	LLDPMAUType1000BaseCX_FD   uint16 = 28
	LLDPMAUType1000BaseT_HD    uint16 = 29
	LLDPMAUType1000BaseT_FD    uint16 = 30
	LLDPMAUType10GBaseX        uint16 = 31
	LLDPMAUType10GBaseLX4      uint16 = 32
	LLDPMAUType10GBaseR        uint16 = 33
	LLDPMAUType10GBaseER       uint16 = 34
	LLDPMAUType10GBaseLR       uint16 = 35
	LLDPMAUType10GBaseSR       uint16 = 36
	LLDPMAUType10GBaseW        uint16 = 37
	LLDPMAUType10GBaseEW       uint16 = 38
	LLDPMAUType10GBaseLW       uint16 = 39
	LLDPMAUType10GBaseSW       uint16 = 40
	LLDPMAUType10GBaseCX4      uint16 = 41
	LLDPMAUType2BaseTL         uint16 = 42
	LLDPMAUType10PASS_TS       uint16 = 43
	LLDPMAUType100BaseBX10D    uint16 = 44
	LLDPMAUType100BaseBX10U    uint16 = 45
	LLDPMAUType100BaseLX10     uint16 = 46
	LLDPMAUType1000BaseBX10D   uint16 = 47
	LLDPMAUType1000BaseBX10U   uint16 = 48
	LLDPMAUType1000BaseLX10    uint16 = 49
	LLDPMAUType1000BasePX10D   uint16 = 50
	LLDPMAUType1000BasePX10U   uint16 = 51
	LLDPMAUType1000BasePX20D   uint16 = 52
	LLDPMAUType1000BasePX20U   uint16 = 53
	LLDPMAUType10GBaseT        uint16 = 54
	LLDPMAUType10GBaseLRM      uint16 = 55
	LLDPMAUType1000BaseKX      uint16 = 56
	LLDPMAUType10GBaseKX4      uint16 = 57
	LLDPMAUType10GBaseKR       uint16 = 58
	LLDPMAUType10_1GBasePRX_D1 uint16 = 59
	LLDPMAUType10_1GBasePRX_D2 uint16 = 60
	LLDPMAUType10_1GBasePRX_D3 uint16 = 61
	LLDPMAUType10_1GBasePRX_U1 uint16 = 62
	LLDPMAUType10_1GBasePRX_U2 uint16 = 63
	LLDPMAUType10_1GBasePRX_U3 uint16 = 64
	LLDPMAUType10GBasePR_D1    uint16 = 65
	LLDPMAUType10GBasePR_D2    uint16 = 66
	LLDPMAUType10GBasePR_D3    uint16 = 67
	LLDPMAUType10GBasePR_U1    uint16 = 68
	LLDPMAUType10GBasePR_U3    uint16 = 69
)

// From RFC 3636 - ifMauAutoNegCapAdvertisedBits
const (
	LLDPMAUPMDOther        uint16 = 1 << 15
	LLDPMAUPMD10BaseT      uint16 = 1 << 14
	LLDPMAUPMD10BaseT_FD   uint16 = 1 << 13
	LLDPMAUPMD100BaseT4    uint16 = 1 << 12
	LLDPMAUPMD100BaseTX    uint16 = 1 << 11
	LLDPMAUPMD100BaseTX_FD uint16 = 1 << 10
	LLDPMAUPMD100BaseT2    uint16 = 1 << 9
	LLDPMAUPMD100BaseT2_FD uint16 = 1 << 8
	LLDPMAUPMDFDXPAUSE     uint16 = 1 << 7
	LLDPMAUPMDFDXAPAUSE    uint16 = 1 << 6
	LLDPMAUPMDFDXSPAUSE    uint16 = 1 << 5
	LLDPMAUPMDFDXBPAUSE    uint16 = 1 << 4
	LLDPMAUPMD1000BaseX    uint16 = 1 << 3
	LLDPMAUPMD1000BaseX_FD uint16 = 1 << 2
	LLDPMAUPMD1000BaseT    uint16 = 1 << 1
	LLDPMAUPMD1000BaseT_FD uint16 = 1 << 0
)

// Inverted ifMauAutoNegCapAdvertisedBits if required
// (Some manufacturers misinterpreted the spec -
// see https://bugs.wireshark.org/bugzilla/show_bug.cgi?id=1455)
const (
	LLDPMAUPMDOtherInv        uint16 = 1 << 0
	LLDPMAUPMD10BaseTInv      uint16 = 1 << 1
	LLDPMAUPMD10BaseT_FDInv   uint16 = 1 << 2
	LLDPMAUPMD100BaseT4Inv    uint16 = 1 << 3
	LLDPMAUPMD100BaseTXInv    uint16 = 1 << 4
	LLDPMAUPMD100BaseTX_FDInv uint16 = 1 << 5
	LLDPMAUPMD100BaseT2Inv    uint16 = 1 << 6
	LLDPMAUPMD100BaseT2_FDInv uint16 = 1 << 7
	LLDPMAUPMDFDXPAUSEInv     uint16 = 1 << 8
	LLDPMAUPMDFDXAPAUSEInv    uint16 = 1 << 9
	LLDPMAUPMDFDXSPAUSEInv    uint16 = 1 << 10
	LLDPMAUPMDFDXBPAUSEInv    uint16 = 1 << 11
	LLDPMAUPMD1000BaseXInv    uint16 = 1 << 12
	LLDPMAUPMD1000BaseX_FDInv uint16 = 1 << 13
	LLDPMAUPMD1000BaseTInv    uint16 = 1 << 14
	LLDPMAUPMD1000BaseT_FDInv uint16 = 1 << 15
)

type LLDPMACPHYConfigStatus struct {
	AutoNegSupported  bool
	AutoNegEnabled    bool
	AutoNegCapability uint16
	MAUType           uint16
}

// MDI Power options
const (
	LLDPMDIPowerPortClass    byte = 1 << 0
	LLDPMDIPowerCapability   byte = 1 << 1
	LLDPMDIPowerStatus       byte = 1 << 2
	LLDPMDIPowerPairsAbility byte = 1 << 3
)

type LLDPPowerType byte

type LLDPPowerSource byte

type LLDPPowerPriority byte

const (
	LLDPPowerPriorityUnknown LLDPPowerPriority = 0
	LLDPPowerPriorityMedium  LLDPPowerPriority = 1
	LLDPPowerPriorityHigh    LLDPPowerPriority = 2
	LLDPPowerPriorityLow     LLDPPowerPriority = 3
)

type LLDPPowerViaMDI8023 struct {
	PortClassPSE    bool // false = PD
	PSESupported    bool
	PSEEnabled      bool
	PSEPairsAbility bool
	PSEPowerPair    uint8
	PSEClass        uint8
	Type            LLDPPowerType
	Source          LLDPPowerSource
	Priority        LLDPPowerPriority
	Requested       uint16 // 1-510 Watts
	Allocated       uint16 // 1-510 Watts
}

// LLDPInfo8023 represents the information carried in 802.3 Org-specific TLVs
type LLDPInfo8023 struct {
	MACPHYConfigStatus LLDPMACPHYConfigStatus
	PowerViaMDI        LLDPPowerViaMDI8023
	LinkAggregation    LLDPLinkAggregation
	MTU                uint16
}

// IEEE 802.1Qbg TLV Subtypes
const (
	LLDP8021QbgEVB   uint8 = 0
	LLDP8021QbgCDCP  uint8 = 1
	LLDP8021QbgVDP   uint8 = 2
	LLDP8021QbgEVB22 uint8 = 13
)

// LLDPEVBCapabilities Types
const (
	LLDPEVBCapsSTD uint16 = 1 << 7
	LLDPEVBCapsRR  uint16 = 1 << 6
	LLDPEVBCapsRTE uint16 = 1 << 2
	LLDPEVBCapsECP uint16 = 1 << 1
	LLDPEVBCapsVDP uint16 = 1 << 0
)

// LLDPEVBCapabilities represents the EVB capabilities of a device
type LLDPEVBCapabilities struct {
	StandardBridging            bool
	ReflectiveRelay             bool
	RetransmissionTimerExponent bool
	EdgeControlProtocol         bool
	VSIDiscoveryProtocol        bool
}

type LLDPEVBSettings struct {
	Supported      LLDPEVBCapabilities
	Enabled        LLDPEVBCapabilities
	SupportedVSIs  uint16
	ConfiguredVSIs uint16
	RTEExponent    uint8
}

// LLDPInfo8021Qbg represents the information carried in 802.1Qbg Org-specific TLVs
type LLDPInfo8021Qbg struct {
	EVBSettings LLDPEVBSettings
}

type LLDPMediaSubtype uint8

// Media TLV Subtypes
const (
	LLDPMediaTypeCapabilities LLDPMediaSubtype = 1
	LLDPMediaTypeNetwork      LLDPMediaSubtype = 2
	LLDPMediaTypeLocation     LLDPMediaSubtype = 3
	LLDPMediaTypePower        LLDPMediaSubtype = 4
	LLDPMediaTypeHardware     LLDPMediaSubtype = 5
	LLDPMediaTypeFirmware     LLDPMediaSubtype = 6
	LLDPMediaTypeSoftware     LLDPMediaSubtype = 7
	LLDPMediaTypeSerial       LLDPMediaSubtype = 8
	LLDPMediaTypeManufacturer LLDPMediaSubtype = 9
	LLDPMediaTypeModel        LLDPMediaSubtype = 10
	LLDPMediaTypeAssetID      LLDPMediaSubtype = 11
)

type LLDPMediaClass uint8

// Media Class Values
const (
	LLDPMediaClassUndefined   LLDPMediaClass = 0
	LLDPMediaClassEndpointI   LLDPMediaClass = 1
	LLDPMediaClassEndpointII  LLDPMediaClass = 2
	LLDPMediaClassEndpointIII LLDPMediaClass = 3
	LLDPMediaClassNetwork     LLDPMediaClass = 4
)

// LLDPMediaCapabilities Types
const (
	LLDPMediaCapsLLDP      uint16 = 1 << 0
	LLDPMediaCapsNetwork   uint16 = 1 << 1
	LLDPMediaCapsLocation  uint16 = 1 << 2
	LLDPMediaCapsPowerPSE  uint16 = 1 << 3
	LLDPMediaCapsPowerPD   uint16 = 1 << 4
	LLDPMediaCapsInventory uint16 = 1 << 5
)

// LLDPMediaCapabilities represents the LLDP Media capabilities of a device
type LLDPMediaCapabilities struct {
	Capabilities  bool
	NetworkPolicy bool
	Location      bool
	PowerPSE      bool
	PowerPD       bool
	Inventory     bool
	Class         LLDPMediaClass
}

type LLDPApplicationType uint8

const (
	LLDPAppTypeReserved            LLDPApplicationType = 0
	LLDPAppTypeVoice               LLDPApplicationType = 1
	LLDPappTypeVoiceSignaling      LLDPApplicationType = 2
	LLDPappTypeGuestVoice          LLDPApplicationType = 3
	LLDPappTypeGuestVoiceSignaling LLDPApplicationType = 4
	LLDPappTypeSoftphoneVoice      LLDPApplicationType = 5
	LLDPappTypeVideoConferencing   LLDPApplicationType = 6
	LLDPappTypeStreamingVideo      LLDPApplicationType = 7
	LLDPappTypeVideoSignaling      LLDPApplicationType = 8
)

type LLDPNetworkPolicy struct {
	ApplicationType LLDPApplicationType
	Defined         bool
	Tagged          bool
	VLANId          uint16
	L2Priority      uint16
	DSCPValue       uint8
}

type LLDPLocationFormat uint8

const (
	LLDPLocationFormatInvalid    LLDPLocationFormat = 0
	LLDPLocationFormatCoordinate LLDPLocationFormat = 1
	LLDPLocationFormatAddress    LLDPLocationFormat = 2
	LLDPLocationFormatECS        LLDPLocationFormat = 3
)

type LLDPLocationAddressWhat uint8

const (
	LLDPLocationAddressWhatDHCP    LLDPLocationAddressWhat = 0
	LLDPLocationAddressWhatNetwork LLDPLocationAddressWhat = 1
	LLDPLocationAddressWhatClient  LLDPLocationAddressWhat = 2
)

type LLDPLocationAddressType uint8

const (
	LLDPLocationAddressTypeLanguage       LLDPLocationAddressType = 0
	LLDPLocationAddressTypeNational       LLDPLocationAddressType = 1
	LLDPLocationAddressTypeCounty         LLDPLocationAddressType = 2
	LLDPLocationAddressTypeCity           LLDPLocationAddressType = 3
	LLDPLocationAddressTypeCityDivision   LLDPLocationAddressType = 4
	LLDPLocationAddressTypeNeighborhood   LLDPLocationAddressType = 5
	LLDPLocationAddressTypeStreet         LLDPLocationAddressType = 6
	LLDPLocationAddressTypeLeadingStreet  LLDPLocationAddressType = 16
	LLDPLocationAddressTypeTrailingStreet LLDPLocationAddressType = 17
	LLDPLocationAddressTypeStreetSuffix   LLDPLocationAddressType = 18
	LLDPLocationAddressTypeHouseNum       LLDPLocationAddressType = 19
	LLDPLocationAddressTypeHouseSuffix    LLDPLocationAddressType = 20
	LLDPLocationAddressTypeLandmark       LLDPLocationAddressType = 21
	LLDPLocationAddressTypeAdditional     LLDPLocationAddressType = 22
	LLDPLocationAddressTypeName           LLDPLocationAddressType = 23
	LLDPLocationAddressTypePostal         LLDPLocationAddressType = 24
	LLDPLocationAddressTypeBuilding       LLDPLocationAddressType = 25
	LLDPLocationAddressTypeUnit           LLDPLocationAddressType = 26
	LLDPLocationAddressTypeFloor          LLDPLocationAddressType = 27
	LLDPLocationAddressTypeRoom           LLDPLocationAddressType = 28
	LLDPLocationAddressTypePlace          LLDPLocationAddressType = 29
	LLDPLocationAddressTypeScript         LLDPLocationAddressType = 128
)

type LLDPLocationCoordinate struct {
	LatitudeResolution  uint8
	Latitude            uint64
	LongitudeResolution uint8
	Longitude           uint64
	AltitudeType        uint8
	AltitudeResolution  uint16
	Altitude            uint32
	Datum               uint8
}

type LLDPLocationAddressLine struct {
	Type  LLDPLocationAddressType
	Value string
}

type LLDPLocationAddress struct {
	What         LLDPLocationAddressWhat
	CountryCode  string
	AddressLines []LLDPLocationAddressLine
}

type LLDPLocationECS struct {
	ELIN string
}

// LLDP represents a physical location.
// Only one of the embedded types will contain values, depending on Format.
type LLDPLocation struct {
	Format     LLDPLocationFormat
	Coordinate LLDPLocationCoordinate
	Address    LLDPLocationAddress
	ECS        LLDPLocationECS
}

type LLDPPowerViaMDI struct {
	Type     LLDPPowerType
	Source   LLDPPowerSource
	Priority LLDPPowerPriority
	Value    uint16
}

// LLDPInfoMedia represents the information carried in TR-41 Org-specific TLVs
type LLDPInfoMedia struct {
	MediaCapabilities LLDPMediaCapabilities
	NetworkPolicy     LLDPNetworkPolicy
	Location          LLDPLocation
	PowerViaMDI       LLDPPowerViaMDI
	HardwareRevision  string
	FirmwareRevision  string
	SoftwareRevision  string
	SerialNumber      string
	Manufacturer      string
	Model             string
	AssetID           string
}

type LLDPCisco2Subtype uint8

// Cisco2 TLV Subtypes
const (
	LLDPCisco2PowerViaMDI LLDPCisco2Subtype = 1
)

const (
	LLDPCiscoPSESupport   uint8 = 1 << 0
	LLDPCiscoArchShared   uint8 = 1 << 1
	LLDPCiscoPDSparePair  uint8 = 1 << 2
	LLDPCiscoPSESparePair uint8 = 1 << 3
)

// LLDPInfoCisco2 represents the information carried in Cisco Org-specific TLVs
type LLDPInfoCisco2 struct {
	PSEFourWirePoESupported       bool
	PDSparePairArchitectureShared bool
	PDRequestSparePairPoEOn       bool
	PSESparePairPoEOn             bool
}

// Profinet Subtypes
type LLDPProfinetSubtype uint8

const (
	LLDPProfinetPNIODelay         LLDPProfinetSubtype = 1
	LLDPProfinetPNIOPortStatus    LLDPProfinetSubtype = 2
	LLDPProfinetPNIOMRPPortStatus LLDPProfinetSubtype = 4
	LLDPProfinetPNIOChassisMAC    LLDPProfinetSubtype = 5
	LLDPProfinetPNIOPTCPStatus    LLDPProfinetSubtype = 6
)

type LLDPPNIODelay struct {
	RXLocal    uint32
	RXRemote   uint32
	TXLocal    uint32
	TXRemote   uint32
	CableLocal uint32
}

type LLDPPNIOPortStatus struct {
	Class2 uint16
	Class3 uint16
}

type LLDPPNIOMRPPortStatus struct {
	UUID   []byte
	Status uint16
}

type LLDPPNIOPTCPStatus struct {
	MasterAddress     []byte
	SubdomainUUID     []byte
	IRDataUUID        []byte
	PeriodValid       bool
	PeriodLength      uint32
	RedPeriodValid    bool
	RedPeriodBegin    uint32
	OrangePeriodValid bool
	OrangePeriodBegin uint32
	GreenPeriodValid  bool
	GreenPeriodBegin  uint32
}

// LLDPInfoProfinet represents the information carried in Profinet Org-specific TLVs
type LLDPInfoProfinet struct {
	PNIODelay         LLDPPNIODelay
	PNIOPortStatus    LLDPPNIOPortStatus
	PNIOMRPPortStatus LLDPPNIOMRPPortStatus
	ChassisMAC        []byte
	PNIOPTCPStatus    LLDPPNIOPTCPStatus
}

// LayerType returns gopacket.LayerTypeLinkLayerDiscovery.
func (c *LinkLayerDiscovery) LayerType() gopacket.LayerType {
	return LayerTypeLinkLayerDiscovery
}

// SerializeTo serializes LLDP packet to bytes and writes on SerializeBuffer.
func (c *LinkLayerDiscovery) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error {
	chassIDLen := c.ChassisID.serializedLen()
	portIDLen := c.PortID.serializedLen()
	vb, err := b.AppendBytes(chassIDLen + portIDLen + 4) // +4 for TTL
	if err != nil {
		return err
	}
	copy(vb[:chassIDLen], c.ChassisID.serialize())
	copy(vb[chassIDLen:], c.PortID.serialize())
	ttlIDLen := uint16(LLDPTLVTTL)<<9 | uint16(2)
	binary.BigEndian.PutUint16(vb[chassIDLen+portIDLen:], ttlIDLen)
	binary.BigEndian.PutUint16(vb[chassIDLen+portIDLen+2:], c.TTL)

	vb, err = b.AppendBytes(2) // End Tlv, 2 bytes
	if err != nil {
		return err
	}
	binary.BigEndian.PutUint16(vb[len(vb)-2:], uint16(0)) //End tlv, 2 bytes, all zero
	return nil

}

func decodeLinkLayerDiscovery(data []byte, p gopacket.PacketBuilder) error {
	var vals []LinkLayerDiscoveryValue
	vData := data[0:]
	for len(vData) > 0 {
		nbit := vData[0] & 0x01
		t := LLDPTLVType(vData[0] >> 1)
		val := LinkLayerDiscoveryValue{Type: t, Length: uint16(nbit)<<8 + uint16(vData[1])}
		if val.Length > 0 {
			val.Value = vData[2 : val.Length+2]
		}
		vals = append(vals, val)
		if t == LLDPTLVEnd {
			break
		}
		if len(vData) < int(2+val.Length) {
			return errors.New("Malformed LinkLayerDiscovery Header")
		}
		vData = vData[2+val.Length:]
	}
	if len(vals) < 4 {
		return errors.New("Missing mandatory LinkLayerDiscovery TLV")
	}
	c := &LinkLayerDiscovery{}
	gotEnd := false
	for _, v := range vals {
		switch v.Type {
		case LLDPTLVEnd:
			gotEnd = true
		case LLDPTLVChassisID:
			if len(v.Value) < 2 {
				return errors.New("Malformed LinkLayerDiscovery ChassisID TLV")
			}
			c.ChassisID.Subtype = LLDPChassisIDSubType(v.Value[0])
			c.ChassisID.ID = v.Value[1:]
		case LLDPTLVPortID:
			if len(v.Value) < 2 {
				return errors.New("Malformed LinkLayerDiscovery PortID TLV")
			}
			c.PortID.Subtype = LLDPPortIDSubType(v.Value[0])
			c.PortID.ID = v.Value[1:]
		case LLDPTLVTTL:
			if len(v.Value) < 2 {
				return errors.New("Malformed LinkLayerDiscovery TTL TLV")
			}
			c.TTL = binary.BigEndian.Uint16(v.Value[0:2])
		default:
			c.Values = append(c.Values, v)
		}
	}
	if c.ChassisID.Subtype == 0 || c.PortID.Subtype == 0 || !gotEnd {
		return errors.New("Missing mandatory LinkLayerDiscovery TLV")
	}
	c.Contents = data
	p.AddLayer(c)

	info := &LinkLayerDiscoveryInfo{}
	p.AddLayer(info)
	for _, v := range c.Values {
		switch v.Type {
		case LLDPTLVPortDescription:
			info.PortDescription = string(v.Value)
		case LLDPTLVSysName:
			info.SysName = string(v.Value)
		case LLDPTLVSysDescription:
			info.SysDescription = string(v.Value)
		case LLDPTLVSysCapabilities:
			if err := checkLLDPTLVLen(v, 4); err != nil {
				return err
			}
			info.SysCapabilities.SystemCap = getCapabilities(binary.BigEndian.Uint16(v.Value[0:2]))
			info.SysCapabilities.EnabledCap = getCapabilities(binary.BigEndian.Uint16(v.Value[2:4]))
		case LLDPTLVMgmtAddress:
			if err := checkLLDPTLVLen(v, 9); err != nil {
				return err
			}
			mlen := v.Value[0]
			if err := checkLLDPTLVLen(v, int(mlen+7)); err != nil {
				return err
			}
			info.MgmtAddress.Subtype = IANAAddressFamily(v.Value[1])
			info.MgmtAddress.Address = v.Value[2 : mlen+1]
			info.MgmtAddress.InterfaceSubtype = LLDPInterfaceSubtype(v.Value[mlen+1])
			info.MgmtAddress.InterfaceNumber = binary.BigEndian.Uint32(v.Value[mlen+2 : mlen+6])
			olen := v.Value[mlen+6]
			if err := checkLLDPTLVLen(v, int(mlen+6+olen)); err != nil {
				return err
			}
			info.MgmtAddress.OID = string(v.Value[mlen+9 : mlen+9+olen])
		case LLDPTLVOrgSpecific:
			if err := checkLLDPTLVLen(v, 4); err != nil {
				return err
			}
			info.OrgTLVs = append(info.OrgTLVs, LLDPOrgSpecificTLV{IEEEOUI(binary.BigEndian.Uint32(append([]byte{byte(0)}, v.Value[0:3]...))), uint8(v.Value[3]), v.Value[4:]})
		}
	}
	return nil
}

func (l *LinkLayerDiscoveryInfo) Decode8021() (info LLDPInfo8021, err error) {
	for _, o := range l.OrgTLVs {
		if o.OUI != IEEEOUI8021 {
			continue
		}
		switch o.SubType {
		case LLDP8021SubtypePortVLANID:
			if err = checkLLDPOrgSpecificLen(o, 2); err != nil {
				return
			}
			info.PVID = binary.BigEndian.Uint16(o.Info[0:2])
		case LLDP8021SubtypeProtocolVLANID:
			if err = checkLLDPOrgSpecificLen(o, 3); err != nil {
				return
			}
			sup := (o.Info[0]&LLDPProtocolVLANIDCapability > 0)
			en := (o.Info[0]&LLDPProtocolVLANIDStatus > 0)
			id := binary.BigEndian.Uint16(o.Info[1:3])
			info.PPVIDs = append(info.PPVIDs, PortProtocolVLANID{sup, en, id})
		case LLDP8021SubtypeVLANName:
			if err = checkLLDPOrgSpecificLen(o, 2); err != nil {
				return
			}
			id := binary.BigEndian.Uint16(o.Info[0:2])
			info.VLANNames = append(info.VLANNames, VLANName{id, string(o.Info[3:])})
		case LLDP8021SubtypeProtocolIdentity:
			if err = checkLLDPOrgSpecificLen(o, 1); err != nil {
				return
			}
			l := int(o.Info[0])
			if l > 0 {
				info.ProtocolIdentities = append(info.ProtocolIdentities, o.Info[1:1+l])
			}
		case LLDP8021SubtypeVDIUsageDigest:
			if err = checkLLDPOrgSpecificLen(o, 4); err != nil {
				return
			}
			info.VIDUsageDigest = binary.BigEndian.Uint32(o.Info[0:4])
		case LLDP8021SubtypeManagementVID:
			if err = checkLLDPOrgSpecificLen(o, 2); err != nil {
				return
			}
			info.ManagementVID = binary.BigEndian.Uint16(o.Info[0:2])
		case LLDP8021SubtypeLinkAggregation:
			if err = checkLLDPOrgSpecificLen(o, 5); err != nil {
				return
			}
			sup := (o.Info[0]&LLDPAggregationCapability > 0)
			en := (o.Info[0]&LLDPAggregationStatus > 0)
			info.LinkAggregation = LLDPLinkAggregation{sup, en, binary.BigEndian.Uint32(o.Info[1:5])}
		}
	}
	return
}

func (l *LinkLayerDiscoveryInfo) Decode8023() (info LLDPInfo8023, err error) {
	for _, o := range l.OrgTLVs {
		if o.OUI != IEEEOUI8023 {
			continue
		}
		switch o.SubType {
		case LLDP8023SubtypeMACPHY:
			if err = checkLLDPOrgSpecificLen(o, 5); err != nil {
				return
			}
			sup := (o.Info[0]&LLDPMACPHYCapability > 0)
			en := (o.Info[0]&LLDPMACPHYStatus > 0)
			ca := binary.BigEndian.Uint16(o.Info[1:3])
			mau := binary.BigEndian.Uint16(o.Info[3:5])
			info.MACPHYConfigStatus = LLDPMACPHYConfigStatus{sup, en, ca, mau}
		case LLDP8023SubtypeMDIPower:
			if err = checkLLDPOrgSpecificLen(o, 3); err != nil {
				return
			}
			info.PowerViaMDI.PortClassPSE = (o.Info[0]&LLDPMDIPowerPortClass > 0)
			info.PowerViaMDI.PSESupported = (o.Info[0]&LLDPMDIPowerCapability > 0)
			info.PowerViaMDI.PSEEnabled = (o.Info[0]&LLDPMDIPowerStatus > 0)
			info.PowerViaMDI.PSEPairsAbility = (o.Info[0]&LLDPMDIPowerPairsAbility > 0)
			info.PowerViaMDI.PSEPowerPair = uint8(o.Info[1])
			info.PowerViaMDI.PSEClass = uint8(o.Info[2])
			if len(o.Info) >= 7 {
				info.PowerViaMDI.Type = LLDPPowerType((o.Info[3] & 0xc0) >> 6)
				info.PowerViaMDI.Source = LLDPPowerSource((o.Info[3] & 0x30) >> 4)
				if info.PowerViaMDI.Type == 1 || info.PowerViaMDI.Type == 3 {
					info.PowerViaMDI.Source += 128 // For Stringify purposes
				}
				info.PowerViaMDI.Priority = LLDPPowerPriority(o.Info[3] & 0x0f)
				info.PowerViaMDI.Requested = binary.BigEndian.Uint16(o.Info[4:6])
				info.PowerViaMDI.Allocated = binary.BigEndian.Uint16(o.Info[6:8])
			}
		case LLDP8023SubtypeLinkAggregation:
			if err = checkLLDPOrgSpecificLen(o, 5); err != nil {
				return
			}
			sup := (o.Info[0]&LLDPAggregationCapability > 0)
			en := (o.Info[0]&LLDPAggregationStatus > 0)
			info.LinkAggregation = LLDPLinkAggregation{sup, en, binary.BigEndian.Uint32(o.Info[1:5])}
		case LLDP8023SubtypeMTU:
			if err = checkLLDPOrgSpecificLen(o, 2); err != nil {
				return
			}
			info.MTU = binary.BigEndian.Uint16(o.Info[0:2])
		}
	}
	return
}

func (l *LinkLayerDiscoveryInfo) Decode8021Qbg() (info LLDPInfo8021Qbg, err error) {
	for _, o := range l.OrgTLVs {
		if o.OUI != IEEEOUI8021Qbg {
			continue
		}
		switch o.SubType {
		case LLDP8021QbgEVB:
			if err = checkLLDPOrgSpecificLen(o, 9); err != nil {
				return
			}
			info.EVBSettings.Supported = getEVBCapabilities(binary.BigEndian.Uint16(o.Info[0:2]))
			info.EVBSettings.Enabled = getEVBCapabilities(binary.BigEndian.Uint16(o.Info[2:4]))
			info.EVBSettings.SupportedVSIs = binary.BigEndian.Uint16(o.Info[4:6])
			info.EVBSettings.ConfiguredVSIs = binary.BigEndian.Uint16(o.Info[6:8])
			info.EVBSettings.RTEExponent = uint8(o.Info[8])
		}
	}
	return
}

func (l *LinkLayerDiscoveryInfo) DecodeMedia() (info LLDPInfoMedia, err error) {
	for _, o := range l.OrgTLVs {
		if o.OUI != IEEEOUIMedia {
			continue
		}
		switch LLDPMediaSubtype(o.SubType) {
		case LLDPMediaTypeCapabilities:
			if err = checkLLDPOrgSpecificLen(o, 3); err != nil {
				return
			}
			b := binary.BigEndian.Uint16(o.Info[0:2])
			info.MediaCapabilities.Capabilities = (b & LLDPMediaCapsLLDP) > 0
			info.MediaCapabilities.NetworkPolicy = (b & LLDPMediaCapsNetwork) > 0
			info.MediaCapabilities.Location = (b & LLDPMediaCapsLocation) > 0
			info.MediaCapabilities.PowerPSE = (b & LLDPMediaCapsPowerPSE) > 0
			info.MediaCapabilities.PowerPD = (b & LLDPMediaCapsPowerPD) > 0
			info.MediaCapabilities.Inventory = (b & LLDPMediaCapsInventory) > 0
			info.MediaCapabilities.Class = LLDPMediaClass(o.Info[2])
		case LLDPMediaTypeNetwork:
			if err = checkLLDPOrgSpecificLen(o, 4); err != nil {
				return
			}
			info.NetworkPolicy.ApplicationType = LLDPApplicationType(o.Info[0])
			b := binary.BigEndian.Uint16(o.Info[1:3])
			info.NetworkPolicy.Defined = (b & 0x8000) == 0
			info.NetworkPolicy.Tagged = (b & 0x4000) > 0
			info.NetworkPolicy.VLANId = (b & 0x1ffe) >> 1
			b = binary.BigEndian.Uint16(o.Info[2:4])
			info.NetworkPolicy.L2Priority = (b & 0x01c0) >> 6
			info.NetworkPolicy.DSCPValue = uint8(o.Info[3] & 0x3f)
		case LLDPMediaTypeLocation:
			if err = checkLLDPOrgSpecificLen(o, 1); err != nil {
				return
			}
			info.Location.Format = LLDPLocationFormat(o.Info[0])
			o.Info = o.Info[1:]
			switch info.Location.Format {
			case LLDPLocationFormatCoordinate:
				if err = checkLLDPOrgSpecificLen(o, 16); err != nil {
					return
				}
				info.Location.Coordinate.LatitudeResolution = uint8(o.Info[0]&0xfc) >> 2
				b := binary.BigEndian.Uint64(o.Info[0:8])
				info.Location.Coordinate.Latitude = (b & 0x03ffffffff000000) >> 24
				info.Location.Coordinate.LongitudeResolution = uint8(o.Info[5]&0xfc) >> 2
				b = binary.BigEndian.Uint64(o.Info[5:13])
				info.Location.Coordinate.Longitude = (b & 0x03ffffffff000000) >> 24
				info.Location.Coordinate.AltitudeType = uint8((o.Info[10] & 0x30) >> 4)
				b1 := binary.BigEndian.Uint16(o.Info[10:12])
				info.Location.Coordinate.AltitudeResolution = (b1 & 0xfc0) >> 6
				b2 := binary.BigEndian.Uint32(o.Info[11:15])
				info.Location.Coordinate.Altitude = b2 & 0x3fffffff
				info.Location.Coordinate.Datum = uint8(o.Info[15])
			case LLDPLocationFormatAddress:
				if err = checkLLDPOrgSpecificLen(o, 3); err != nil {
					return
				}
				//ll := uint8(o.Info[0])
				info.Location.Address.What = LLDPLocationAddressWhat(o.Info[1])
				info.Location.Address.CountryCode = string(o.Info[2:4])
				data := o.Info[4:]
				for len(data) > 1 {
					aType := LLDPLocationAddressType(data[0])
					aLen := int(data[1])
					if len(data) >= aLen+2 {
						info.Location.Address.AddressLines = append(info.Location.Address.AddressLines, LLDPLocationAddressLine{aType, string(data[2 : aLen+2])})
						data = data[aLen+2:]
					} else {
						break
					}
				}
			case LLDPLocationFormatECS:
				info.Location.ECS.ELIN = string(o.Info)
			}
		case LLDPMediaTypePower:
			if err = checkLLDPOrgSpecificLen(o, 3); err != nil {
				return
			}
			info.PowerViaMDI.Type = LLDPPowerType((o.Info[0] & 0xc0) >> 6)
			info.PowerViaMDI.Source = LLDPPowerSource((o.Info[0] & 0x30) >> 4)
			if info.PowerViaMDI.Type == 1 || info.PowerViaMDI.Type == 3 {
				info.PowerViaMDI.Source += 128 // For Stringify purposes
			}
			info.PowerViaMDI.Priority = LLDPPowerPriority(o.Info[0] & 0x0f)
			info.PowerViaMDI.Value = binary.BigEndian.Uint16(o.Info[1:3]) * 100 // 0 to 102.3 w, 0.1W increments
		case LLDPMediaTypeHardware:
			info.HardwareRevision = string(o.Info)
		case LLDPMediaTypeFirmware:
			info.FirmwareRevision = string(o.Info)
		case LLDPMediaTypeSoftware:
			info.SoftwareRevision = string(o.Info)
		case LLDPMediaTypeSerial:
			info.SerialNumber = string(o.Info)
		case LLDPMediaTypeManufacturer:
			info.Manufacturer = string(o.Info)
		case LLDPMediaTypeModel:
			info.Model = string(o.Info)
		case LLDPMediaTypeAssetID:
			info.AssetID = string(o.Info)
		}
	}
	return
}

func (l *LinkLayerDiscoveryInfo) DecodeCisco2() (info LLDPInfoCisco2, err error) {
	for _, o := range l.OrgTLVs {
		if o.OUI != IEEEOUICisco2 {
			continue
		}
		switch LLDPCisco2Subtype(o.SubType) {
		case LLDPCisco2PowerViaMDI:
			if err = checkLLDPOrgSpecificLen(o, 1); err != nil {
				return
			}
			info.PSEFourWirePoESupported = (o.Info[0] & LLDPCiscoPSESupport) > 0
			info.PDSparePairArchitectureShared = (o.Info[0] & LLDPCiscoArchShared) > 0
			info.PDRequestSparePairPoEOn = (o.Info[0] & LLDPCiscoPDSparePair) > 0
			info.PSESparePairPoEOn = (o.Info[0] & LLDPCiscoPSESparePair) > 0
		}
	}
	return
}

func (l *LinkLayerDiscoveryInfo) DecodeProfinet() (info LLDPInfoProfinet, err error) {
	for _, o := range l.OrgTLVs {
		if o.OUI != IEEEOUIProfinet {
			continue
		}
		switch LLDPProfinetSubtype(o.SubType) {
		case LLDPProfinetPNIODelay:
			if err = checkLLDPOrgSpecificLen(o, 20); err != nil {
				return
			}
			info.PNIODelay.RXLocal = binary.BigEndian.Uint32(o.Info[0:4])
			info.PNIODelay.RXRemote = binary.BigEndian.Uint32(o.Info[4:8])
			info.PNIODelay.TXLocal = binary.BigEndian.Uint32(o.Info[8:12])
			info.PNIODelay.TXRemote = binary.BigEndian.Uint32(o.Info[12:16])
			info.PNIODelay.CableLocal = binary.BigEndian.Uint32(o.Info[16:20])
		case LLDPProfinetPNIOPortStatus:
			if err = checkLLDPOrgSpecificLen(o, 4); err != nil {
				return
			}
			info.PNIOPortStatus.Class2 = binary.BigEndian.Uint16(o.Info[0:2])
			info.PNIOPortStatus.Class3 = binary.BigEndian.Uint16(o.Info[2:4])
		case LLDPProfinetPNIOMRPPortStatus:
			if err = checkLLDPOrgSpecificLen(o, 18); err != nil {
				return
			}
			info.PNIOMRPPortStatus.UUID = o.Info[0:16]
			info.PNIOMRPPortStatus.Status = binary.BigEndian.Uint16(o.Info[16:18])
		case LLDPProfinetPNIOChassisMAC:
			if err = checkLLDPOrgSpecificLen(o, 6); err != nil {
				return
			}
			info.ChassisMAC = o.Info[0:6]
		case LLDPProfinetPNIOPTCPStatus:
			if err = checkLLDPOrgSpecificLen(o, 54); err != nil {
				return
			}
			info.PNIOPTCPStatus.MasterAddress = o.Info[0:6]
			info.PNIOPTCPStatus.SubdomainUUID = o.Info[6:22]
			info.PNIOPTCPStatus.IRDataUUID = o.Info[22:38]
			b := binary.BigEndian.Uint32(o.Info[38:42])
			info.PNIOPTCPStatus.PeriodValid = (b & 0x80000000) > 0
			info.PNIOPTCPStatus.PeriodLength = b & 0x7fffffff
			b = binary.BigEndian.Uint32(o.Info[42:46])
			info.PNIOPTCPStatus.RedPeriodValid = (b & 0x80000000) > 0
			info.PNIOPTCPStatus.RedPeriodBegin = b & 0x7fffffff
			b = binary.BigEndian.Uint32(o.Info[46:50])
			info.PNIOPTCPStatus.OrangePeriodValid = (b & 0x80000000) > 0
			info.PNIOPTCPStatus.OrangePeriodBegin = b & 0x7fffffff
			b = binary.BigEndian.Uint32(o.Info[50:54])
			info.PNIOPTCPStatus.GreenPeriodValid = (b & 0x80000000) > 0
			info.PNIOPTCPStatus.GreenPeriodBegin = b & 0x7fffffff
		}
	}
	return
}

// LayerType returns gopacket.LayerTypeLinkLayerDiscoveryInfo.
func (c *LinkLayerDiscoveryInfo) LayerType() gopacket.LayerType {
	return LayerTypeLinkLayerDiscoveryInfo
}

func getCapabilities(v uint16) (c LLDPCapabilities) {
	c.Other = (v&LLDPCapsOther > 0)
	c.Repeater = (v&LLDPCapsRepeater > 0)
	c.Bridge = (v&LLDPCapsBridge > 0)
	c.WLANAP = (v&LLDPCapsWLANAP > 0)
	c.Router = (v&LLDPCapsRouter > 0)
	c.Phone = (v&LLDPCapsPhone > 0)
	c.DocSis = (v&LLDPCapsDocSis > 0)
	c.StationOnly = (v&LLDPCapsStationOnly > 0)
	c.CVLAN = (v&LLDPCapsCVLAN > 0)
	c.SVLAN = (v&LLDPCapsSVLAN > 0)
	c.TMPR = (v&LLDPCapsTmpr > 0)
	return
}

func getEVBCapabilities(v uint16) (c LLDPEVBCapabilities) {
	c.StandardBridging = (v & LLDPEVBCapsSTD) > 0
	c.StandardBridging = (v & LLDPEVBCapsSTD) > 0
	c.ReflectiveRelay = (v & LLDPEVBCapsRR) > 0
	c.RetransmissionTimerExponent = (v & LLDPEVBCapsRTE) > 0
	c.EdgeControlProtocol = (v & LLDPEVBCapsECP) > 0
	c.VSIDiscoveryProtocol = (v & LLDPEVBCapsVDP) > 0
	return
}

func (t LLDPTLVType) String() (s string) {
	switch t {
	case LLDPTLVEnd:
		s = "TLV End"
	case LLDPTLVChassisID:
		s = "Chassis ID"
	case LLDPTLVPortID:
		s = "Port ID"
	case LLDPTLVTTL:
		s = "TTL"
	case LLDPTLVPortDescription:
		s = "Port Description"
	case LLDPTLVSysName:
		s = "System Name"
	case LLDPTLVSysDescription:
		s = "System Description"
	case LLDPTLVSysCapabilities:
		s = "System Capabilities"
	case LLDPTLVMgmtAddress:
		s = "Management Address"
	case LLDPTLVOrgSpecific:
		s = "Organisation Specific"
	default:
		s = "Unknown"
	}
	return
}

func (t LLDPChassisIDSubType) String() (s string) {
	switch t {
	case LLDPChassisIDSubTypeReserved:
		s = "Reserved"
	case LLDPChassisIDSubTypeChassisComp:
		s = "Chassis Component"
	case LLDPChassisIDSubtypeIfaceAlias:
		s = "Interface Alias"
	case LLDPChassisIDSubTypePortComp:
		s = "Port Component"
	case LLDPChassisIDSubTypeMACAddr:
		s = "MAC Address"
	case LLDPChassisIDSubTypeNetworkAddr:
		s = "Network Address"
	case LLDPChassisIDSubtypeIfaceName:
		s = "Interface Name"
	case LLDPChassisIDSubTypeLocal:
		s = "Local"
	default:
		s = "Unknown"
	}
	return
}

func (t LLDPPortIDSubType) String() (s string) {
	switch t {
	case LLDPPortIDSubtypeReserved:
		s = "Reserved"
	case LLDPPortIDSubtypeIfaceAlias:
		s = "Interface Alias"
	case LLDPPortIDSubtypePortComp:
		s = "Port Component"
	case LLDPPortIDSubtypeMACAddr:
		s = "MAC Address"
	case LLDPPortIDSubtypeNetworkAddr:
		s = "Network Address"
	case LLDPPortIDSubtypeIfaceName:
		s = "Interface Name"
	case LLDPPortIDSubtypeAgentCircuitID:
		s = "Agent Circuit ID"
	case LLDPPortIDSubtypeLocal:
		s = "Local"
	default:
		s = "Unknown"
	}
	return
}

func (t IANAAddressFamily) String() (s string) {
	switch t {
	case IANAAddressFamilyReserved:
		s = "Reserved"
	case IANAAddressFamilyIPV4:
		s = "IPv4"
	case IANAAddressFamilyIPV6:
		s = "IPv6"
	case IANAAddressFamilyNSAP:
		s = "NSAP"
	case IANAAddressFamilyHDLC:
		s = "HDLC"
	case IANAAddressFamilyBBN1822:
		s = "BBN 1822"
	case IANAAddressFamily802:
		s = "802 media plus Ethernet 'canonical format'"
	case IANAAddressFamilyE163:
		s = "E.163"
	case IANAAddressFamilyE164:
		s = "E.164 (SMDS, Frame Relay, ATM)"
	case IANAAddressFamilyF69:
		s = "F.69 (Telex)"
	case IANAAddressFamilyX121:
		s = "X.121, X.25, Frame Relay"
	case IANAAddressFamilyIPX:
		s = "IPX"
	case IANAAddressFamilyAtalk:
		s = "Appletalk"
	case IANAAddressFamilyDecnet:
		s = "Decnet IV"
	case IANAAddressFamilyBanyan:
		s = "Banyan Vines"
	case IANAAddressFamilyE164NSAP:
		s = "E.164 with NSAP format subaddress"
	case IANAAddressFamilyDNS:
		s = "DNS"
	case IANAAddressFamilyDistname:
		s = "Distinguished Name"
	case IANAAddressFamilyASNumber:
		s = "AS Number"
	case IANAAddressFamilyXTPIPV4:
		s = "XTP over IP version 4"
	case IANAAddressFamilyXTPIPV6:
		s = "XTP over IP version 6"
	case IANAAddressFamilyXTP:
		s = "XTP native mode XTP"
	case IANAAddressFamilyFcWWPN:
		s = "Fibre Channel World-Wide Port Name"
	case IANAAddressFamilyFcWWNN:
		s = "Fibre Channel World-Wide Node Name"
	case IANAAddressFamilyGWID:
		s = "GWID"
	case IANAAddressFamilyL2VPN:
		s = "AFI for Layer 2 VPN"
	default:
		s = "Unknown"
	}
	return
}

func (t LLDPInterfaceSubtype) String() (s string) {
	switch t {
	case LLDPInterfaceSubtypeUnknown:
		s = "Unknown"
	case LLDPInterfaceSubtypeifIndex:
		s = "IfIndex"
	case LLDPInterfaceSubtypeSysPort:
		s = "System Port Number"
	default:
		s = "Unknown"
	}
	return
}

func (t LLDPPowerType) String() (s string) {
	switch t {
	case 0:
		s = "Type 2 PSE Device"
	case 1:
		s = "Type 2 PD Device"
	case 2:
		s = "Type 1 PSE Device"
	case 3:
		s = "Type 1 PD Device"
	default:
		s = "Unknown"
	}
	return
}

func (t LLDPPowerSource) String() (s string) {
	switch t {
	// PD Device
	case 0:
		s = "Unknown"
	case 1:
		s = "PSE"
	case 2:
		s = "Local"
	case 3:
		s = "PSE and Local"
	// PSE Device  (Actual value  + 128)
	case 128:
		s = "Unknown"
	case 129:
		s = "Primary Power Source"
	case 130:
		s = "Backup Power Source"
	default:
		s = "Unknown"
	}
	return
}

func (t LLDPPowerPriority) String() (s string) {
	switch t {
	case 0:
		s = "Unknown"
	case 1:
		s = "Critical"
	case 2:
		s = "High"
	case 3:
		s = "Low"
	default:
		s = "Unknown"
	}
	return
}

func (t LLDPMediaSubtype) String() (s string) {
	switch t {
	case LLDPMediaTypeCapabilities:
		s = "Media Capabilities "
	case LLDPMediaTypeNetwork:
		s = "Network Policy"
	case LLDPMediaTypeLocation:
		s = "Location Identification"
	case LLDPMediaTypePower:
		s = "Extended Power-via-MDI"
	case LLDPMediaTypeHardware:
		s = "Hardware Revision"
	case LLDPMediaTypeFirmware:
		s = "Firmware Revision"
	case LLDPMediaTypeSoftware:
		s = "Software Revision"
	case LLDPMediaTypeSerial:
		s = "Serial Number"
	case LLDPMediaTypeManufacturer:
		s = "Manufacturer"
	case LLDPMediaTypeModel:
		s = "Model"
	case LLDPMediaTypeAssetID:
		s = "Asset ID"
	default:
		s = "Unknown"
	}
	return
}

func (t LLDPMediaClass) String() (s string) {
	switch t {
	case LLDPMediaClassUndefined:
		s = "Undefined"
	case LLDPMediaClassEndpointI:
		s = "Endpoint Class I"
	case LLDPMediaClassEndpointII:
		s = "Endpoint Class II"
	case LLDPMediaClassEndpointIII:
		s = "Endpoint Class III"
	case LLDPMediaClassNetwork:
		s = "Network connectivity "
	default:
		s = "Unknown"
	}
	return
}

func (t LLDPApplicationType) String() (s string) {
	switch t {
	case LLDPAppTypeReserved:
		s = "Reserved"
	case LLDPAppTypeVoice:
		s = "Voice"
	case LLDPappTypeVoiceSignaling:
		s = "Voice Signaling"
	case LLDPappTypeGuestVoice:
		s = "Guest Voice"
	case LLDPappTypeGuestVoiceSignaling:
		s = "Guest Voice Signaling"
	case LLDPappTypeSoftphoneVoice:
		s = "Softphone Voice"
	case LLDPappTypeVideoConferencing:
		s = "Video Conferencing"
	case LLDPappTypeStreamingVideo:
		s = "Streaming Video"
	case LLDPappTypeVideoSignaling:
		s = "Video Signaling"
	default:
		s = "Unknown"
	}
	return
}

func (t LLDPLocationFormat) String() (s string) {
	switch t {
	case LLDPLocationFormatInvalid:
		s = "Invalid"
	case LLDPLocationFormatCoordinate:
		s = "Coordinate-based LCI"
	case LLDPLocationFormatAddress:
		s = "Address-based LCO"
	case LLDPLocationFormatECS:
		s = "ECS ELIN"
	default:
		s = "Unknown"
	}
	return
}

func (t LLDPLocationAddressType) String() (s string) {
	switch t {
	case LLDPLocationAddressTypeLanguage:
		s = "Language"
	case LLDPLocationAddressTypeNational:
		s = "National subdivisions (province, state, etc)"
	case LLDPLocationAddressTypeCounty:
		s = "County, parish, district"
	case LLDPLocationAddressTypeCity:
		s = "City, township"
	case LLDPLocationAddressTypeCityDivision:
		s = "City division, borough, ward"
	case LLDPLocationAddressTypeNeighborhood:
		s = "Neighborhood, block"
	case LLDPLocationAddressTypeStreet:
		s = "Street"
	case LLDPLocationAddressTypeLeadingStreet:
		s = "Leading street direction"
	case LLDPLocationAddressTypeTrailingStreet:
		s = "Trailing street suffix"
	case LLDPLocationAddressTypeStreetSuffix:
		s = "Street suffix"
	case LLDPLocationAddressTypeHouseNum:
		s = "House number"
	case LLDPLocationAddressTypeHouseSuffix:
		s = "House number suffix"
	case LLDPLocationAddressTypeLandmark:
		s = "Landmark or vanity address"
	case LLDPLocationAddressTypeAdditional:
		s = "Additional location information"
	case LLDPLocationAddressTypeName:
		s = "Name"
	case LLDPLocationAddressTypePostal:
		s = "Postal/ZIP code"
	case LLDPLocationAddressTypeBuilding:
		s = "Building"
	case LLDPLocationAddressTypeUnit:
		s = "Unit"
	case LLDPLocationAddressTypeFloor:
		s = "Floor"
	case LLDPLocationAddressTypeRoom:
		s = "Room number"
	case LLDPLocationAddressTypePlace:
		s = "Place type"
	case LLDPLocationAddressTypeScript:
		s = "Script"
	default:
		s = "Unknown"
	}
	return
}

func checkLLDPTLVLen(v LinkLayerDiscoveryValue, l int) (err error) {
	if len(v.Value) < l {
		err = fmt.Errorf("Invalid TLV %v length %d (wanted mimimum %v", v.Type, len(v.Value), l)
	}
	return
}

func checkLLDPOrgSpecificLen(o LLDPOrgSpecificTLV, l int) (err error) {
	if len(o.Info) < l {
		err = fmt.Errorf("Invalid Org Specific TLV %v length %d (wanted minimum %v)", o.SubType, len(o.Info), l)
	}
	return
}
