package rtnetlink

import (
	"errors"
	"fmt"
	"net"

	"github.com/jsimonetti/rtnetlink/v2/internal/unix"

	"github.com/mdlayher/netlink"
)

var (
	// errInvalidLinkMessage is returned when a LinkMessage is malformed.
	errInvalidLinkMessage = errors.New("rtnetlink LinkMessage is invalid or too short")

	// errInvalidLinkMessageAttr is returned when link attributes are malformed.
	errInvalidLinkMessageAttr = errors.New("rtnetlink LinkMessage has a wrong attribute data length")
)

var _ Message = &LinkMessage{}

// A LinkMessage is a route netlink link message.
type LinkMessage struct {
	// Always set to AF_UNSPEC (0)
	Family uint16

	// Device Type
	Type uint16

	// Unique interface index, using a nonzero value with
	// NewLink will instruct the kernel to create a
	// device with the given index (kernel 3.7+ required)
	Index uint32

	// Contains device flags, see netdevice(7)
	Flags uint32

	// Change Flags, specifies which flags will be affected by the Flags field
	Change uint32

	// Attributes List
	Attributes *LinkAttributes

	// A response was filtered as requested, see NLM_F_DUMP_FILTERED
	filtered bool
}

// MarshalBinary marshals a LinkMessage into a byte slice.
func (m *LinkMessage) MarshalBinary() ([]byte, error) {
	b := make([]byte, unix.SizeofIfInfomsg)

	b[0] = 0 // Family
	b[1] = 0 // reserved
	nativeEndian.PutUint16(b[2:4], m.Type)
	nativeEndian.PutUint32(b[4:8], m.Index)
	nativeEndian.PutUint32(b[8:12], m.Flags)
	nativeEndian.PutUint32(b[12:16], m.Change)

	if m.Attributes != nil {
		if m.Attributes.Info != nil && m.Attributes.Info.Data != nil {
			if verifier, ok := m.Attributes.Info.Data.(LinkDriverVerifier); ok {
				if err := verifier.Verify(m); err != nil {
					return nil, err
				}
			}
		}

		ae := netlink.NewAttributeEncoder()
		ae.ByteOrder = nativeEndian
		err := m.Attributes.encode(ae)
		if err != nil {
			return nil, err
		}

		a, err := ae.Encode()
		if err != nil {
			return nil, err
		}

		return append(b, a...), nil
	}

	return b, nil
}

// UnmarshalBinary unmarshals the contents of a byte slice into a LinkMessage.
func (m *LinkMessage) UnmarshalBinary(b []byte) error {
	l := len(b)
	if l < unix.SizeofIfInfomsg {
		return errInvalidLinkMessage
	}

	m.Family = nativeEndian.Uint16(b[0:2])
	m.Type = nativeEndian.Uint16(b[2:4])
	m.Index = nativeEndian.Uint32(b[4:8])
	m.Flags = nativeEndian.Uint32(b[8:12])
	m.Change = nativeEndian.Uint32(b[12:16])

	if l > unix.SizeofIfInfomsg {
		m.Attributes = &LinkAttributes{}
		ad, err := netlink.NewAttributeDecoder(b[16:])
		if err != nil {
			return err
		}
		ad.ByteOrder = nativeEndian
		err = m.Attributes.decode(ad)
		if err != nil {
			return err
		}
	}

	return nil
}

// rtMessage is an empty method to sattisfy the Message interface.
func (*LinkMessage) rtMessage() {}

// LinkService is used to retrieve rtnetlink family information.
type LinkService struct {
	c *Conn
}

// execute executes the request and returns the messages as a LinkMessage slice
func (l *LinkService) execute(m Message, family uint16, flags netlink.HeaderFlags) ([]LinkMessage, error) {
	msgs, err := l.c.Execute(m, family, flags)

	links := make([]LinkMessage, len(msgs))
	for i, msg := range msgs {
		if link, ok := msg.(*LinkMessage); ok {
			links[i] = *link
		}
	}

	return links, err
}

// New creates a new interface using the LinkMessage information.
func (l *LinkService) New(req *LinkMessage) error {
	flags := netlink.Request | netlink.Create | netlink.Acknowledge | netlink.Excl
	_, err := l.execute(req, unix.RTM_NEWLINK, flags)

	return err
}

// Delete removes an interface by index.
func (l *LinkService) Delete(index uint32) error {
	req := &LinkMessage{
		Index: index,
	}

	flags := netlink.Request | netlink.Acknowledge
	_, err := l.c.Execute(req, unix.RTM_DELLINK, flags)

	return err
}

// Get retrieves interface information by index.
func (l *LinkService) Get(index uint32) (LinkMessage, error) {
	req := &LinkMessage{
		Index: index,
	}

	flags := netlink.Request | netlink.DumpFiltered
	links, err := l.execute(req, unix.RTM_GETLINK, flags)

	if len(links) != 1 {
		return LinkMessage{}, fmt.Errorf("too many/little matches, expected 1, actual %d", len(links))
	}

	return links[0], err
}

// Set sets interface attributes according to the LinkMessage information.
//
// ref: https://lwn.net/Articles/236919/
// We explicitly use RTM_NEWLINK to set link attributes instead of
// RTM_SETLINK because:
//   - using RTM_SETLINK is actually an old rtnetlink API, not supporting most
//     attributes common today
//   - using RTM_NEWLINK is the preferred way to create AND update links
//   - RTM_NEWLINK is backward compatible to RTM_SETLINK
func (l *LinkService) Set(req *LinkMessage) error {
	flags := netlink.Request | netlink.Acknowledge
	_, err := l.c.Execute(req, unix.RTM_NEWLINK, flags)

	return err
}

// SetMaster enslaves an interface to a master device (such as a bridge or bond).
// The optional slaveConfig parameter allows configuring slave-specific settings
// (such as BridgePort configuration when enslaving to a bridge).
//
// Example usage with a bridge:
//
//	bridgePort := &driver.BridgePort{
//	    Learning:     &driver.BridgeEnableEnabled,
//	    UnicastFlood: &driver.BridgeEnableEnabled,
//	}
//	err := conn.Link.SetMaster(ifaceIndex, bridgeIndex, bridgePort)
//
// To remove an interface from its master, use RemoveMaster instead.
func (l *LinkService) SetMaster(ifaceIndex, masterIndex uint32, slaveConfig LinkSlaveDriver) error {
	// Get current interface state
	rx, err := l.Get(ifaceIndex)
	if err != nil {
		return err
	}

	// Prepare link attributes with master set
	master := masterIndex
	attrs := &LinkAttributes{
		Master:    &master,
		Name:      rx.Attributes.Name,
		MTU:       rx.Attributes.MTU,
		Type:      rx.Attributes.Type,
		QueueDisc: rx.Attributes.QueueDisc,
	}

	// If slave configuration is provided, set the slave info
	if slaveConfig != nil {
		attrs.Info = &LinkInfo{
			SlaveKind: slaveConfig.Kind(),
			SlaveData: slaveConfig,
		}
	}

	// Apply the changes
	tx := &LinkMessage{
		Family:     unix.AF_UNSPEC,
		Type:       rx.Type,
		Index:      ifaceIndex,
		Flags:      rx.Flags,
		Change:     0,
		Attributes: attrs,
	}
	return l.Set(tx)
}

// RemoveMaster un-enslaves an interface from its master device.
// This sets the Master field to 0, removing the interface from any bridge, bond, or other master.
//
// Example usage:
//
//	err := conn.Link.RemoveMaster(ifaceIndex)
func (l *LinkService) RemoveMaster(ifaceIndex uint32) error {
	return l.SetMaster(ifaceIndex, 0, nil)
}

func (l *LinkService) list(kind string) ([]LinkMessage, error) {
	req := &LinkMessage{}
	flags := netlink.Request | netlink.Dump

	if kind == "" {
		return l.execute(req, unix.RTM_GETLINK, flags)
	}

	req.Attributes = &LinkAttributes{
		Info: &LinkInfo{Kind: kind},
	}

	msgs, err := l.execute(req, unix.RTM_GETLINK, flags)

	// All filtered links are marked by a NLM_F_DUMP_FILTERED flag
	// no other links present in a response, so just check the first one
	if err == nil && len(msgs) > 0 && !msgs[0].filtered {
		msgs = []LinkMessage{}
	}

	return msgs, err

}

// ListByKind retrieves all interfaces of a specific kind.
func (l *LinkService) ListByKind(kind string) ([]LinkMessage, error) {
	return l.list(kind)
}

// List retrieves all interfaces.
func (l *LinkService) List() ([]LinkMessage, error) {
	return l.list("")
}

// ListWithVFInfo retrieves all interfaces including SR-IOV VF information.
// This sets the RTEXT_FILTER_VF extended filter mask to request VF details.
func (l *LinkService) ListWithVFInfo() ([]LinkMessage, error) {
	extMask := uint32(unix.RTEXT_FILTER_VF)
	req := &LinkMessage{
		Attributes: &LinkAttributes{
			ExtMask: &extMask,
		},
	}
	flags := netlink.Request | netlink.Dump
	return l.execute(req, unix.RTM_GETLINK, flags)
}

// LinkAttributes contains all attributes for an interface.
type LinkAttributes struct {
	Address          net.HardwareAddr // Interface L2 address
	Alias            *string          // Interface alias name
	AltNames         []string         // Alternative interface names
	Broadcast        net.HardwareAddr // L2 broadcast address
	Carrier          *uint8           // Current physical link state of the interface.
	CarrierChanges   *uint32          // Number of times the link has seen a change from UP to DOWN and vice versa
	CarrierUpCount   *uint32          // Number of times the link has been up
	CarrierDownCount *uint32          // Number of times the link has been down
	ExtMask          *uint32          // Extended info mask for queries (RTEXT_FILTER_*)
	Index            *uint32          // System-wide interface unique index identifier
	Info             *LinkInfo        // Detailed Interface Information
	LinkMode         *uint8           // Interface link mode
	MTU              uint32           // MTU of the device
	Name             string           // Device name
	NetDevGroup      *uint32          // Interface network device group
	NumVF            *uint32          // Number of Virtual Functions (SR-IOV)
	OperationalState OperationalState // Interface operation state
	PhysPortID       *string          // Interface unique physical port identifier within the NIC
	PhysPortName     *string          // Interface physical port name within the NIC
	PhysSwitchID     *string          // Unique physical switch identifier of a switch this port belongs to
	QueueDisc        string           // Queueing discipline
	Master           *uint32          // Master device index (0 value un-enslaves)
	Stats            *LinkStats       // Interface Statistics
	Stats64          *LinkStats64     // Interface Statistics (64 bits version)
	TxQueueLen       *uint32          // Interface transmit queue len in number of packets
	Type             uint32           // Link type
	VFInfoList       []VFInfo         // Virtual Function information list (SR-IOV)
	XDP              *LinkXDP         // Express Data Patch Information
	NetNS            *NetNS           // Interface network namespace
}

// OperationalState represents an interface's operational state.
type OperationalState uint8

// Constants that represent operational state of an interface
//
// Adapted from https://elixir.bootlin.com/linux/v4.19.2/source/include/uapi/linux/if.h#L166
const (
	OperStateUnknown        OperationalState = iota // status could not be determined
	OperStateNotPresent                             // down, due to some missing component (typically hardware)
	OperStateDown                                   // down, either administratively or due to a fault
	OperStateLowerLayerDown                         // down, due to lower-layer interfaces
	OperStateTesting                                // operationally down, in some test mode
	OperStateDormant                                // down, waiting for some external event
	OperStateUp                                     // interface is in a state to send and receive packets
)

// unmarshalBinary unmarshals the contents of a byte slice into a LinkMessage.
func (a *LinkAttributes) decode(ad *netlink.AttributeDecoder) error {
	for ad.Next() {
		switch ad.Type() {
		case unix.IFLA_UNSPEC:
			// unused attribute
		case unix.IFLA_ADDRESS:
			l := len(ad.Bytes())
			if l < 4 || l > 32 {
				return errInvalidLinkMessageAttr
			}
			a.Address = ad.Bytes()
		case unix.IFLA_IFALIAS:
			v := ad.String()
			a.Alias = &v
		case unix.IFLA_BROADCAST:
			l := len(ad.Bytes())
			if l < 4 || l > 32 {
				return errInvalidLinkMessageAttr
			}
			a.Broadcast = ad.Bytes()
		case unix.IFLA_CARRIER:
			v := ad.Uint8()
			a.Carrier = &v
		case unix.IFLA_CARRIER_CHANGES:
			v := ad.Uint32()
			a.CarrierChanges = &v
		case unix.IFLA_CARRIER_UP_COUNT:
			v := ad.Uint32()
			a.CarrierUpCount = &v
		case unix.IFLA_CARRIER_DOWN_COUNT:
			v := ad.Uint32()
			a.CarrierDownCount = &v
		case unix.IFLA_GROUP:
			v := ad.Uint32()
			a.NetDevGroup = &v
		case unix.IFLA_MTU:
			a.MTU = ad.Uint32()
		case unix.IFLA_IFNAME:
			a.Name = ad.String()
		case unix.IFLA_LINK:
			a.Type = ad.Uint32()
		case unix.IFLA_LINKINFO:
			a.Info = &LinkInfo{}
			ad.Nested(a.Info.decode)
		case unix.IFLA_LINKMODE:
			v := ad.Uint8()
			a.LinkMode = &v
		case unix.IFLA_MASTER:
			v := ad.Uint32()
			a.Master = &v
		case unix.IFLA_OPERSTATE:
			a.OperationalState = OperationalState(ad.Uint8())
		case unix.IFLA_PHYS_PORT_ID:
			v := ad.String()
			a.PhysPortID = &v
		case unix.IFLA_PHYS_SWITCH_ID:
			v := ad.String()
			a.PhysSwitchID = &v
		case unix.IFLA_PHYS_PORT_NAME:
			v := ad.String()
			a.PhysPortName = &v
		case unix.IFLA_QDISC:
			a.QueueDisc = ad.String()
		case unix.IFLA_STATS:
			a.Stats = &LinkStats{}
			err := a.Stats.unmarshalBinary(ad.Bytes())
			if err != nil {
				return err
			}
		case unix.IFLA_STATS64:
			a.Stats64 = &LinkStats64{}
			err := a.Stats64.unmarshalBinary(ad.Bytes())
			if err != nil {
				return err
			}
		case unix.IFLA_TXQLEN:
			v := ad.Uint32()
			a.TxQueueLen = &v
		case unix.IFLA_XDP:
			a.XDP = &LinkXDP{}
			ad.Nested(a.XDP.decode)
		case unix.IFLA_PROP_LIST:
			// read nested encoded property list
			nad, err := netlink.NewAttributeDecoder(ad.Bytes())
			if err != nil {
				return err
			}
			for nad.Next() {
				if nad.Type() == unix.IFLA_ALT_IFNAME {
					a.AltNames = append(a.AltNames, nad.String())
				}
			}
		case unix.IFLA_NUM_VF:
			v := ad.Uint32()
			a.NumVF = &v
		case unix.IFLA_VFINFO_LIST:
			nad, err := netlink.NewAttributeDecoder(ad.Bytes())
			if err != nil {
				return err
			}
			nad.ByteOrder = nativeEndian
			vfs, err := decodeVFInfoList(nad)
			if err != nil {
				return err
			}
			a.VFInfoList = vfs
		}
	}

	return nil
}

// MarshalBinary marshals a LinkAttributes into a byte slice.
func (a *LinkAttributes) encode(ae *netlink.AttributeEncoder) error {
	if a.Name != "" {
		ae.String(unix.IFLA_IFNAME, a.Name)
	}

	if a.Alias != nil {
		ae.String(unix.IFLA_IFALIAS, *a.Alias)
	}

	if a.Type != 0 {
		ae.Uint32(unix.IFLA_LINK, a.Type)
	}

	if a.QueueDisc != "" {
		ae.String(unix.IFLA_QDISC, a.QueueDisc)
	}

	if a.MTU != 0 {
		ae.Uint32(unix.IFLA_MTU, a.MTU)
	}

	if len(a.Address) != 0 {
		ae.Bytes(unix.IFLA_ADDRESS, a.Address)
	}

	if len(a.Broadcast) != 0 {
		ae.Bytes(unix.IFLA_BROADCAST, a.Broadcast)
	}

	if a.OperationalState != OperStateUnknown {
		ae.Uint8(unix.IFLA_OPERSTATE, uint8(a.OperationalState))
	}

	if a.Info != nil {
		nae := netlink.NewAttributeEncoder()
		nae.ByteOrder = ae.ByteOrder

		err := a.Info.encode(nae)
		if err != nil {
			return err
		}
		b, err := nae.Encode()
		if err != nil {
			return err
		}
		ae.Bytes(unix.IFLA_LINKINFO, b)
	}

	if a.XDP != nil {
		nae := netlink.NewAttributeEncoder()
		nae.ByteOrder = ae.ByteOrder

		err := a.XDP.encode(nae)
		if err != nil {
			return err
		}
		b, err := nae.Encode()
		if err != nil {
			return err
		}

		ae.Bytes(unix.IFLA_XDP, b)
	}

	if a.Master != nil {
		ae.Uint32(unix.IFLA_MASTER, *a.Master)
	}

	if a.NetNS != nil {
		ae.Uint32(a.NetNS.value())
	}

	if a.ExtMask != nil {
		ae.Uint32(unix.IFLA_EXT_MASK, *a.ExtMask)
	}

	return nil
}

// LinkStats contains packet statistics
type LinkStats struct {
	RXPackets  uint32 // total packets received
	TXPackets  uint32 // total packets transmitted
	RXBytes    uint32 // total bytes received
	TXBytes    uint32 // total bytes transmitted
	RXErrors   uint32 // bad packets received
	TXErrors   uint32 // packet transmit problems
	RXDropped  uint32 // no space in linux buffers
	TXDropped  uint32 // no space available in linux
	Multicast  uint32 // multicast packets received
	Collisions uint32

	// detailed rx_errors:
	RXLengthErrors uint32
	RXOverErrors   uint32 // receiver ring buff overflow
	RXCRCErrors    uint32 // recved pkt with crc error
	RXFrameErrors  uint32 // recv'd frame alignment error
	RXFIFOErrors   uint32 // recv'r fifo overrun
	RXMissedErrors uint32 // receiver missed packet

	// detailed tx_errors
	TXAbortedErrors   uint32
	TXCarrierErrors   uint32
	TXFIFOErrors      uint32
	TXHeartbeatErrors uint32
	TXWindowErrors    uint32

	// for cslip etc
	RXCompressed uint32
	TXCompressed uint32

	RXNoHandler uint32 // dropped, no handler found
}

// unmarshalBinary unmarshals the contents of a byte slice into a LinkMessage.
func (a *LinkStats) unmarshalBinary(b []byte) error {
	l := len(b)
	if l != 92 && l != 96 {
		return fmt.Errorf("incorrect LinkMessage size, want: 92 or 96, got: %d", len(b))
	}

	a.RXPackets = nativeEndian.Uint32(b[0:4])
	a.TXPackets = nativeEndian.Uint32(b[4:8])
	a.RXBytes = nativeEndian.Uint32(b[8:12])
	a.TXBytes = nativeEndian.Uint32(b[12:16])
	a.RXErrors = nativeEndian.Uint32(b[16:20])
	a.TXErrors = nativeEndian.Uint32(b[20:24])
	a.RXDropped = nativeEndian.Uint32(b[24:28])
	a.TXDropped = nativeEndian.Uint32(b[28:32])
	a.Multicast = nativeEndian.Uint32(b[32:36])
	a.Collisions = nativeEndian.Uint32(b[36:40])

	a.RXLengthErrors = nativeEndian.Uint32(b[40:44])
	a.RXOverErrors = nativeEndian.Uint32(b[44:48])
	a.RXCRCErrors = nativeEndian.Uint32(b[48:52])
	a.RXFrameErrors = nativeEndian.Uint32(b[52:56])
	a.RXFIFOErrors = nativeEndian.Uint32(b[56:60])
	a.RXMissedErrors = nativeEndian.Uint32(b[60:64])

	a.TXAbortedErrors = nativeEndian.Uint32(b[64:68])
	a.TXCarrierErrors = nativeEndian.Uint32(b[68:72])
	a.TXFIFOErrors = nativeEndian.Uint32(b[72:76])
	a.TXHeartbeatErrors = nativeEndian.Uint32(b[76:80])
	a.TXWindowErrors = nativeEndian.Uint32(b[80:84])

	a.RXCompressed = nativeEndian.Uint32(b[84:88])
	a.TXCompressed = nativeEndian.Uint32(b[88:92])

	if l == 96 { // kernel 4.6+
		a.RXNoHandler = nativeEndian.Uint32(b[92:96])
	}

	return nil
}

// LinkStats64 contains packet statistics
type LinkStats64 struct {
	RXPackets  uint64 // total packets received
	TXPackets  uint64 // total packets transmitted
	RXBytes    uint64 // total bytes received
	TXBytes    uint64 // total bytes transmitted
	RXErrors   uint64 // bad packets received
	TXErrors   uint64 // packet transmit problems
	RXDropped  uint64 // no space in linux buffers
	TXDropped  uint64 // no space available in linux
	Multicast  uint64 // multicast packets received
	Collisions uint64

	// detailed rx_errors:
	RXLengthErrors uint64
	RXOverErrors   uint64 // receiver ring buff overflow
	RXCRCErrors    uint64 // recved pkt with crc error
	RXFrameErrors  uint64 // recv'd frame alignment error
	RXFIFOErrors   uint64 // recv'r fifo overrun
	RXMissedErrors uint64 // receiver missed packet

	// detailed tx_errors
	TXAbortedErrors   uint64
	TXCarrierErrors   uint64
	TXFIFOErrors      uint64
	TXHeartbeatErrors uint64
	TXWindowErrors    uint64

	// for cslip etc
	RXCompressed uint64
	TXCompressed uint64

	RXNoHandler uint64 // dropped, no handler found

	RXOtherhostDropped uint64 // Number of packets dropped due to mismatch in destination MAC address.
}

// unmarshalBinary unmarshals the contents of a byte slice into a LinkMessage.
func (a *LinkStats64) unmarshalBinary(b []byte) error {
	l := len(b)
	if l != 184 && l != 192 && l != 200 {
		return fmt.Errorf("incorrect size, want: 184 or 192 or 200")
	}

	a.RXPackets = nativeEndian.Uint64(b[0:8])
	a.TXPackets = nativeEndian.Uint64(b[8:16])
	a.RXBytes = nativeEndian.Uint64(b[16:24])
	a.TXBytes = nativeEndian.Uint64(b[24:32])
	a.RXErrors = nativeEndian.Uint64(b[32:40])
	a.TXErrors = nativeEndian.Uint64(b[40:48])
	a.RXDropped = nativeEndian.Uint64(b[48:56])
	a.TXDropped = nativeEndian.Uint64(b[56:64])
	a.Multicast = nativeEndian.Uint64(b[64:72])
	a.Collisions = nativeEndian.Uint64(b[72:80])

	a.RXLengthErrors = nativeEndian.Uint64(b[80:88])
	a.RXOverErrors = nativeEndian.Uint64(b[88:96])
	a.RXCRCErrors = nativeEndian.Uint64(b[96:104])
	a.RXFrameErrors = nativeEndian.Uint64(b[104:112])
	a.RXFIFOErrors = nativeEndian.Uint64(b[112:120])
	a.RXMissedErrors = nativeEndian.Uint64(b[120:128])

	a.TXAbortedErrors = nativeEndian.Uint64(b[128:136])
	a.TXCarrierErrors = nativeEndian.Uint64(b[136:144])
	a.TXFIFOErrors = nativeEndian.Uint64(b[144:152])
	a.TXHeartbeatErrors = nativeEndian.Uint64(b[152:160])
	a.TXWindowErrors = nativeEndian.Uint64(b[160:168])

	a.RXCompressed = nativeEndian.Uint64(b[168:176])
	a.TXCompressed = nativeEndian.Uint64(b[176:184])

	if l > 191 { // kernel 4.6+
		a.RXNoHandler = nativeEndian.Uint64(b[184:192])
	}

	if l > 199 { // kernel 5.19+
		a.RXOtherhostDropped = nativeEndian.Uint64(b[192:200])
	}

	return nil
}

// VFLinkState represents the link state of a VF
type VFLinkState uint32

// Constants for VF link state
const (
	VFLinkStateAuto    VFLinkState = iota // link state of the uplink
	VFLinkStateEnable                     // link always up
	VFLinkStateDisable                    // link always down
)

// VFStats contains statistics for a Virtual Function
type VFStats struct {
	RxPackets uint64
	TxPackets uint64
	RxBytes   uint64
	TxBytes   uint64
	Broadcast uint64
	Multicast uint64
	RxDropped uint64
	TxDropped uint64
}

func (s *VFStats) decode(ad *netlink.AttributeDecoder) error {
	for ad.Next() {
		switch ad.Type() {
		case unix.IFLA_VF_STATS_RX_PACKETS:
			s.RxPackets = ad.Uint64()
		case unix.IFLA_VF_STATS_TX_PACKETS:
			s.TxPackets = ad.Uint64()
		case unix.IFLA_VF_STATS_RX_BYTES:
			s.RxBytes = ad.Uint64()
		case unix.IFLA_VF_STATS_TX_BYTES:
			s.TxBytes = ad.Uint64()
		case unix.IFLA_VF_STATS_BROADCAST:
			s.Broadcast = ad.Uint64()
		case unix.IFLA_VF_STATS_MULTICAST:
			s.Multicast = ad.Uint64()
		case unix.IFLA_VF_STATS_RX_DROPPED:
			s.RxDropped = ad.Uint64()
		case unix.IFLA_VF_STATS_TX_DROPPED:
			s.TxDropped = ad.Uint64()
		}
	}
	return nil
}

// VFInfo contains information about a Virtual Function
type VFInfo struct {
	ID         uint32           // VF index
	MAC        net.HardwareAddr // VF MAC address
	Broadcast  net.HardwareAddr // VF broadcast address
	Vlan       uint32           // VLAN ID
	Qos        uint32           // VLAN QoS
	TxRate     uint32           // Max TX bandwidth (deprecated, use MaxTxRate)
	MinTxRate  uint32           // Min TX bandwidth in Mbps
	MaxTxRate  uint32           // Max TX bandwidth in Mbps
	SpoofCheck bool             // Spoof checking enabled
	LinkState  VFLinkState      // Link state
	RssQuery   bool             // RSS query enabled
	Trust      bool             // VF trust setting
	Stats      *VFStats         // VF statistics
}

func (vf *VFInfo) decode(ad *netlink.AttributeDecoder) error {
	var firstVFID *uint32

	for ad.Next() {
		switch ad.Type() {
		case unix.IFLA_VF_MAC:
			b := ad.Bytes()
			if len(b) < 36 { // struct ifla_vf_mac: 4 bytes vf + 32 bytes mac
				return errInvalidLinkMessageAttr
			}
			vfID := nativeEndian.Uint32(b[0:4])
			if firstVFID == nil {
				firstVFID = &vfID
				vf.ID = vfID
			} else if *firstVFID != vfID {
				return fmt.Errorf("inconsistent VF ID: expected %d, got %d", *firstVFID, vfID)
			}
			// MAC is typically 6 bytes, but kernel struct has 32 bytes
			vf.MAC = make(net.HardwareAddr, 6)
			copy(vf.MAC, b[4:10])
		case unix.IFLA_VF_BROADCAST:
			b := ad.Bytes()
			if len(b) < 32 { // struct ifla_vf_broadcast: 32 bytes
				return errInvalidLinkMessageAttr
			}
			vf.Broadcast = make(net.HardwareAddr, 6)
			copy(vf.Broadcast, b[0:6])
		case unix.IFLA_VF_VLAN:
			b := ad.Bytes()
			if len(b) < 12 { // struct ifla_vf_vlan: 4 bytes vf + 4 bytes vlan + 4 bytes qos
				return errInvalidLinkMessageAttr
			}
			vfID := nativeEndian.Uint32(b[0:4])
			if firstVFID == nil {
				firstVFID = &vfID
				vf.ID = vfID
			} else if *firstVFID != vfID {
				return fmt.Errorf("inconsistent VF ID: expected %d, got %d", *firstVFID, vfID)
			}
			vf.Vlan = nativeEndian.Uint32(b[4:8])
			vf.Qos = nativeEndian.Uint32(b[8:12])
		case unix.IFLA_VF_TX_RATE:
			b := ad.Bytes()
			if len(b) < 8 { // struct ifla_vf_tx_rate: 4 bytes vf + 4 bytes rate
				return errInvalidLinkMessageAttr
			}
			vfID := nativeEndian.Uint32(b[0:4])
			if firstVFID == nil {
				firstVFID = &vfID
				vf.ID = vfID
			} else if *firstVFID != vfID {
				return fmt.Errorf("inconsistent VF ID: expected %d, got %d", *firstVFID, vfID)
			}
			vf.TxRate = nativeEndian.Uint32(b[4:8])
		case unix.IFLA_VF_RATE:
			b := ad.Bytes()
			if len(b) < 12 { // struct ifla_vf_rate: 4 bytes vf + 4 bytes min + 4 bytes max
				return errInvalidLinkMessageAttr
			}
			vfID := nativeEndian.Uint32(b[0:4])
			if firstVFID == nil {
				firstVFID = &vfID
				vf.ID = vfID
			} else if *firstVFID != vfID {
				return fmt.Errorf("inconsistent VF ID: expected %d, got %d", *firstVFID, vfID)
			}
			vf.MinTxRate = nativeEndian.Uint32(b[4:8])
			vf.MaxTxRate = nativeEndian.Uint32(b[8:12])
		case unix.IFLA_VF_SPOOFCHK:
			b := ad.Bytes()
			if len(b) < 8 { // struct ifla_vf_spoofchk: 4 bytes vf + 4 bytes setting
				return errInvalidLinkMessageAttr
			}
			vfID := nativeEndian.Uint32(b[0:4])
			if firstVFID == nil {
				firstVFID = &vfID
				vf.ID = vfID
			} else if *firstVFID != vfID {
				return fmt.Errorf("inconsistent VF ID: expected %d, got %d", *firstVFID, vfID)
			}
			vf.SpoofCheck = nativeEndian.Uint32(b[4:8]) != 0
		case unix.IFLA_VF_LINK_STATE:
			b := ad.Bytes()
			if len(b) < 8 { // struct ifla_vf_link_state: 4 bytes vf + 4 bytes link_state
				return errInvalidLinkMessageAttr
			}
			vfID := nativeEndian.Uint32(b[0:4])
			if firstVFID == nil {
				firstVFID = &vfID
				vf.ID = vfID
			} else if *firstVFID != vfID {
				return fmt.Errorf("inconsistent VF ID: expected %d, got %d", *firstVFID, vfID)
			}
			vf.LinkState = VFLinkState(nativeEndian.Uint32(b[4:8]))
		case unix.IFLA_VF_RSS_QUERY_EN:
			b := ad.Bytes()
			if len(b) < 8 { // struct ifla_vf_rss_query_en: 4 bytes vf + 4 bytes setting
				return errInvalidLinkMessageAttr
			}
			vfID := nativeEndian.Uint32(b[0:4])
			if firstVFID == nil {
				firstVFID = &vfID
				vf.ID = vfID
			} else if *firstVFID != vfID {
				return fmt.Errorf("inconsistent VF ID: expected %d, got %d", *firstVFID, vfID)
			}
			vf.RssQuery = nativeEndian.Uint32(b[4:8]) != 0
		case unix.IFLA_VF_TRUST:
			b := ad.Bytes()
			if len(b) < 8 { // struct ifla_vf_trust: 4 bytes vf + 4 bytes setting
				return errInvalidLinkMessageAttr
			}
			vfID := nativeEndian.Uint32(b[0:4])
			if firstVFID == nil {
				firstVFID = &vfID
				vf.ID = vfID
			} else if *firstVFID != vfID {
				return fmt.Errorf("inconsistent VF ID: expected %d, got %d", *firstVFID, vfID)
			}
			vf.Trust = nativeEndian.Uint32(b[4:8]) != 0
		case unix.IFLA_VF_STATS:
			vf.Stats = &VFStats{}
			ad.Nested(vf.Stats.decode)
		}
	}
	return nil
}

func decodeVFInfoList(ad *netlink.AttributeDecoder) ([]VFInfo, error) {
	var vfs []VFInfo
	for ad.Next() {
		if ad.Type() == unix.IFLA_VF_INFO {
			vf := VFInfo{}
			ad.Nested(vf.decode)
			vfs = append(vfs, vf)
		}
	}
	return vfs, ad.Err()
}

var (
	// registeredDrivers is the global map of registered drivers
	registeredDrivers = make(map[string]LinkDriver)

	// registeredSlaveDrivers is the global map of registered slave drivers
	registeredSlaveDrivers = make(map[string]LinkDriver)
)

// RegisterDriver registers a driver with the link service
// This allows the driver to be used to encode/decode the link data
//
// This function is not threadsafe. This should not be used after Dial
func RegisterDriver(d LinkDriver) error {
	if _, ok := d.(LinkSlaveDriver); ok {
		if _, ok := registeredSlaveDrivers[d.Kind()]; ok {
			return fmt.Errorf("driver %s already registered", d.Kind())
		}
		registeredSlaveDrivers[d.Kind()] = d
		return nil
	}
	if _, ok := registeredDrivers[d.Kind()]; ok {
		return fmt.Errorf("driver %s already registered", d.Kind())
	}
	registeredDrivers[d.Kind()] = d
	return nil
}

// getDriver returns the driver instance for the given kind, and true if the driver is registered
// it returns the default (LinkData) driver, and false if the driver is not registered
func getDriver(kind string, slave bool) (LinkDriver, bool) {
	if slave {
		if t, ok := registeredSlaveDrivers[kind]; ok {
			return t.New(), true
		}

		return &LinkData{Name: kind, Slave: true}, false
	}
	if t, ok := registeredDrivers[kind]; ok {
		return t.New(), true
	}

	return &LinkData{Name: kind}, false
}

// LinkDriver is the interface that wraps link-specific Encode, Decode, and Kind methods
type LinkDriver interface {
	// New returns a new instance of the LinkDriver
	New() LinkDriver

	// Encode the driver data into the netlink message attribute
	Encode(*netlink.AttributeEncoder) error

	// Decode the driver data from the netlink message attribute
	Decode(*netlink.AttributeDecoder) error

	// Return the driver kind as string, this will be matched with the LinkInfo.Kind to find a driver to decode the data
	Kind() string
}

// LinkSlaveDriver defines a LinkDriver with Slave method
type LinkSlaveDriver interface {
	LinkDriver

	// Slave method specifies driver is a slave link info
	Slave()
}

// LinkDriverVerifier defines a LinkDriver with Verify method
type LinkDriverVerifier interface {
	LinkDriver

	//  Verify function run before Encode function to check for correctness and
	//  pass related values that otherwise unavailable to the driver
	Verify(*LinkMessage) error
}

// LinkData implements the default LinkDriver interface for not registered drivers
type LinkData struct {
	Name  string
	Data  []byte
	Slave bool
}

var _ LinkDriver = &LinkData{}

func (d *LinkData) New() LinkDriver {
	return &LinkData{}
}

func (d *LinkData) Decode(ad *netlink.AttributeDecoder) error {
	d.Data = ad.Bytes()
	return nil
}

func (d *LinkData) Encode(ae *netlink.AttributeEncoder) error {
	if len(d.Data) == 0 {
		return nil
	}
	if d.Slave {
		ae.Bytes(unix.IFLA_INFO_SLAVE_DATA, d.Data)
	} else {
		ae.Bytes(unix.IFLA_INFO_DATA, d.Data)
	}
	return nil
}

func (d *LinkData) Kind() string {
	return d.Name
}

// LinkInfo contains data for specific network types
type LinkInfo struct {
	Kind      string     // Driver name
	Data      LinkDriver // Driver specific configuration stored as nested Netlink messages
	SlaveKind string     // Slave driver name
	SlaveData LinkDriver // Slave driver specific configuration
}

func (i *LinkInfo) decode(ad *netlink.AttributeDecoder) error {
	for ad.Next() {
		switch ad.Type() {
		case unix.IFLA_INFO_KIND:
			i.Kind = ad.String()
		case unix.IFLA_INFO_SLAVE_KIND:
			i.SlaveKind = ad.String()
		case unix.IFLA_INFO_DATA:
			driver, found := getDriver(i.Kind, false)
			i.Data = driver
			if found {
				ad.Nested(i.Data.Decode)
				continue
			}
			_ = i.Data.Decode(ad)
		case unix.IFLA_INFO_SLAVE_DATA:
			driver, found := getDriver(i.SlaveKind, true)
			i.SlaveData = driver
			if found {
				ad.Nested(i.SlaveData.Decode)
				continue
			}
			_ = i.SlaveData.Decode(ad)
		}
	}
	return nil
}

func (i *LinkInfo) encode(ae *netlink.AttributeEncoder) error {
	ae.String(unix.IFLA_INFO_KIND, i.Kind)
	if i.Data != nil {
		if i.Kind != i.Data.Kind() {
			return fmt.Errorf("driver kind %s is not equal to info kind %s", i.Data.Kind(), i.Kind)
		}
		if _, ok := i.Data.(*LinkData); ok {
			_ = i.Data.Encode(ae)
		} else {
			ae.Nested(unix.IFLA_INFO_DATA, i.Data.Encode)
		}
	}
	if i.SlaveData != nil {
		if i.SlaveKind != i.SlaveData.Kind() {
			return fmt.Errorf("slave driver kind %s is not equal to slave info kind %s", i.SlaveData.Kind(), i.SlaveKind)
		}
		ae.String(unix.IFLA_INFO_SLAVE_KIND, i.SlaveKind)
		if _, ok := i.SlaveData.(*LinkData); ok {
			_ = i.SlaveData.Encode(ae)
		} else {
			ae.Nested(unix.IFLA_INFO_SLAVE_DATA, i.SlaveData.Encode)
		}
	}

	return nil
}

// LinkXDP holds Express Data Path specific information
type LinkXDP struct {
	FD         int32
	ExpectedFD int32
	Attached   uint8
	Flags      uint32
	ProgID     uint32
}

func (xdp *LinkXDP) decode(ad *netlink.AttributeDecoder) error {
	for ad.Next() {
		switch ad.Type() {
		case unix.IFLA_XDP_FD:
			xdp.FD = ad.Int32()
		case unix.IFLA_XDP_EXPECTED_FD:
			xdp.ExpectedFD = ad.Int32()
		case unix.IFLA_XDP_ATTACHED:
			xdp.Attached = ad.Uint8()
		case unix.IFLA_XDP_FLAGS:
			xdp.Flags = ad.Uint32()
		case unix.IFLA_XDP_PROG_ID:
			xdp.ProgID = ad.Uint32()
		}
	}
	return nil
}

func (xdp *LinkXDP) encode(ae *netlink.AttributeEncoder) error {
	ae.Int32(unix.IFLA_XDP_FD, xdp.FD)
	ae.Int32(unix.IFLA_XDP_EXPECTED_FD, xdp.ExpectedFD)
	ae.Uint32(unix.IFLA_XDP_FLAGS, xdp.Flags)
	// XDP_ATTACHED and XDP_PROG_ID are things that can only be returned by the
	// kernel, so we don't encode them. source:
	// https://elixir.bootlin.com/linux/v5.10.15/source/net/core/rtnetlink.c#L2894
	return nil
}
