package dns

import (
	"bytes"
	"net"
	"testing"
)

// TestPacketDataNsec tests generated using fuzz.go and with a message pack
// containing the following bytes: 0000\x00\x00000000\x00\x002000000\x0060000\x00\x130000000000000000000"
// That bytes sequence created the overflow error and further permutations of that sequence were able to trigger
// the other code paths.
func TestPackDataNsec(t *testing.T) {
	type args struct {
		bitmap []uint16
		msg    []byte
		off    int
	}
	tests := []struct {
		name       string
		args       args
		wantOff    int
		wantBytes  []byte
		wantErr    bool
		wantErrMsg string
	}{
		{
			name: "overflow",
			args: args{
				bitmap: []uint16{
					8962, 8963, 8970, 8971, 8978, 8979,
					8986, 8987, 8994, 8995, 9002, 9003,
					9010, 9011, 9018, 9019, 9026, 9027,
					9034, 9035, 9042, 9043, 9050, 9051,
					9058, 9059, 9066,
				},
				msg: []byte{
					48, 48, 48, 48, 0, 0, 0,
					1, 0, 0, 0, 0, 0, 0, 50,
					48, 48, 48, 48, 48, 48,
					0, 54, 48, 48, 48, 48,
					0, 19, 48, 48,
				},
				off: 48,
			},
			wantErr:    true,
			wantErrMsg: "dns: overflow packing nsec",
			wantOff:    48,
		},
		{
			name: "disordered nsec bits",
			args: args{
				bitmap: []uint16{
					8962,
					1,
				},
				msg: []byte{
					48, 48, 48, 48, 0, 0, 0, 1, 0, 0, 0, 0,
					0, 0, 50, 48, 48, 48, 48, 48, 48, 0, 54, 48,
					48, 48, 48, 0, 19, 48, 48, 48, 48, 48, 48, 0,
					0, 0, 1, 0, 0, 0, 0, 0, 0, 50, 48, 48,
					48, 48, 48, 48, 0, 54, 48, 48, 48, 48, 0, 19,
					48, 48, 48, 48, 48, 48, 0, 0, 0, 1, 0, 0,
					0, 0, 0, 0, 50, 48, 48, 48, 48, 48, 48, 0,
					54, 48, 48, 48, 48, 0, 19, 48, 48, 48, 48, 48,
					48, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 50,
					48, 48, 48, 48, 48, 48, 0, 54, 48, 48, 48, 48,
					0, 19, 48, 48, 48, 48, 48, 48, 0, 0, 0, 1,
					0, 0, 0, 0, 0, 0, 50, 48, 48, 48, 48, 48,
					48, 0, 54, 48, 48, 48, 48, 0, 19, 48, 48,
				},
				off: 0,
			},
			wantErr:    true,
			wantErrMsg: "dns: nsec bits out of order",
			wantOff:    155,
		},
		{
			name: "simple message with only one window",
			args: args{
				bitmap: []uint16{
					1,
				},
				msg: []byte{
					48, 48, 48, 48, 0, 0,
					0, 1, 0, 0, 0, 0,
					0, 0, 50, 48, 48, 48,
					48, 48, 48, 0, 54, 48,
					48, 48, 48, 0, 19, 48, 48,
				},
				off: 0,
			},
			wantErr:   false,
			wantOff:   3,
			wantBytes: []byte{0, 1, 64},
		},
		{
			name: "multiple types",
			args: args{
				bitmap: []uint16{
					TypeNS, TypeSOA, TypeRRSIG, TypeDNSKEY, TypeNSEC3PARAM,
				},
				msg: []byte{
					48, 48, 48, 48, 0, 0,
					0, 1, 0, 0, 0, 0,
					0, 0, 50, 48, 48, 48,
					48, 48, 48, 0, 54, 48,
					48, 48, 48, 0, 19, 48, 48,
				},
				off: 0,
			},
			wantErr:   false,
			wantOff:   9,
			wantBytes: []byte{0, 7, 34, 0, 0, 0, 0, 2, 144},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			gotOff, err := packDataNsec(tt.args.bitmap, tt.args.msg, tt.args.off)
			if (err != nil) != tt.wantErr {
				t.Errorf("packDataNsec() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if err != nil && tt.wantErrMsg != err.Error() {
				t.Errorf("packDataNsec() error msg = %v, wantErrMsg %v", err.Error(), tt.wantErrMsg)
				return
			}
			if gotOff != tt.wantOff {
				t.Errorf("packDataNsec() = %v, want off %v", gotOff, tt.wantOff)
			}
			if err == nil && tt.args.off < len(tt.args.msg) && gotOff < len(tt.args.msg) {
				if want, got := tt.wantBytes, tt.args.msg[tt.args.off:gotOff]; !bytes.Equal(got, want) {
					t.Errorf("packDataNsec() = %v, want bytes %v", got, want)
				}
			}
		})
	}
}

func TestPackDataNsecDirtyBuffer(t *testing.T) {
	zeroBuf := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0}
	dirtyBuf := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}
	off1, _ := packDataNsec([]uint16{TypeNS, TypeSOA, TypeRRSIG}, zeroBuf, 0)
	off2, _ := packDataNsec([]uint16{TypeNS, TypeSOA, TypeRRSIG}, dirtyBuf, 0)
	if off1 != off2 {
		t.Errorf("off1 %v != off2 %v", off1, off2)
	}
	if !bytes.Equal(zeroBuf[:off1], dirtyBuf[:off2]) {
		t.Errorf("dirty buffer differs from zero buffer: %v, %v", zeroBuf[:off1], dirtyBuf[:off2])
	}
}

func BenchmarkPackDataNsec(b *testing.B) {
	benches := []struct {
		name  string
		types []uint16
	}{
		{"empty", nil},
		{"typical", []uint16{TypeNS, TypeSOA, TypeRRSIG, TypeDNSKEY, TypeNSEC3PARAM}},
		{"multiple_windows", []uint16{1, 300, 350, 10000, 20000}},
	}
	for _, bb := range benches {
		b.Run(bb.name, func(b *testing.B) {
			buf := make([]byte, 100)
			for n := 0; n < b.N; n++ {
				packDataNsec(bb.types, buf, 0)
			}
		})
	}
}
func TestUnpackString(t *testing.T) {
	msg := []byte("\x00abcdef\x0f\\\"ghi\x04mmm\x7f")
	msg[0] = byte(len(msg) - 1)

	got, _, err := unpackString(msg, 0)
	if err != nil {
		t.Fatal(err)
	}

	if want := `abcdef\015\\\"ghi\004mmm\127`; want != got {
		t.Errorf("expected %q, got %q", want, got)
	}
}

func BenchmarkUnpackString(b *testing.B) {
	b.Run("Escaped", func(b *testing.B) {
		msg := []byte("\x00abcdef\x0f\\\"ghi\x04mmm")
		msg[0] = byte(len(msg) - 1)

		for n := 0; n < b.N; n++ {
			got, _, err := unpackString(msg, 0)
			if err != nil {
				b.Fatal(err)
			}

			if want := `abcdef\015\\\"ghi\004mmm`; want != got {
				b.Errorf("expected %q, got %q", want, got)
			}
		}
	})
	b.Run("Unescaped", func(b *testing.B) {
		msg := []byte("\x00large.example.com")
		msg[0] = byte(len(msg) - 1)

		for n := 0; n < b.N; n++ {
			got, _, err := unpackString(msg, 0)
			if err != nil {
				b.Fatal(err)
			}

			if want := "large.example.com"; want != got {
				b.Errorf("expected %q, got %q", want, got)
			}
		}
	})
}

func TestPackDataAplPrefix(t *testing.T) {
	tests := []struct {
		name     string
		negation bool
		ip       net.IP
		mask     net.IPMask
		expect   []byte
	}{
		{
			"1:192.0.2.0/24",
			false,
			net.ParseIP("192.0.2.0").To4(),
			net.CIDRMask(24, 32),
			[]byte{0x00, 0x01, 0x18, 0x03, 192, 0, 2},
		},
		{
			"2:2001:db8:cafe::0/48",
			false,
			net.ParseIP("2001:db8:cafe::"),
			net.CIDRMask(48, 128),
			[]byte{0x00, 0x02, 0x30, 0x06, 0x20, 0x01, 0x0d, 0xb8, 0xca, 0xfe},
		},
		{
			"with trailing zero bytes 2:2001:db8:cafe::0/64",
			false,
			net.ParseIP("2001:db8:cafe::"),
			net.CIDRMask(64, 128),
			[]byte{0x00, 0x02, 0x40, 0x06, 0x20, 0x01, 0x0d, 0xb8, 0xca, 0xfe},
		},
		{
			"no non-zero bytes 2::/16",
			false,
			net.ParseIP("::"),
			net.CIDRMask(16, 128),
			[]byte{0x00, 0x02, 0x10, 0x00},
		},
		{
			"!2:2001:db8::/32",
			true,
			net.ParseIP("2001:db8::"),
			net.CIDRMask(32, 128),
			[]byte{0x00, 0x02, 0x20, 0x84, 0x20, 0x01, 0x0d, 0xb8},
		},
		{
			"normalize 1:198.51.103.255/22",
			false,
			net.ParseIP("198.51.103.255").To4(),
			net.CIDRMask(22, 32),
			[]byte{0x00, 0x01, 0x16, 0x03, 198, 51, 100}, // 1:198.51.100.0/22
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ap := &APLPrefix{
				Negation: tt.negation,
				Network:  net.IPNet{IP: tt.ip, Mask: tt.mask},
			}
			out := make([]byte, 16)
			off, err := packDataAplPrefix(ap, out, 0)
			if err != nil {
				t.Fatalf("expected no error, got %q", err)
			}
			if !bytes.Equal(tt.expect, out[:off]) {
				t.Fatalf("expected output %02x, got %02x", tt.expect, out[:off])
			}
			// Make sure the packed bytes would be accepted by its own unpack
			_, _, err = unpackDataAplPrefix(out, 0)
			if err != nil {
				t.Fatalf("expected no error, got %q", err)
			}
		})
	}
}

func TestPackDataAplPrefix_Failures(t *testing.T) {
	tests := []struct {
		name string
		ip   net.IP
		mask net.IPMask
	}{
		{
			"family mismatch",
			net.ParseIP("2001:db8::"),
			net.CIDRMask(24, 32),
		},
		{
			"unrecognized family",
			[]byte{0x42},
			[]byte{0xff},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ap := &APLPrefix{Network: net.IPNet{IP: tt.ip, Mask: tt.mask}}
			msg := make([]byte, 16)
			off, err := packDataAplPrefix(ap, msg, 0)
			if err == nil {
				t.Fatal("expected error, got none")
			}
			if off != len(msg) {
				t.Fatalf("expected %d, got %d", len(msg), off)
			}
		})
	}
}

func TestPackDataAplPrefix_BufferBounds(t *testing.T) {
	ap := &APLPrefix{
		Negation: false,
		Network: net.IPNet{
			IP:   net.ParseIP("2001:db8::"),
			Mask: net.CIDRMask(32, 128),
		},
	}
	wire := []byte{0x00, 0x02, 0x20, 0x04, 0x20, 0x01, 0x0d, 0xb8}

	t.Run("small", func(t *testing.T) {
		msg := make([]byte, len(wire))
		_, err := packDataAplPrefix(ap, msg, 1) // offset
		if err == nil {
			t.Fatal("expected error, got none")
		}
	})

	t.Run("exact fit", func(t *testing.T) {
		msg := make([]byte, len(wire))
		off, err := packDataAplPrefix(ap, msg, 0)
		if err != nil {
			t.Fatalf("expected no error, got %q", err)
		}
		if !bytes.Equal(wire, msg[:off]) {
			t.Fatalf("expected %02x, got %02x", wire, msg[:off])
		}
	})
}

func TestPackDataApl(t *testing.T) {
	in := []APLPrefix{
		{
			Negation: true,
			Network: net.IPNet{
				IP:   net.ParseIP("198.51.0.0").To4(),
				Mask: net.CIDRMask(16, 32),
			},
		},
		{
			Negation: false,
			Network: net.IPNet{
				IP:   net.ParseIP("2001:db8:beef::"),
				Mask: net.CIDRMask(48, 128),
			},
		},
	}
	expect := []byte{
		// 1:192.51.0.0/16
		0x00, 0x01, 0x10, 0x82, 0xc6, 0x33,
		// 2:2001:db8:beef::0/48
		0x00, 0x02, 0x30, 0x06, 0x20, 0x01, 0x0d, 0xb8, 0xbe, 0xef,
	}

	msg := make([]byte, 32)
	off, err := packDataApl(in, msg, 0)
	if err != nil {
		t.Fatalf("expected no error, got %q", err)
	}
	if !bytes.Equal(expect, msg[:off]) {
		t.Fatalf("expected %02x, got %02x", expect, msg[:off])
	}
}

func TestUnpackDataAplPrefix(t *testing.T) {
	tests := []struct {
		name     string
		wire     []byte
		negation bool
		ip       net.IP
		mask     net.IPMask
	}{
		{
			"1:192.0.2.0/24",
			[]byte{0x00, 0x01, 0x18, 0x03, 192, 0, 2},
			false,
			net.ParseIP("192.0.2.0").To4(),
			net.CIDRMask(24, 32),
		},
		{
			"2:2001:db8::/32",
			[]byte{0x00, 0x02, 0x20, 0x04, 0x20, 0x01, 0x0d, 0xb8},
			false,
			net.ParseIP("2001:db8::"),
			net.CIDRMask(32, 128),
		},
		{
			"!2:2001:db8:8000::/33",
			[]byte{0x00, 0x02, 0x21, 0x85, 0x20, 0x01, 0x0d, 0xb8, 0x80},
			true,
			net.ParseIP("2001:db8:8000::"),
			net.CIDRMask(33, 128),
		},
		{
			"1:0.0.0.0/0",
			[]byte{0x00, 0x01, 0x00, 0x00},
			false,
			net.ParseIP("0.0.0.0").To4(),
			net.CIDRMask(0, 32),
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, off, err := unpackDataAplPrefix(tt.wire, 0)
			if err != nil {
				t.Fatalf("expected no error, got %q", err)
			}
			if off != len(tt.wire) {
				t.Fatalf("expected offset %d, got %d", len(tt.wire), off)
			}
			if got.Negation != tt.negation {
				t.Errorf("expected negation %v, got %v", tt.negation, got.Negation)
			}
			if !bytes.Equal(got.Network.IP, tt.ip) {
				t.Errorf("expected IP %02x, got %02x", tt.ip, got.Network.IP)
			}
			if !bytes.Equal(got.Network.Mask, tt.mask) {
				t.Errorf("expected mask %02x, got %02x", tt.mask, got.Network.Mask)
			}
		})
	}
}

func TestUnpackDataAplPrefix_Errors(t *testing.T) {
	tests := []struct {
		name string
		wire []byte
		want string
	}{
		{
			"incomplete header",
			[]byte{0x00, 0x01, 0x18},
			"dns: overflow unpacking APL prefix",
		},
		{
			"unrecognized family",
			[]byte{0x00, 0x03, 0x00, 0x00},
			"dns: unrecognized APL address family",
		},
		{
			"prefix too large for family IPv4",
			[]byte{0x00, 0x01, 0x21, 0x04, 192, 0, 2, 0},
			"dns: APL prefix too long",
		},
		{
			"prefix too large for family IPv6",
			[]byte{0x00, 0x02, 0x81, 0x85, 0x20, 0x01, 0x0d, 0xb8, 0x80},
			"dns: APL prefix too long",
		},
		{
			"afdlen too long for address family IPv4",
			[]byte{0x00, 0x01, 22, 0x05, 192, 0, 2, 0, 0},
			"dns: APL length too long",
		},
		{
			"overflow unpacking APL address",
			[]byte{0x00, 0x01, 0x10, 0x02, 192},
			"dns: overflow unpacking APL address",
		},
		{
			"address included trailing zeros",
			[]byte{0x00, 0x01, 0x10, 0x04, 192, 0, 2, 0},
			"dns: extra APL address bits",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, _, err := unpackDataAplPrefix(tt.wire, 0)
			if err == nil {
				t.Fatal("expected error, got none")
			}

			if err.Error() != tt.want {
				t.Errorf("expected %s, got %s", tt.want, err.Error())
			}
		})
	}
}

func TestUnpackDataApl(t *testing.T) {
	wire := []byte{
		// 2:2001:db8:cafe:4200:0/56
		0x00, 0x02, 0x38, 0x07, 0x20, 0x01, 0x0d, 0xb8, 0xca, 0xfe, 0x42,
		// 1:192.0.2.0/24
		0x00, 0x01, 0x18, 0x03, 192, 0, 2,
		// !1:192.0.2.128/25
		0x00, 0x01, 0x19, 0x84, 192, 0, 2, 128,
		// 1:10.0.0.0/24
		0x00, 0x01, 0x18, 0x01, 0x0a,
		// !1:10.0.0.1/32
		0x00, 0x01, 0x20, 0x84, 0x0a, 0, 0, 1,
		// !1:0.0.0.0/0
		0x00, 0x01, 0x00, 0x80,
		// 2::0/0
		0x00, 0x02, 0x00, 0x00,
	}
	expect := []APLPrefix{
		{
			Negation: false,
			Network: net.IPNet{
				IP:   net.ParseIP("2001:db8:cafe:4200::"),
				Mask: net.CIDRMask(56, 128),
			},
		},
		{
			Negation: false,
			Network: net.IPNet{
				IP:   net.ParseIP("192.0.2.0").To4(),
				Mask: net.CIDRMask(24, 32),
			},
		},
		{
			Negation: true,
			Network: net.IPNet{
				IP:   net.ParseIP("192.0.2.128").To4(),
				Mask: net.CIDRMask(25, 32),
			},
		},
		{
			Negation: false,
			Network: net.IPNet{
				IP:   net.ParseIP("10.0.0.0").To4(),
				Mask: net.CIDRMask(24, 32),
			},
		},
		{
			Negation: true,
			Network: net.IPNet{
				IP:   net.ParseIP("10.0.0.1").To4(),
				Mask: net.CIDRMask(32, 32),
			},
		},
		{
			Negation: true,
			Network: net.IPNet{
				IP:   net.ParseIP("0.0.0.0").To4(),
				Mask: net.CIDRMask(0, 32),
			},
		},
		{
			Negation: false,
			Network: net.IPNet{
				IP:   net.ParseIP("::").To16(),
				Mask: net.CIDRMask(0, 128),
			},
		},
	}

	got, off, err := unpackDataApl(wire, 0)
	if err != nil {
		t.Fatalf("expected no error, got %q", err)
	}
	if off != len(wire) {
		t.Fatalf("expected offset %d, got %d", len(wire), off)
	}
	if len(got) != len(expect) {
		t.Fatalf("expected %d prefixes, got %d", len(expect), len(got))
	}
	for i, exp := range expect {
		if got[i].Negation != exp.Negation {
			t.Errorf("[%d] expected negation %v, got %v", i, exp.Negation, got[i].Negation)
		}
		if !bytes.Equal(got[i].Network.IP, exp.Network.IP) {
			t.Errorf("[%d] expected IP %02x, got %02x", i, exp.Network.IP, got[i].Network.IP)
		}
		if !bytes.Equal(got[i].Network.Mask, exp.Network.Mask) {
			t.Errorf("[%d] expected mask %02x, got %02x", i, exp.Network.Mask, got[i].Network.Mask)
		}
	}
}
