// SPDX-FileCopyrightText: 2023 Free Mobile
// SPDX-License-Identifier: AGPL-3.0-only

package decoder

import (
	"encoding/binary"
	"net/netip"

	"akvorado/common/helpers"
	"akvorado/common/schema"
)

// ParseIPv4 parses an IPv4 packet and returns layer-3 length.
func ParseIPv4(sch *schema.Component, bf *schema.FlowMessage, data []byte) uint64 {
	var l3length uint64
	var proto uint8
	if len(data) < 20 {
		return 0
	}
	bf.AppendUint(schema.ColumnEType, helpers.ETypeIPv4)
	l3length = uint64(binary.BigEndian.Uint16(data[2:4]))
	bf.SrcAddr = DecodeIP(data[12:16])
	bf.DstAddr = DecodeIP(data[16:20])
	proto = data[9]
	fragoffset := binary.BigEndian.Uint16(data[6:8]) & 0x1fff
	if !sch.IsDisabled(schema.ColumnGroupL3L4) {
		bf.AppendUint(schema.ColumnIPTos, uint64(data[1]))
		bf.AppendUint(schema.ColumnIPTTL, uint64(data[8]))
		bf.AppendUint(schema.ColumnIPFragmentID,
			uint64(binary.BigEndian.Uint16(data[4:6])))
		bf.AppendUint(schema.ColumnIPFragmentOffset,
			uint64(fragoffset))
	}
	ihl := int((data[0] & 0xf) * 4)
	if len(data) >= ihl {
		data = data[ihl:]
	} else {
		data = data[:0]
	}
	bf.AppendUint(schema.ColumnProto, uint64(proto))
	if fragoffset == 0 {
		ParseL4(sch, bf, data, proto)
	}
	return l3length
}

// ParseIPv6 parses an IPv6 packet and returns layer-3 length.
func ParseIPv6(sch *schema.Component, bf *schema.FlowMessage, data []byte) uint64 {
	var l3length uint64
	var proto uint8
	if len(data) < 40 {
		return 0
	}
	l3length = uint64(binary.BigEndian.Uint16(data[4:6])) + 40
	bf.AppendUint(schema.ColumnEType, helpers.ETypeIPv6)
	bf.SrcAddr = DecodeIP(data[8:24])
	bf.DstAddr = DecodeIP(data[24:40])
	proto = data[6]
	bf.AppendUint(schema.ColumnProto, uint64(proto))
	if !sch.IsDisabled(schema.ColumnGroupL3L4) {
		bf.AppendUint(schema.ColumnIPTos,
			uint64(binary.BigEndian.Uint16(data[0:2])&0xff0>>4))
		bf.AppendUint(schema.ColumnIPTTL, uint64(data[7]))
		bf.AppendUint(schema.ColumnIPv6FlowLabel,
			uint64(binary.BigEndian.Uint32(data[0:4])&0xfffff))
		// TODO fragmentID/fragmentOffset are in a separate header
	}
	data = data[40:]
	bf.AppendUint(schema.ColumnProto, uint64(proto))
	ParseL4(sch, bf, data, proto)
	return l3length
}

// ParseL4 parses L4 layer.
func ParseL4(sch *schema.Component, bf *schema.FlowMessage, data []byte, proto uint8) {
	if proto == 6 || proto == 17 {
		// UDP or TCP
		if len(data) > 4 {
			bf.AppendUint(schema.ColumnSrcPort,
				uint64(binary.BigEndian.Uint16(data[0:2])))
			bf.AppendUint(schema.ColumnDstPort,
				uint64(binary.BigEndian.Uint16(data[2:4])))
		}
	}
	if !sch.IsDisabled(schema.ColumnGroupL3L4) {
		if proto == 6 {
			// TCP
			if len(data) > 13 {
				bf.AppendUint(schema.ColumnTCPFlags,
					uint64(data[13]))
			}
		} else if proto == 1 {
			// ICMPv4
			if len(data) > 2 {
				bf.AppendUint(schema.ColumnICMPv4Type,
					uint64(data[0]))
				bf.AppendUint(schema.ColumnICMPv4Code,
					uint64(data[1]))
			}
		} else if proto == 58 {
			// ICMPv6
			if len(data) > 2 {
				bf.AppendUint(schema.ColumnICMPv6Type,
					uint64(data[0]))
				bf.AppendUint(schema.ColumnICMPv6Code,
					uint64(data[1]))
			}
		}
	}
}

// ParseEthernet parses an Ethernet packet and returns L3 length.
func ParseEthernet(sch *schema.Component, bf *schema.FlowMessage, data []byte) uint64 {
	if len(data) < 14 {
		return 0
	}
	if !sch.IsDisabled(schema.ColumnGroupL2) {
		bf.AppendUint(schema.ColumnDstMAC,
			binary.BigEndian.Uint64([]byte{0, 0, data[0], data[1], data[2], data[3], data[4], data[5]}))
		bf.AppendUint(schema.ColumnSrcMAC,
			binary.BigEndian.Uint64([]byte{0, 0, data[6], data[7], data[8], data[9], data[10], data[11]}))
	}
	etherType := data[12:14]
	data = data[14:]
	var vlan uint16
	for etherType[0] == 0x81 && etherType[1] == 0x00 {
		// 802.1q
		if len(data) < 4 {
			return 0
		}
		if !sch.IsDisabled(schema.ColumnGroupL2) {
			vlan = (uint16(data[0]&0xf) << 8) + uint16(data[1])
		}
		etherType = data[2:4]
		data = data[4:]
	}
	if vlan != 0 && bf.SrcVlan == 0 {
		bf.SrcVlan = vlan
	}
	if etherType[0] == 0x88 && etherType[1] == 0x47 {
		// MPLS
		mplsLabels := make([]uint32, 0, 5)
		for {
			if len(data) < 5 {
				return 0
			}
			label := binary.BigEndian.Uint32(append([]byte{0}, data[:3]...)) >> 4
			bottom := data[2] & 1
			data = data[4:]
			mplsLabels = append(mplsLabels, label)
			if bottom == 1 || label <= 15 {
				switch data[0] & 0xf0 >> 4 {
				case 4:
					etherType = []byte{0x8, 0x0}
				case 6:
					etherType = []byte{0x86, 0xdd}
				default:
					return 0
				}
				break
			}
		}
		if len(mplsLabels) > 0 {
			bf.AppendArrayUInt32(schema.ColumnMPLSLabels, mplsLabels)
		}
	}
	if etherType[0] == 0x8 && etherType[1] == 0x0 {
		return ParseIPv4(sch, bf, data)
	} else if etherType[0] == 0x86 && etherType[1] == 0xdd {
		return ParseIPv6(sch, bf, data)
	}
	return 0
}

// DecodeIP decodes an IP address
func DecodeIP(b []byte) netip.Addr {
	if ip, ok := netip.AddrFromSlice(b); ok {
		return netip.AddrFrom16(ip.As16())
	}
	return netip.Addr{}
}
