package xt

import (
	"net"
	"reflect"
	"testing"

	"golang.org/x/sys/unix"
)

func TestMatchConntrack(t *testing.T) {
	t.Parallel()
	tests := []struct {
		name  string
		fam   byte
		rev   uint32
		info  InfoAny
		empty InfoAny
	}{
		{
			name: "un/marshal ConntrackMtinfo1 IPv4 round-trip",
			fam:  unix.NFPROTO_IPV4,
			rev:  0,
			info: &ConntrackMtinfo1{
				ConntrackMtinfoBase: ConntrackMtinfoBase{
					OrigSrcAddr: net.ParseIP("1.2.3.4").To4(),
					OrigSrcMask: net.IPv4Mask(0x12, 0x23, 0x34, 0x45), // only for test ;)
					OrigDstAddr: net.ParseIP("2.3.4.5").To4(),
					OrigDstMask: net.IPv4Mask(0x23, 0x34, 0x45, 0x56), // only for test ;)
					ReplSrcAddr: net.ParseIP("10.20.30.40").To4(),
					ReplSrcMask: net.IPv4Mask(0xf2, 0xe3, 0xd4, 0xc5), // only for test ;)
					ReplDstAddr: net.ParseIP("2.3.4.5").To4(),
					ReplDstMask: net.IPv4Mask(0xe3, 0xd4, 0xc5, 0xb6), // only for test ;)
					ExpiresMin:  0x1234,
					ExpiresMax:  0x2345,
					L4Proto:     0xaa55,
					OrigSrcPort: 123,
					OrigDstPort: 321,
					ReplSrcPort: 789,
					ReplDstPort: 987,
					MatchFlags:  0x01,
					InvertFlags: 0x01,
				},
				StateMask:  0x55,
				StatusMask: 0xaa,
			},
			empty: &ConntrackMtinfo1{},
		},
		{
			name: "un/marshal ConntrackMtinfo1 IPv6 round-trip",
			fam:  unix.NFPROTO_IPV6,
			rev:  0,
			info: &ConntrackMtinfo1{
				ConntrackMtinfoBase: ConntrackMtinfoBase{
					OrigSrcAddr: net.ParseIP("fe80::dead:f001"),
					OrigSrcMask: net.IPMask{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, // only for test ;)
					OrigDstAddr: net.ParseIP("fd00::dead:f001"),
					OrigDstMask: net.IPMask{0x11, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, // only for test ;)
					ReplSrcAddr: net.ParseIP("fe80::c01d:cafe"),
					ReplSrcMask: net.IPMask{0x21, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, // only for test ;)
					ReplDstAddr: net.ParseIP("fd00::c01d:cafe"),
					ReplDstMask: net.IPMask{0x31, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, // only for test ;)
					ExpiresMin:  0x1234,
					ExpiresMax:  0x2345,
					L4Proto:     0xaa55,
					OrigSrcPort: 123,
					OrigDstPort: 321,
					ReplSrcPort: 789,
					ReplDstPort: 987,
					MatchFlags:  0x01,
					InvertFlags: 0x01,
				},
				StateMask:  0x55,
				StatusMask: 0xaa,
			},
			empty: &ConntrackMtinfo1{},
		},
		{
			name: "un/marshal ConntrackMtinfo2 IPv4 round-trip",
			fam:  unix.NFPROTO_IPV4,
			rev:  0,
			info: &ConntrackMtinfo2{
				ConntrackMtinfoBase: ConntrackMtinfoBase{
					OrigSrcAddr: net.ParseIP("1.2.3.4").To4(),
					OrigSrcMask: net.IPv4Mask(0x12, 0x23, 0x34, 0x45), // only for test ;)
					OrigDstAddr: net.ParseIP("2.3.4.5").To4(),
					OrigDstMask: net.IPv4Mask(0x23, 0x34, 0x45, 0x56), // only for test ;)
					ReplSrcAddr: net.ParseIP("10.20.30.40").To4(),
					ReplSrcMask: net.IPv4Mask(0xf2, 0xe3, 0xd4, 0xc5), // only for test ;)
					ReplDstAddr: net.ParseIP("2.3.4.5").To4(),
					ReplDstMask: net.IPv4Mask(0xe3, 0xd4, 0xc5, 0xb6), // only for test ;)
					ExpiresMin:  0x1234,
					ExpiresMax:  0x2345,
					L4Proto:     0xaa55,
					OrigSrcPort: 123,
					OrigDstPort: 321,
					ReplSrcPort: 789,
					ReplDstPort: 987,
					MatchFlags:  0x01,
					InvertFlags: 0x01,
				},
				StateMask:  0x55aa,
				StatusMask: 0xaa55,
			},
			empty: &ConntrackMtinfo2{},
		},
		{
			name: "un/marshal ConntrackMtinfo1 IPv6 round-trip",
			fam:  unix.NFPROTO_IPV6,
			rev:  0,
			info: &ConntrackMtinfo2{
				ConntrackMtinfoBase: ConntrackMtinfoBase{
					OrigSrcAddr: net.ParseIP("fe80::dead:f001"),
					OrigSrcMask: net.IPMask{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, // only for test ;)
					OrigDstAddr: net.ParseIP("fd00::dead:f001"),
					OrigDstMask: net.IPMask{0x11, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, // only for test ;)
					ReplSrcAddr: net.ParseIP("fe80::c01d:cafe"),
					ReplSrcMask: net.IPMask{0x21, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, // only for test ;)
					ReplDstAddr: net.ParseIP("fd00::c01d:cafe"),
					ReplDstMask: net.IPMask{0x31, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, // only for test ;)
					ExpiresMin:  0x1234,
					ExpiresMax:  0x2345,
					L4Proto:     0xaa55,
					OrigSrcPort: 123,
					OrigDstPort: 321,
					ReplSrcPort: 789,
					ReplDstPort: 987,
					MatchFlags:  0x01,
					InvertFlags: 0x01,
				},
				StateMask:  0x55aa,
				StatusMask: 0xaa55,
			},
			empty: &ConntrackMtinfo2{},
		},
		{
			name: "un/marshal ConntrackMtinfo3 IPv4 round-trip",
			fam:  unix.NFPROTO_IPV4,
			rev:  0,
			info: &ConntrackMtinfo3{
				ConntrackMtinfo2: ConntrackMtinfo2{
					ConntrackMtinfoBase: ConntrackMtinfoBase{
						OrigSrcAddr: net.ParseIP("1.2.3.4").To4(),
						OrigSrcMask: net.IPv4Mask(0x12, 0x23, 0x34, 0x45), // only for test ;)
						OrigDstAddr: net.ParseIP("2.3.4.5").To4(),
						OrigDstMask: net.IPv4Mask(0x23, 0x34, 0x45, 0x56), // only for test ;)
						ReplSrcAddr: net.ParseIP("10.20.30.40").To4(),
						ReplSrcMask: net.IPv4Mask(0xf2, 0xe3, 0xd4, 0xc5), // only for test ;)
						ReplDstAddr: net.ParseIP("2.3.4.5").To4(),
						ReplDstMask: net.IPv4Mask(0xe3, 0xd4, 0xc5, 0xb6), // only for test ;)
						ExpiresMin:  0x1234,
						ExpiresMax:  0x2345,
						L4Proto:     0xaa55,
						OrigSrcPort: 123,
						OrigDstPort: 321,
						ReplSrcPort: 789,
						ReplDstPort: 987,
						MatchFlags:  0x01,
						InvertFlags: 0x01,
					},
					StateMask:  0x55aa,
					StatusMask: 0xaa55,
				},
				OrigSrcPortHigh: 0xabcd,
				OrigDstPortHigh: 0xcdba,
				ReplSrcPortHigh: 0x1234,
				ReplDstPortHigh: 0x4321,
			},
			empty: &ConntrackMtinfo3{},
		},
		{
			name: "un/marshal ConntrackMtinfo1 IPv6 round-trip",
			fam:  unix.NFPROTO_IPV6,
			rev:  0,
			info: &ConntrackMtinfo3{
				ConntrackMtinfo2: ConntrackMtinfo2{
					ConntrackMtinfoBase: ConntrackMtinfoBase{
						OrigSrcAddr: net.ParseIP("fe80::dead:f001"),
						OrigSrcMask: net.IPMask{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, // only for test ;)
						OrigDstAddr: net.ParseIP("fd00::dead:f001"),
						OrigDstMask: net.IPMask{0x11, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, // only for test ;)
						ReplSrcAddr: net.ParseIP("fe80::c01d:cafe"),
						ReplSrcMask: net.IPMask{0x21, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, // only for test ;)
						ReplDstAddr: net.ParseIP("fd00::c01d:cafe"),
						ReplDstMask: net.IPMask{0x31, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, // only for test ;)
						ExpiresMin:  0x1234,
						ExpiresMax:  0x2345,
						L4Proto:     0xaa55,
						OrigSrcPort: 123,
						OrigDstPort: 321,
						ReplSrcPort: 789,
						ReplDstPort: 987,
						MatchFlags:  0x01,
						InvertFlags: 0x01,
					},
					StateMask:  0x55aa,
					StatusMask: 0xaa55,
				},
				OrigSrcPortHigh: 0xabcd,
				OrigDstPortHigh: 0xcdba,
				ReplSrcPortHigh: 0x1234,
				ReplDstPortHigh: 0x4321,
			},
			empty: &ConntrackMtinfo3{},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			data, err := tt.info.marshal(TableFamily(tt.fam), tt.rev)
			if err != nil {
				t.Fatalf("marshal error: %+v", err)
			}
			var recoveredInfo InfoAny = tt.empty
			err = recoveredInfo.unmarshal(TableFamily(tt.fam), tt.rev, data)
			if err != nil {
				t.Fatalf("unmarshal error: %+v", err)
			}
			if !reflect.DeepEqual(tt.info, recoveredInfo) {
				t.Fatalf("original %+v and recovered %+v are different", tt.info, recoveredInfo)
			}
		})
	}
}
