package snmp

import (
	"bytes"
	"encoding/binary"
	"git.loafle.net/overflow/overflow_probe/matcher/packet"
	"git.loafle.net/overflow/overflow_probe/model/scaninfo"
)

const (
	SNMP_START_SEQUENCE uint8 = 0X30
	SNMP_TYPE_INTEGER   uint8 = 0X02
	SNMP_TYPE_STRING    uint8 = 0X04
	SNMP_TYPE_NULL      uint8 = 0X05
	SNMP_TYPE_OBJECT    uint8 = 0X06
	SNMP_GET_REQUEST    uint8 = 0XA0
	//SNMP_RESPONSE            uint8  = 0XA2
	SNMP_NO_DESC             uint16 = 0X0004
	SNMP_END_SEQUENCE        uint8  = 0X30
	SNMP_PROTOCOL_VERSION_3  uint8  = 0X03
	SNMP_PROTOCOL_VERSION_2c uint8  = 0X01
	SNMP_MSG_ID_MAX_VALUE    uint32 = 0xFFFFFF7F
)

type snmpv2VarBinding struct {
	VarBindindStart uint8
	VarBindLen      uint8
	ObjectStart     uint8
	ObjectLen       uint8
	ValueType       uint8
	ValueLen        uint8
	ObjectValue     uint64
	NullValue       uint8
	EndIndicator    uint8
}

type snmpv2Data struct {
	DataType        uint8
	DataLen         uint8
	RequestIdType   uint8
	RequestIdLen    uint8
	RequestId       uint8
	ErrorStatusType uint8
	ErrorStatusLen  uint8
	ErrorStatus     uint8
	ErrorIndexType  uint8
	ErrorIndexLen   uint8
	ErrorIndex      uint8

	VarBinding snmpv2VarBinding
}

type snmpv2 struct {
	StartSeq             uint8
	SeqLen               uint8
	SNMPVersionType      uint8
	SNMPVersionLen       uint8
	SNMPVersion          uint8
	CommunityVersionType uint8
	CommunityVersionLen  uint8
	Community            [6]byte

	Data snmpv2Data
}

type SNMPv2Matcher struct {
	packets []*packet.Packet
}

func NewSNMPv2Matcher() *SNMPv2Matcher {

	snmpMatcher := &SNMPv2Matcher{}

	snmpTempBuf := new(bytes.Buffer)
	binary.Write(snmpTempBuf, binary.BigEndian, snmpv2{}) //For getting the struct size

	snmpDataTempBuf := new(bytes.Buffer)
	binary.Write(snmpDataTempBuf, binary.BigEndian, snmpv2Data{}) //For getting the struct size

	snmpVarTempBuf := new(bytes.Buffer)
	binary.Write(snmpVarTempBuf, binary.BigEndian, snmpv2VarBinding{}) //For getting the struct size

	q := snmpv2{}
	q.StartSeq = SNMP_START_SEQUENCE
	q.SeqLen = uint8(len(snmpTempBuf.Bytes())) - 2
	q.SNMPVersionType = SNMP_TYPE_INTEGER
	q.SNMPVersionLen = 0x01
	q.SNMPVersion = SNMP_PROTOCOL_VERSION_2c
	q.CommunityVersionType = SNMP_TYPE_STRING
	q.CommunityVersionLen = 0x06
	var community [6]byte
	copy(community[:], "public")
	q.Community = community
	q.Data.DataType = SNMP_GET_REQUEST
	q.Data.DataLen = uint8(len(snmpDataTempBuf.Bytes())) - 2
	q.Data.RequestIdType = SNMP_TYPE_INTEGER
	q.Data.RequestIdLen = 0x01
	q.Data.RequestId = 0x01
	q.Data.ErrorStatusType = SNMP_TYPE_INTEGER
	q.Data.ErrorStatusLen = 0x01
	q.Data.ErrorStatus = 0x00
	q.Data.ErrorIndexType = SNMP_TYPE_INTEGER
	q.Data.ErrorIndexLen = 0x01
	q.Data.ErrorIndex = 0x00
	q.Data.VarBinding.VarBindindStart = SNMP_START_SEQUENCE
	q.Data.VarBinding.VarBindLen = uint8(len(snmpVarTempBuf.Bytes())) - 2
	q.Data.VarBinding.ObjectStart = SNMP_START_SEQUENCE
	q.Data.VarBinding.ObjectLen = uint8(len(snmpVarTempBuf.Bytes())) - 4
	q.Data.VarBinding.ValueType = SNMP_TYPE_OBJECT
	q.Data.VarBinding.ValueLen = 0x08
	q.Data.VarBinding.ObjectValue = 0x000001010201062b
	q.Data.VarBinding.NullValue = SNMP_TYPE_NULL
	q.Data.VarBinding.EndIndicator = 0x00

	writer := new(bytes.Buffer)
	binary.Write(writer, binary.LittleEndian, q)

	snmpMatcher.packets = append(snmpMatcher.packets, packet.NewPacket(writer.Bytes(), writer.Len()))

	return snmpMatcher
}

func (t *SNMPv2Matcher) ServiceName() string {
	return "SNMP"
}

func (t *SNMPv2Matcher) PacketCount() int {
	return len(t.packets)
}
func (t *SNMPv2Matcher) Packet(index int) *packet.Packet {
	return t.packets[index]
}

func (t *SNMPv2Matcher) IsError(index int, packet *packet.Packet, info scaninfo.ServiceScanInfo) bool {
	return false
}

func (t *SNMPv2Matcher) IsNoResponse(index int) bool {
	return false
}

func (t *SNMPv2Matcher) IsPrePacket() bool {
	return false
}

func (t *SNMPv2Matcher) Match(index int, packet *packet.Packet, info scaninfo.ServiceScanInfo) bool {

	if packet == nil {
		return false
	}

	r := new(bytes.Buffer)
	r.Write(packet.Buffer)

	s := snmpv2{}
	if err := binary.Read(r, binary.BigEndian, &s); err != nil {
		return false
	}

	if s.StartSeq != SNMP_START_SEQUENCE {
		return false
	}

	var p uint8
	reader := new(bytes.Buffer)
	reader.Write(packet.Buffer)
	for idx := 0; idx < 5; idx++ {
		if err := binary.Read(reader, binary.BigEndian, &p); err != nil {
			return false
		}
		if p == SNMP_TYPE_INTEGER {
			break
		}
		p++
	}

	//finding protocol version type : 0x02 0x01 0x01
	if err := binary.Read(reader, binary.BigEndian, &p); err != nil {
		return false
	}

	if p == 0x01 {
		if err := binary.Read(reader, binary.BigEndian, &p); err != nil {
			return false
		}
		if p == 0x01 {
			return true
		}
	}

	return false

}

func (t *SNMPv2Matcher) IsSend(port int) bool {
	if port == 161 {
		return true
	}
	return false
}
