quic: basic packet operations

The type of a QUIC packet can be identified by inspecting its first
byte, and the destination connection ID can be determined without
decrypting and parsing the entire packet.

For golang/go#58547

Change-Id: Ie298c0f6c0017343168a0974543e37ab7a569b0f
Reviewed-on: https://go-review.googlesource.com/c/net/+/475437
Run-TryBot: Damien Neil <dneil@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Matt Layher <mdlayher@gmail.com>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/internal/quic/packet.go b/internal/quic/packet.go
new file mode 100644
index 0000000..4645ae7
--- /dev/null
+++ b/internal/quic/packet.go
@@ -0,0 +1,159 @@
+// 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
+
+// packetType is a QUIC packet type.
+// https://www.rfc-editor.org/rfc/rfc9000.html#section-17
+type packetType byte
+
+const (
+	packetTypeInvalid = packetType(iota)
+	packetTypeInitial
+	packetType0RTT
+	packetTypeHandshake
+	packetTypeRetry
+	packetType1RTT
+	packetTypeVersionNegotiation
+)
+
+// Bits set in the first byte of a packet.
+const (
+	headerFormLong  = 0x80 // https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2-3.2.1
+	headerFormShort = 0x00 // https://www.rfc-editor.org/rfc/rfc9000.html#section-17.3.1-4.2.1
+	fixedBit        = 0x40 // https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2-3.4.1
+	reservedBits    = 0x0c // https://www.rfc-editor.org/rfc/rfc9000#section-17.2-8.2.1
+)
+
+// Long Packet Type bits.
+// https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2-3.6.1
+const (
+	longPacketTypeInitial   = 0 << 4
+	longPacketType0RTT      = 1 << 4
+	longPacketTypeHandshake = 2 << 4
+	longPacketTypeRetry     = 3 << 4
+)
+
+// Frame types.
+// https://www.rfc-editor.org/rfc/rfc9000.html#section-19
+const (
+	frameTypePadding                    = 0x00
+	frameTypePing                       = 0x01
+	frameTypeAck                        = 0x02
+	frameTypeAckECN                     = 0x03
+	frameTypeResetStream                = 0x04
+	frameTypeStopSending                = 0x05
+	frameTypeCrypto                     = 0x06
+	frameTypeNewToken                   = 0x07
+	frameTypeStreamBase                 = 0x08 // low three bits carry stream flags
+	frameTypeMaxData                    = 0x10
+	frameTypeMaxStreamData              = 0x11
+	frameTypeMaxStreamsBidi             = 0x12
+	frameTypeMaxStreamsUni              = 0x13
+	frameTypeDataBlocked                = 0x14
+	frameTypeStreamDataBlocked          = 0x15
+	frameTypeStreamsBlockedBidi         = 0x16
+	frameTypeStreamsBlockedUni          = 0x17
+	frameTypeNewConnectionID            = 0x18
+	frameTypeRetireConnectionID         = 0x19
+	frameTypePathChallenge              = 0x1a
+	frameTypePathResponse               = 0x1b
+	frameTypeConnectionCloseTransport   = 0x1c
+	frameTypeConnectionCloseApplication = 0x1d
+	frameTypeHandshakeDone              = 0x1e
+)
+
+// The low three bits of STREAM frames.
+// https://www.rfc-editor.org/rfc/rfc9000.html#section-19.8
+const (
+	streamOffBit = 0x04
+	streamLenBit = 0x02
+	streamFinBit = 0x01
+)
+
+// isLongHeader returns true if b is the first byte of a long header.
+func isLongHeader(b byte) bool {
+	return b&headerFormLong == headerFormLong
+}
+
+// getPacketType returns the type of a packet.
+func getPacketType(b []byte) packetType {
+	if len(b) == 0 {
+		return packetTypeInvalid
+	}
+	if !isLongHeader(b[0]) {
+		if b[0]&fixedBit != fixedBit {
+			return packetTypeInvalid
+		}
+		return packetType1RTT
+	}
+	if len(b) < 5 {
+		return packetTypeInvalid
+	}
+	if b[1] == 0 && b[2] == 0 && b[3] == 0 && b[4] == 0 {
+		// Version Negotiation packets don't necessarily set the fixed bit.
+		return packetTypeVersionNegotiation
+	}
+	if b[0]&fixedBit != fixedBit {
+		return packetTypeInvalid
+	}
+	switch b[0] & 0x30 {
+	case longPacketTypeInitial:
+		return packetTypeInitial
+	case longPacketType0RTT:
+		return packetType0RTT
+	case longPacketTypeHandshake:
+		return packetTypeHandshake
+	case longPacketTypeRetry:
+		return packetTypeRetry
+	}
+	return packetTypeInvalid
+}
+
+// dstConnIDForDatagram returns the destination connection ID field of the
+// first QUIC packet in a datagram.
+func dstConnIDForDatagram(pkt []byte) (id []byte, ok bool) {
+	if len(pkt) < 1 {
+		return nil, false
+	}
+	var n int
+	var b []byte
+	if isLongHeader(pkt[0]) {
+		if len(pkt) < 6 {
+			return nil, false
+		}
+		n = int(pkt[5])
+		b = pkt[6:]
+	} else {
+		n = connIDLen
+		b = pkt[1:]
+	}
+	if len(b) < n {
+		return nil, false
+	}
+	return b[:n], true
+}
+
+// A longPacket is a long header packet.
+type longPacket struct {
+	ptype        packetType
+	reservedBits uint8
+	version      uint32
+	num          packetNumber
+	dstConnID    []byte
+	srcConnID    []byte
+	payload      []byte
+
+	// The extra data depends on the packet type:
+	//   Initial: Token.
+	//   Retry: Retry token and integrity tag.
+	extra []byte
+}
+
+// A shortPacket is a short header (1-RTT) packet.
+type shortPacket struct {
+	reservedBits uint8
+	num          packetNumber
+	payload      []byte
+}
diff --git a/internal/quic/packet_test.go b/internal/quic/packet_test.go
new file mode 100644
index 0000000..3011dda
--- /dev/null
+++ b/internal/quic/packet_test.go
@@ -0,0 +1,125 @@
+// 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/hex"
+	"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 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
+}