package http3

import (
	
	
	
	
	
	
	
	
	
	
	
	
	

	

	
)

// Settings are HTTP/3 settings that apply to the underlying connection.
type Settings struct {
	// Support for HTTP/3 datagrams (RFC 9297)
	EnableDatagrams bool
	// Extended CONNECT, RFC 9220
	EnableExtendedConnect bool
	// Other settings, defined by the application
	Other map[uint64]uint64
}

// RoundTripOpt are options for the Transport.RoundTripOpt method.
type RoundTripOpt struct {
	// OnlyCachedConn controls whether the Transport may create a new QUIC connection.
	// If set true and no cached connection is available, RoundTripOpt will return ErrNoCachedConn.
	OnlyCachedConn bool
}

type clientConn interface {
	OpenRequestStream(context.Context) (*RequestStream, error)
	RoundTrip(*http.Request) (*http.Response, error)
	handleUnidirectionalStream(*quic.ReceiveStream)
}

type roundTripperWithCount struct {
	cancel     context.CancelFunc
	dialing    chan struct{} // closed as soon as quic.Dial(Early) returned
	dialErr    error
	conn       *quic.Conn
	clientConn clientConn

	useCount atomic.Int64
}

func ( *roundTripperWithCount) () error {
	.cancel()
	<-.dialing
	if .conn != nil {
		return .conn.CloseWithError(0, "")
	}
	return nil
}

// Transport implements the http.RoundTripper interface
type Transport struct {
	// TLSClientConfig specifies the TLS configuration to use with
	// tls.Client. If nil, the default configuration is used.
	TLSClientConfig *tls.Config

	// QUICConfig is the quic.Config used for dialing new connections.
	// If nil, reasonable default values will be used.
	QUICConfig *quic.Config

	// Dial specifies an optional dial function for creating QUIC
	// connections for requests.
	// If Dial is nil, a UDPConn will be created at the first request
	// and will be reused for subsequent connections to other servers.
	Dial func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error)

	// Enable support for HTTP/3 datagrams (RFC 9297).
	// If a QUICConfig is set, datagram support also needs to be enabled on the QUIC layer by setting EnableDatagrams.
	EnableDatagrams bool

	// Additional HTTP/3 settings.
	// It is invalid to specify any settings defined by RFC 9114 (HTTP/3) and RFC 9297 (HTTP Datagrams).
	AdditionalSettings map[uint64]uint64

	// MaxResponseHeaderBytes specifies a limit on how many response bytes are
	// allowed in the server's response header.
	// Zero means to use a default limit.
	MaxResponseHeaderBytes int

	// DisableCompression, if true, prevents the Transport from requesting compression with an
	// "Accept-Encoding: gzip" request header when the Request contains no existing Accept-Encoding value.
	// If the Transport requests gzip on its own and gets a gzipped response, it's transparently
	// decoded in the Response.Body.
	// However, if the user explicitly requested gzip it is not automatically uncompressed.
	DisableCompression bool

	Logger *slog.Logger

	mutex sync.Mutex

	initOnce sync.Once
	initErr  error

	newClientConn func(*quic.Conn) clientConn

	clients   map[string]*roundTripperWithCount
	transport *quic.Transport
	closed    bool
}

var (
	_ http.RoundTripper = &Transport{}
	_ io.Closer         = &Transport{}
)

var (
	// ErrNoCachedConn is returned when Transport.OnlyCachedConn is set
	ErrNoCachedConn = errors.New("http3: no cached connection was available")
	// ErrTransportClosed is returned when attempting to use a closed Transport
	ErrTransportClosed = errors.New("http3: transport is closed")
)

func ( *Transport) () error {
	if .newClientConn == nil {
		.newClientConn = func( *quic.Conn) clientConn {
			return newClientConn(
				,
				.EnableDatagrams,
				.AdditionalSettings,
				.MaxResponseHeaderBytes,
				.DisableCompression,
				.Logger,
			)
		}
	}
	if .QUICConfig == nil {
		.QUICConfig = defaultQuicConfig.Clone()
		.QUICConfig.EnableDatagrams = .EnableDatagrams
	}
	if .EnableDatagrams && !.QUICConfig.EnableDatagrams {
		return errors.New("HTTP Datagrams enabled, but QUIC Datagrams disabled")
	}
	if len(.QUICConfig.Versions) == 0 {
		.QUICConfig = .QUICConfig.Clone()
		.QUICConfig.Versions = []quic.Version{quic.SupportedVersions()[0]}
	}
	if len(.QUICConfig.Versions) != 1 {
		return errors.New("can only use a single QUIC version for dialing a HTTP/3 connection")
	}
	if .QUICConfig.MaxIncomingStreams == 0 {
		.QUICConfig.MaxIncomingStreams = -1 // don't allow any bidirectional streams
	}
	if .Dial == nil {
		,  := net.ListenUDP("udp", nil)
		if  != nil {
			return 
		}
		.transport = &quic.Transport{Conn: }
	}
	return nil
}

// RoundTripOpt is like RoundTrip, but takes options.
func ( *Transport) ( *http.Request,  RoundTripOpt) (*http.Response, error) {
	,  := .roundTripOpt(, )
	if  != nil {
		if .Body != nil {
			.Body.Close()
		}
		return nil, 
	}
	return , nil
}

func ( *Transport) ( *http.Request,  RoundTripOpt) (*http.Response, error) {
	.initOnce.Do(func() { .initErr = .init() })
	if .initErr != nil {
		return nil, .initErr
	}

	if .URL == nil {
		return nil, errors.New("http3: nil Request.URL")
	}
	if .URL.Scheme != "https" {
		return nil, fmt.Errorf("http3: unsupported protocol scheme: %s", .URL.Scheme)
	}
	if .URL.Host == "" {
		return nil, errors.New("http3: no Host in request URL")
	}
	if .Header == nil {
		return nil, errors.New("http3: nil Request.Header")
	}
	if .Method != "" && !validMethod(.Method) {
		return nil, fmt.Errorf("http3: invalid method %q", .Method)
	}
	for ,  := range .Header {
		if !httpguts.ValidHeaderFieldName() {
			return nil, fmt.Errorf("http3: invalid http header field name %q", )
		}
		for ,  := range  {
			if !httpguts.ValidHeaderFieldValue() {
				return nil, fmt.Errorf("http3: invalid http header field value %q for key %v", , )
			}
		}
	}

	return .doRoundTripOpt(, , false)
}

func ( *Transport) ( *http.Request,  RoundTripOpt,  bool) (*http.Response, error) {
	 := authorityAddr(hostnameFromURL(.URL))
	 := httptrace.ContextClientTrace(.Context())
	traceGetConn(, )
	, ,  := .getClient(.Context(), , .OnlyCachedConn)
	if  != nil {
		return nil, 
	}

	select {
	case <-.dialing:
	case <-.Context().Done():
		return nil, context.Cause(.Context())
	}

	if .dialErr != nil {
		.removeClient()
		return nil, .dialErr
	}
	defer .useCount.Add(-1)
	traceGotConn(, .conn, )
	,  := .clientConn.RoundTrip()
	if  != nil {
		// request aborted due to context cancellation
		select {
		case <-.Context().Done():
			return nil, 
		default:
		}
		if  {
			return nil, 
		}

		.removeClient()
		,  = canRetryRequest(, )
		if  != nil {
			return nil, 
		}
		return .(, , true)
	}
	return , nil
}

func canRetryRequest( error,  *http.Request) (*http.Request, error) {
	// error occurred while opening the stream, we can be sure that the request wasn't sent out
	var  *errConnUnusable
	if errors.As(, &) {
		return , nil
	}

	// If the request stream is reset, we can only be sure that the request wasn't processed
	// if the error code is H3_REQUEST_REJECTED.
	var  *Error
	if !errors.As(, &) || .ErrorCode != ErrCodeRequestRejected {
		return nil, 
	}
	// if the body is nil (or http.NoBody), it's safe to reuse this request and its body
	if .Body == nil || .Body == http.NoBody {
		return , nil
	}
	// if the request body can be reset back to its original state via req.GetBody, do that
	if .GetBody != nil {
		,  := .GetBody()
		if  != nil {
			return nil, 
		}
		 := *
		.Body = 
		 = &
		return &, nil
	}
	return nil, fmt.Errorf("http3: Transport: cannot retry err [%w] after Request.Body was written; define Request.GetBody to avoid this error", )
}

// RoundTrip does a round trip.
func ( *Transport) ( *http.Request) (*http.Response, error) {
	return .RoundTripOpt(, RoundTripOpt{})
}

func ( *Transport) ( context.Context,  string,  bool) ( *roundTripperWithCount,  bool,  error) {
	.mutex.Lock()
	defer .mutex.Unlock()
	if .closed {
		return nil, false, ErrTransportClosed
	}

	if .clients == nil {
		.clients = make(map[string]*roundTripperWithCount)
	}

	,  := .clients[]
	if ! {
		if  {
			return nil, false, ErrNoCachedConn
		}
		,  := context.WithCancel()
		 = &roundTripperWithCount{
			dialing: make(chan struct{}),
			cancel:  ,
		}
		go func() {
			defer close(.dialing)
			defer ()
			, ,  := .dial(, )
			if  != nil {
				.dialErr = 
				return
			}
			.conn = 
			.clientConn = 
		}()
		.clients[] = 
	}
	select {
	case <-.dialing:
		if .dialErr != nil {
			delete(.clients, )
			return nil, false, .dialErr
		}
		select {
		case <-.conn.HandshakeComplete():
			 = true
		default:
		}
	default:
	}
	.useCount.Add(1)
	return , , nil
}

func ( *Transport) ( context.Context,  string) (*quic.Conn, clientConn, error) {
	var  *tls.Config
	if .TLSClientConfig == nil {
		 = &tls.Config{}
	} else {
		 = .TLSClientConfig.Clone()
	}
	if .ServerName == "" {
		, ,  := net.SplitHostPort()
		if  != nil {
			// It's ok if net.SplitHostPort returns an error - it could be a hostname/IP address without a port.
			 = 
		}
		.ServerName = 
	}
	// Replace existing ALPNs by H3
	.NextProtos = []string{NextProtoH3}

	 := .Dial
	if  == nil {
		 = func( context.Context,  string,  *tls.Config,  *quic.Config) (*quic.Conn, error) {
			 := "udp"
			,  := .resolveUDPAddr(, , )
			if  != nil {
				return nil, 
			}
			 := httptrace.ContextClientTrace()
			traceConnectStart(, , .String())
			traceTLSHandshakeStart()
			,  := .transport.DialEarly(, , , )
			var  tls.ConnectionState
			if  != nil {
				 = .ConnectionState().TLS
			}
			traceTLSHandshakeDone(, , )
			traceConnectDone(, , .String(), )
			return , 
		}
	}
	,  := (, , , .QUICConfig)
	if  != nil {
		return nil, nil, 
	}
	 := .newClientConn()
	go func() {
		for {
			,  := .AcceptUniStream(context.Background())
			if  != nil {
				return
			}
			go .handleUnidirectionalStream()
		}
	}()
	return , , nil
}

func ( *Transport) ( context.Context, ,  string) (*net.UDPAddr, error) {
	, ,  := net.SplitHostPort()
	if  != nil {
		return nil, 
	}
	,  := net.LookupPort(, )
	if  != nil {
		return nil, 
	}
	 := net.DefaultResolver
	,  := .LookupIPAddr(, )
	if  != nil {
		return nil, 
	}
	 := addrList()
	 := .forResolve(, )
	return &net.UDPAddr{IP: .IP, Port: , Zone: .Zone}, nil
}

func ( *Transport) ( string) {
	.mutex.Lock()
	defer .mutex.Unlock()
	if .clients == nil {
		return
	}
	delete(.clients, )
}

// NewClientConn creates a new HTTP/3 client connection on top of a QUIC connection.
// Most users should use RoundTrip instead of creating a connection directly.
// Specifically, it is not needed to perform GET, POST, HEAD and CONNECT requests.
//
// Obtaining a ClientConn is only needed for more advanced use cases, such as
// using Extended CONNECT for WebTransport or the various MASQUE protocols.
func ( *Transport) ( *quic.Conn) *ClientConn {
	 := newClientConn(
		,
		.EnableDatagrams,
		.AdditionalSettings,
		.MaxResponseHeaderBytes,
		.DisableCompression,
		.Logger,
	)
	go func() {
		for {
			,  := .AcceptUniStream(context.Background())
			if  != nil {
				return
			}
			go .handleUnidirectionalStream()
		}
	}()
	return 
}

// NewRawClientConn creates a new low-level HTTP/3 client connection on top of a QUIC connection.
// Unlike NewClientConn, the returned RawClientConn allows the application to take control
// of the stream accept loops, by calling HandleUnidirectionalStream for incoming unidirectional
// streams and HandleBidirectionalStream for incoming bidirectional streams.
func ( *Transport) ( *quic.Conn) *RawClientConn {
	return &RawClientConn{
		ClientConn: newClientConn(
			,
			.EnableDatagrams,
			.AdditionalSettings,
			.MaxResponseHeaderBytes,
			.DisableCompression,
			.Logger,
		),
	}
}

// Close closes the QUIC connections that this Transport has used.
// A Transport cannot be used after it has been closed.
func ( *Transport) () error {
	.mutex.Lock()
	defer .mutex.Unlock()
	for ,  := range .clients {
		if  := .Close();  != nil {
			return 
		}
	}
	.clients = nil
	if .transport != nil {
		if  := .transport.Close();  != nil {
			return 
		}
		if  := .transport.Conn.Close();  != nil {
			return 
		}
		.transport = nil
	}
	.closed = true
	return nil
}

func hostnameFromURL( *url.URL) string {
	if  != nil {
		return .Host
	}
	return ""
}

func validMethod( string) bool {
	/*
				     Method         = "OPTIONS"                ; Section 9.2
		   		                    | "GET"                    ; Section 9.3
		   		                    | "HEAD"                   ; Section 9.4
		   		                    | "POST"                   ; Section 9.5
		   		                    | "PUT"                    ; Section 9.6
		   		                    | "DELETE"                 ; Section 9.7
		   		                    | "TRACE"                  ; Section 9.8
		   		                    | "CONNECT"                ; Section 9.9
		   		                    | extension-method
		   		   extension-method = token
		   		     token          = 1*<any CHAR except CTLs or separators>
	*/
	return len() > 0 && strings.IndexFunc(, isNotToken) == -1
}

// copied from net/http/http.go
func isNotToken( rune) bool {
	return !httpguts.IsTokenRune()
}

// CloseIdleConnections closes any QUIC connections in the transport's pool that are currently idle.
// An idle connection is one that was previously used for requests but is now sitting unused.
// This method does not interrupt any connections currently in use.
// It also does not affect connections obtained via NewClientConn.
func ( *Transport) () {
	.mutex.Lock()
	defer .mutex.Unlock()
	for ,  := range .clients {
		if .useCount.Load() == 0 {
			.Close()
			delete(.clients, )
		}
	}
}