// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package quic

import (
	"bytes"
	"encoding/binary"
	"encoding/hex"
	"reflect"
	"strings"
	"testing"
)

func TestPacketHeader(t *testing.T) {
	for _, test := range []struct {
		name         string
		packet       []byte
		isLongHeader bool
		packetType   packetType
		dstConnID    []byte
	}{{
		// Initial packet from https://www.rfc-editor.org/rfc/rfc9001#section-a.1
		// (truncated)
		name: "rfc9001_a1",
		packet: unhex(`
			c000000001088394c8f03e5157080000 449e7b9aec34d1b1c98dd7689fb8ec11
		`),
		isLongHeader: true,
		packetType:   packetTypeInitial,
		dstConnID:    unhex(`8394c8f03e515708`),
	}, {
		// Initial packet from https://www.rfc-editor.org/rfc/rfc9001#section-a.3
		// (truncated)
		name: "rfc9001_a3",
		packet: unhex(`
			cf000000010008f067a5502a4262b500 4075c0d95a482cd0991cd25b0aac406a
		`),
		isLongHeader: true,
		packetType:   packetTypeInitial,
		dstConnID:    []byte{},
	}, {
		// Retry packet from https://www.rfc-editor.org/rfc/rfc9001#section-a.4
		name: "rfc9001_a4",
		packet: unhex(`
			ff000000010008f067a5502a4262b574 6f6b656e04a265ba2eff4d829058fb3f
			0f2496ba
		`),
		isLongHeader: true,
		packetType:   packetTypeRetry,
		dstConnID:    []byte{},
	}, {
		// Short header packet from https://www.rfc-editor.org/rfc/rfc9001#section-a.5
		name: "rfc9001_a5",
		packet: unhex(`
			4cfe4189655e5cd55c41f69080575d7999c25a5bfb
		`),
		isLongHeader: false,
		packetType:   packetType1RTT,
		dstConnID:    unhex(`fe4189655e5cd55c`),
	}, {
		// Version Negotiation packet.
		name: "version_negotiation",
		packet: unhex(`
			80 00000000 01ff0001020304
		`),
		isLongHeader: true,
		packetType:   packetTypeVersionNegotiation,
		dstConnID:    []byte{0xff},
	}, {
		// Too-short packet.
		name: "truncated_after_connid_length",
		packet: unhex(`
			cf0000000105
		`),
		isLongHeader: true,
		packetType:   packetTypeInitial,
		dstConnID:    nil,
	}, {
		// Too-short packet.
		name: "truncated_after_version",
		packet: unhex(`
			cf00000001
		`),
		isLongHeader: true,
		packetType:   packetTypeInitial,
		dstConnID:    nil,
	}, {
		// Much too short packet.
		name: "truncated_in_version",
		packet: unhex(`
			cf000000
		`),
		isLongHeader: true,
		packetType:   packetTypeInvalid,
		dstConnID:    nil,
	}} {
		t.Run(test.name, func(t *testing.T) {
			if got, want := isLongHeader(test.packet[0]), test.isLongHeader; got != want {
				t.Errorf("packet %x:\nisLongHeader(packet) = %v, want %v", test.packet, got, want)
			}
			if got, want := getPacketType(test.packet), test.packetType; got != want {
				t.Errorf("packet %x:\ngetPacketType(packet) = %v, want %v", test.packet, got, want)
			}
			gotConnID, gotOK := dstConnIDForDatagram(test.packet)
			wantConnID, wantOK := test.dstConnID, test.dstConnID != nil
			if !bytes.Equal(gotConnID, wantConnID) || gotOK != wantOK {
				t.Errorf("packet %x:\ndstConnIDForDatagram(packet) = {%x}, %v; want {%x}, %v", test.packet, gotConnID, gotOK, wantConnID, wantOK)
			}
		})
	}
}

func TestEncodeDecodeVersionNegotiation(t *testing.T) {
	dstConnID := []byte("this is a very long destination connection id")
	srcConnID := []byte("this is a very long source connection id")
	versions := []uint32{1, 0xffffffff}
	got := appendVersionNegotiation([]byte{}, dstConnID, srcConnID, versions...)
	want := bytes.Join([][]byte{{
		0b1100_0000, // header byte
		0, 0, 0, 0,  // Version
		byte(len(dstConnID)),
	}, dstConnID, {
		byte(len(srcConnID)),
	}, srcConnID, {
		0x00, 0x00, 0x00, 0x01,
		0xff, 0xff, 0xff, 0xff,
	}}, nil)
	if !bytes.Equal(got, want) {
		t.Fatalf("appendVersionNegotiation(nil, %x, %x, %v):\ngot  %x\nwant %x",
			dstConnID, srcConnID, versions, got, want)
	}
	gotDst, gotSrc, gotVersionBytes := parseVersionNegotiation(got)
	if got, want := gotDst, dstConnID; !bytes.Equal(got, want) {
		t.Errorf("parseVersionNegotiation: got dstConnID = %x, want %x", got, want)
	}
	if got, want := gotSrc, srcConnID; !bytes.Equal(got, want) {
		t.Errorf("parseVersionNegotiation: got srcConnID = %x, want %x", got, want)
	}
	var gotVersions []uint32
	for len(gotVersionBytes) >= 4 {
		gotVersions = append(gotVersions, binary.BigEndian.Uint32(gotVersionBytes))
		gotVersionBytes = gotVersionBytes[4:]
	}
	if got, want := gotVersions, versions; !reflect.DeepEqual(got, want) {
		t.Errorf("parseVersionNegotiation: got versions = %v, want %v", got, want)
	}
}

func TestParseGenericLongHeaderPacket(t *testing.T) {
	for _, test := range []struct {
		name      string
		packet    []byte
		version   uint32
		dstConnID []byte
		srcConnID []byte
		data      []byte
	}{{
		name: "long header packet",
		packet: unhex(`
			80 01020304 04a1a2a3a4 05b1b2b3b4b5 c1
		`),
		version:   0x01020304,
		dstConnID: unhex(`a1a2a3a4`),
		srcConnID: unhex(`b1b2b3b4b5`),
		data:      unhex(`c1`),
	}, {
		name: "zero everything",
		packet: unhex(`
			80 00000000 00 00
		`),
		version:   0,
		dstConnID: []byte{},
		srcConnID: []byte{},
		data:      []byte{},
	}} {
		t.Run(test.name, func(t *testing.T) {
			p, ok := parseGenericLongHeaderPacket(test.packet)
			if !ok {
				t.Fatalf("parseGenericLongHeaderPacket() = _, false; want true")
			}
			if got, want := p.version, test.version; got != want {
				t.Errorf("version = %v, want %v", got, want)
			}
			if got, want := p.dstConnID, test.dstConnID; !bytes.Equal(got, want) {
				t.Errorf("Destination Connection ID = {%x}, want {%x}", got, want)
			}
			if got, want := p.srcConnID, test.srcConnID; !bytes.Equal(got, want) {
				t.Errorf("Source Connection ID = {%x}, want {%x}", got, want)
			}
			if got, want := p.data, test.data; !bytes.Equal(got, want) {
				t.Errorf("Data = {%x}, want {%x}", got, want)
			}
		})
	}
}

func TestParseGenericLongHeaderPacketErrors(t *testing.T) {
	for _, test := range []struct {
		name   string
		packet []byte
	}{{
		name: "short header packet",
		packet: unhex(`
			00 01020304 04a1a2a3a4 05b1b2b3b4b5 c1
		`),
	}, {
		name: "packet too short",
		packet: unhex(`
			80 000000
		`),
	}, {
		name: "destination id too long",
		packet: unhex(`
			80 00000000 02 00
		`),
	}, {
		name: "source id too long",
		packet: unhex(`
			80 00000000 00 01
		`),
	}} {
		t.Run(test.name, func(t *testing.T) {
			_, ok := parseGenericLongHeaderPacket(test.packet)
			if ok {
				t.Fatalf("parseGenericLongHeaderPacket() = _, true; want false")
			}
		})
	}
}

func unhex(s string) []byte {
	b, err := hex.DecodeString(strings.Map(func(c rune) rune {
		switch c {
		case ' ', '\t', '\n':
			return -1
		}
		return c
	}, s))
	if err != nil {
		panic(err)
	}
	return b
}
