quic: error codes and types

Constants for the transport error codes in RFC 9000 Section 20,
types representing transport errors sent to or received from the peer,
and a type representing application protocol errors.

For golang/go#58547

Change-Id: Ib4325e1272f6e0984f233ef494827a1799d7dc26
Reviewed-on: https://go-review.googlesource.com/c/net/+/495235
Reviewed-by: Jonathan Amsterdam <jba@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Damien Neil <dneil@google.com>
diff --git a/internal/quic/errors.go b/internal/quic/errors.go
new file mode 100644
index 0000000..725a7da
--- /dev/null
+++ b/internal/quic/errors.go
@@ -0,0 +1,110 @@
+// 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 (
+	"fmt"
+)
+
+// A transportError is an transport error code from RFC 9000 Section 20.1.
+//
+// The transportError type doesn't implement the error interface to ensure we always
+// distinguish between errors sent to and received from the peer.
+// See the localTransportError and peerTransportError types below.
+type transportError uint64
+
+// https://www.rfc-editor.org/rfc/rfc9000.html#section-20.1
+const (
+	errNo                   = transportError(0x00)
+	errInternal             = transportError(0x01)
+	errConnectionRefused    = transportError(0x02)
+	errFlowControl          = transportError(0x03)
+	errStreamLimit          = transportError(0x04)
+	errStreamState          = transportError(0x05)
+	errFinalSize            = transportError(0x06)
+	errFrameEncoding        = transportError(0x07)
+	errTransportParameter   = transportError(0x08)
+	errConnectionIDLimit    = transportError(0x09)
+	errProtocolViolation    = transportError(0x0a)
+	errInvalidToken         = transportError(0x0b)
+	errApplicationError     = transportError(0x0c)
+	errCryptoBufferExceeded = transportError(0x0d)
+	errKeyUpdateError       = transportError(0x0e)
+	errAEADLimitReached     = transportError(0x0f)
+	errNoViablePath         = transportError(0x10)
+	errTLSBase              = transportError(0x0100) // 0x0100-0x01ff; base + TLS code
+)
+
+func (e transportError) String() string {
+	switch e {
+	case errNo:
+		return "NO_ERROR"
+	case errInternal:
+		return "INTERNAL_ERROR"
+	case errConnectionRefused:
+		return "CONNECTION_REFUSED"
+	case errFlowControl:
+		return "FLOW_CONTROL_ERROR"
+	case errStreamLimit:
+		return "STREAM_LIMIT_ERROR"
+	case errStreamState:
+		return "STREAM_STATE_ERROR"
+	case errFinalSize:
+		return "FINAL_SIZE_ERROR"
+	case errFrameEncoding:
+		return "FRAME_ENCODING_ERROR"
+	case errTransportParameter:
+		return "TRANSPORT_PARAMETER_ERROR"
+	case errConnectionIDLimit:
+		return "CONNECTION_ID_LIMIT_ERROR"
+	case errProtocolViolation:
+		return "PROTOCOL_VIOLATION"
+	case errInvalidToken:
+		return "INVALID_TOKEN"
+	case errApplicationError:
+		return "APPLICATION_ERROR"
+	case errCryptoBufferExceeded:
+		return "CRYPTO_BUFFER_EXCEEDED"
+	case errKeyUpdateError:
+		return "KEY_UPDATE_ERROR"
+	case errAEADLimitReached:
+		return "AEAD_LIMIT_REACHED"
+	case errNoViablePath:
+		return "NO_VIABLE_PATH"
+	}
+	if e >= 0x0100 && e <= 0x01ff {
+		return fmt.Sprintf("CRYPTO_ERROR(%v)", uint64(e)&0xff)
+	}
+	return fmt.Sprintf("ERROR %d", uint64(e))
+}
+
+// A localTransportError is an error sent to the peer.
+type localTransportError transportError
+
+func (e localTransportError) Error() string {
+	return "closed connection: " + transportError(e).String()
+}
+
+// A peerTransportError is an error received from the peer.
+type peerTransportError struct {
+	code   transportError
+	reason string
+}
+
+func (e peerTransportError) Error() string {
+	return fmt.Sprintf("peer closed connection: %v: %q", e.code, e.reason)
+}
+
+// An ApplicationError is an application protocol error code (RFC 9000, Section 20.2).
+// Application protocol errors may be sent when terminating a stream or connection.
+type ApplicationError struct {
+	Code   uint64
+	Reason string
+}
+
+func (e ApplicationError) Error() string {
+	// TODO: Include the Reason string here, but sanitize it first.
+	return fmt.Sprintf("AppError %v", e.Code)
+}