// 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.

//go:build go1.21

package quic

import (
	"fmt"
)

// A transportError is a 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)
}

// A StreamErrorCode is an application protocol error code (RFC 9000, Section 20.2)
// indicating whay a stream is being closed.
type StreamErrorCode uint64

func (e StreamErrorCode) Error() string {
	return fmt.Sprintf("stream error code %v", uint64(e))
}

// 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)
}

// Is reports a match if err is an *ApplicationError with a matching Code.
func (e *ApplicationError) Is(err error) bool {
	e2, ok := err.(*ApplicationError)
	return ok && e2.Code == e.Code
}
