package snmp

import (
	"bytes"
	"encoding/binary"
	"git.loafle.net/overflow/overflow_probe/discovery/core/scan/service/matcher/packet"
	"git.loafle.net/overflow/overflow_probe/discovery/discovery/types"
)

type snmpv3GlobalData struct {
	GlobalDataStartSeq   uint8
	GlobalDataLen        uint8
	MsgIdType            uint8
	MsgIdLen             uint8
	MsgId                uint32
	MsgMaxSizeType       uint8
	MsgMaxSizeLen        uint8
	MsgMaxSize           [3]uint8
	MsgFlagsType         uint8
	MsgFlagsTypeLen      uint8
	MsgFlags             uint8
	MsgSecurityModelType uint8
	MsgSecurityModelLen  uint8
	MsgSecurityModel     uint8
}

type snmpv3MsgData struct {
	MsgDataStartSeq   uint8
	MsgDataLen        uint8
	ContextEngineId   uint16
	ContextEngineName uint16
	SnmpType          uint8
	Len               uint8
	RequestIdType     uint8
	RequestIdLen      uint8
	RequestId         uint32
	ErrorStatusType   uint8
	ErrorStatusLen    uint8
	ErrorStatus       uint8
	ErrorIndexType    uint8
	ErrorIndexLen     uint8
	ErrorIndex        uint8
	EndSeq            uint8
	EndIndicator      uint8
}

type snmpv3 struct {
	StartSeq                 uint8
	SeqLen                   uint8
	SNMPVersionType          uint8
	SNMPVersionLen           uint8
	SNMPVersion              uint8
	MsgGlobalData            snmpv3GlobalData
	Unk1                     uint16
	Unk2                     uint16
	MsgAuthoritativeEngineId uint16

	MsgAuthoritativeEngineBootsType uint8
	MsgAuthoritativeEngineBootsLen  uint8
	MsgAuthoritativeEngineBoots     uint8
	MsgAuthoritativeEngineTimeType  uint8
	MsgAuthoritativeEngineTimeLen   uint8
	MsgAuthoritativeEngineTime      uint8
	MsgUserName                     uint16
	MsgAuthenticationParam          uint16
	MsgPrivacyParam                 uint16
	MsgData                         snmpv3MsgData
}

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

func NewSNMPv3Matcher() *SNMPv3Matcher {

	snmpMatcher := &SNMPv3Matcher{}

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

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

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

	q := snmpv3{}
	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_3
	q.MsgGlobalData.GlobalDataStartSeq = SNMP_START_SEQUENCE
	q.MsgGlobalData.GlobalDataLen = uint8(len(snmpGlobalTempBuf.Bytes())) - 2
	q.MsgGlobalData.MsgIdType = SNMP_TYPE_INTEGER
	q.MsgGlobalData.MsgIdLen = 0x04
	q.MsgGlobalData.MsgId = SNMP_MSG_ID_MAX_VALUE
	q.MsgGlobalData.MsgMaxSizeType = SNMP_TYPE_INTEGER
	q.MsgGlobalData.MsgMaxSizeLen = 0x03
	q.MsgGlobalData.MsgMaxSize[2] = 0xe3
	q.MsgGlobalData.MsgMaxSize[1] = 0xff
	q.MsgGlobalData.MsgMaxSize[0] = 0x00
	q.MsgGlobalData.MsgFlagsType = SNMP_TYPE_STRING
	q.MsgGlobalData.MsgFlagsTypeLen = 0x01
	q.MsgGlobalData.MsgFlags = 0x04
	q.MsgGlobalData.MsgSecurityModelType = SNMP_TYPE_INTEGER
	q.MsgGlobalData.MsgSecurityModelLen = 0x01
	q.MsgGlobalData.MsgSecurityModel = 0x03
	q.Unk1 = 0x1004
	q.Unk2 = 0x0e30
	q.MsgAuthoritativeEngineId = SNMP_NO_DESC
	q.MsgAuthoritativeEngineBootsType = SNMP_TYPE_INTEGER
	q.MsgAuthoritativeEngineBootsLen = 0x01
	q.MsgAuthoritativeEngineBoots = 0x00
	q.MsgAuthoritativeEngineTimeType = SNMP_TYPE_INTEGER
	q.MsgAuthoritativeEngineTimeLen = 0x01
	q.MsgAuthoritativeEngineTime = 0x00
	q.MsgUserName = SNMP_NO_DESC
	q.MsgAuthenticationParam = SNMP_NO_DESC
	q.MsgPrivacyParam = SNMP_NO_DESC

	q.MsgData.MsgDataStartSeq = SNMP_START_SEQUENCE
	q.MsgData.MsgDataLen = uint8(len(snmpMsgDataTempBuf.Bytes())) - 2
	q.MsgData.ContextEngineId = SNMP_NO_DESC
	q.MsgData.ContextEngineName = SNMP_NO_DESC
	q.MsgData.SnmpType = SNMP_GET_REQUEST
	q.MsgData.Len = 0x0E
	q.MsgData.RequestIdType = SNMP_TYPE_INTEGER
	q.MsgData.RequestIdLen = 0x04
	q.MsgData.RequestId = 0x00 //
	q.MsgData.ErrorStatusType = SNMP_TYPE_INTEGER
	q.MsgData.ErrorStatusLen = 0x01
	q.MsgData.ErrorStatus = 0x00
	q.MsgData.ErrorIndexType = SNMP_TYPE_INTEGER
	q.MsgData.ErrorIndexLen = 0x01
	q.MsgData.ErrorIndex = 0x00
	q.MsgData.EndSeq = SNMP_END_SEQUENCE
	q.MsgData.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 *SNMPv3Matcher) ServiceName() string {
	return "SNMP"
}

func (t *SNMPv3Matcher) PacketCount() int {
	return len(t.packets)
}

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

func (t *SNMPv3Matcher) IsError(index int, packet *packet.Packet, info *types.ServiceScanInfo) bool {
	return false
}
func (t *SNMPv3Matcher) IsNoResponse(index int) bool {
	return false
}

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

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

	if packet == nil {
		return false
	}

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

	s := snmpv3{}
	if err := binary.Read(reader, binary.LittleEndian, &s); err != nil {
		return false
	}

	if s.StartSeq != SNMP_START_SEQUENCE {
		return false
	}

	var p uint8
	r := new(bytes.Buffer)
	r.Write(packet.Buffer)

	for {
		binary.Read(r, binary.LittleEndian, &p)

		if p == SNMP_TYPE_INTEGER {
			break
		}
	}

	binary.Read(r, binary.BigEndian, &p)
	if p == 0x01 {
		binary.Read(r, binary.BigEndian, &p)
		if p == 0x03 {
			return true
		}
	}

	return false

}

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