package inet

// https://en.wikipedia.org/wiki/Transmission_Control_Protocol

import (
	"github.com/wader/fq/format"
	"github.com/wader/fq/pkg/decode"
	"github.com/wader/fq/pkg/interp"
	"github.com/wader/fq/pkg/scalar"
)

func init() {
	interp.RegisterFormat(decode.Format{
		Name:        format.TCP_SEGMENT,
		Description: "Transmission control protocol segment",
		Groups:      []string{format.IP_PACKET},
		DecodeFn:    decodeTCP,
	})
}

const (
	tcpOptionEnd           = 0
	tcpOptionNop           = 1
	tcpOptionMSS           = 2
	tcpOptionWinscale      = 3
	tcpOptionSackPermitted = 4
	tcpOptionSack          = 5
	tcpOptionTimestamp     = 8
)

var tcpOptionsMap = scalar.UintMap{
	tcpOptionEnd:           {Sym: "end", Description: "End of options list"},
	tcpOptionNop:           {Sym: "nop", Description: "No operation"},
	tcpOptionMSS:           {Sym: "mss", Description: "Maximum segment size"},
	tcpOptionWinscale:      {Sym: "winscale", Description: "Window scale"},
	tcpOptionSackPermitted: {Sym: "sack_permitted", Description: "Selective Acknowledgement permitted"},
	tcpOptionSack:          {Sym: "sack", Description: "Selective Acknowledgement"},
	tcpOptionTimestamp:     {Sym: "timestamp", Description: "Timestamp and echo of previous timestamp"},
}

func decodeTCP(d *decode.D, in any) any {
	if ipi, ok := in.(format.IPPacketIn); ok && ipi.Protocol != format.IPv4ProtocolTCP {
		d.Fatalf("incorrect protocol %d", ipi.Protocol)
	}

	d.FieldU16("source_port", format.TCPPortMap)
	d.FieldU16("destination_port", format.TCPPortMap)
	d.FieldU32("sequence_number")
	d.FieldU32("acknowledgment_number")
	dataOffset := d.FieldU4("data_offset")
	d.FieldU3("reserved")
	d.FieldBool("ns")
	d.FieldBool("cwr")
	d.FieldBool("ece")
	d.FieldBool("urg")
	d.FieldBool("ack")
	d.FieldBool("psh")
	d.FieldBool("rst")
	d.FieldBool("syn")
	d.FieldBool("fin")
	d.FieldU16("window_size")
	// checksumStart := d.Pos()
	d.FieldU16("checksum", scalar.UintHex)
	// checksumEnd := d.Pos()
	d.FieldU16("urgent_pointer")
	optionsLen := (int64(dataOffset) - 5) * 8 * 4
	if optionsLen > 0 {
		d.FramedFn(optionsLen, func(d *decode.D) {
			d.FieldArray("options", func(d *decode.D) {
				for !d.End() {
					d.FieldStruct("option", func(d *decode.D) {
						kind := d.FieldU8("kind", tcpOptionsMap)
						switch kind {
						case tcpOptionEnd, tcpOptionNop:
							// has no length or data
						default:
							l := d.FieldU8("length")
							switch kind {
							case tcpOptionMSS:
								d.FieldU16("size")
							case tcpOptionWinscale:
								d.FieldU8("shift")
							case tcpOptionSackPermitted:
								// none
							case tcpOptionSack:
								d.FramedFn((int64(l-2))*8, func(d *decode.D) {
									d.FieldArray("blocks", func(d *decode.D) {
										for !d.End() {
											d.FieldStruct("block", func(d *decode.D) {
												d.FieldU32("left_edge")
												d.FieldU32("right_edge")
											})
										}
									})
								})
							case tcpOptionTimestamp:
								d.FieldU32("value")
								d.FieldU32("echo_reply")
							default:
								d.FieldRawLen("data", (int64(l-2))*8)
							}
						}
					})
				}
			})
		})
	}

	// TODO: need to pass ipv4 pseudo header somehow
	// tcpChecksum := &checksum.IPv4{}
	// d.MustCopy(tcpChecksum, d.BitBufRange(0, checksumStart))
	// d.MustCopy(tcpChecksum, d.BitBufRange(checksumEnd, d.Len()-checksumEnd))
	// _ = d.FieldMustGet("checksum").TryScalarFn(d.UintValidateBytes(tcpChecksum.Sum(nil)), scalar.Hex)

	d.FieldRawLen("payload", d.BitsLeft())

	return nil
}
