package http3

import (
	
	
	
	
	
	
	
	
	
	

	
	
	
	
)

const (
	// MethodGet0RTT allows a GET request to be sent using 0-RTT.
	// Note that 0-RTT doesn't provide replay protection and should only be used for idempotent requests.
	MethodGet0RTT = "GET_0RTT"
	// MethodHead0RTT allows a HEAD request to be sent using 0-RTT.
	// Note that 0-RTT doesn't provide replay protection and should only be used for idempotent requests.
	MethodHead0RTT = "HEAD_0RTT"
)

const (
	defaultUserAgent              = "quic-go HTTP/3"
	defaultMaxResponseHeaderBytes = 10 * 1 << 20 // 10 MB
)

var errGoAway = errors.New("connection in graceful shutdown")

type errConnUnusable struct{ e error }

func ( *errConnUnusable) () error { return .e }
func ( *errConnUnusable) () string { return fmt.Sprintf("http3: conn unusable: %s", .e.Error()) }

const max1xxResponses = 5 // arbitrary bound on number of informational responses

var defaultQuicConfig = &quic.Config{
	MaxIncomingStreams: -1, // don't allow the server to create bidirectional streams
	KeepAlivePeriod:    10 * time.Second,
}

// ClientConn is an HTTP/3 client doing requests to a single remote server.
type ClientConn struct {
	conn    *quic.Conn
	rawConn *rawConn

	decoder *qpack.Decoder

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

	streamMx     sync.Mutex
	maxStreamID  quic.StreamID // set once a GOAWAY frame is received
	lastStreamID quic.StreamID // the highest stream ID that was opened

	qlogger qlogwriter.Recorder
	logger  *slog.Logger

	requestWriter *requestWriter
}

var _ http.RoundTripper = &ClientConn{}

func newClientConn(
	 *quic.Conn,
	 bool,
	 map[uint64]uint64,
	 int,
	 bool,
	 *slog.Logger,
) *ClientConn {
	var  qlogwriter.Recorder
	if  := .QlogTrace();  != nil && .SupportsSchemas(qlog.EventSchema) {
		 = .AddProducer()
	}
	 := &ClientConn{
		conn:               ,
		additionalSettings: ,
		disableCompression: ,
		maxStreamID:        invalidStreamID,
		lastStreamID:       invalidStreamID,
		logger:             ,
		qlogger:            ,
		decoder:            qpack.NewDecoder(),
	}
	if  <= 0 {
		.maxResponseHeaderBytes = defaultMaxResponseHeaderBytes
	} else {
		.maxResponseHeaderBytes = 
	}
	.requestWriter = newRequestWriter()
	.rawConn = newRawConn(
		,
		,
		.onStreamsEmpty,
		.handleControlStream,
		,
		.logger,
	)
	// send the SETTINGs frame, using 0-RTT data, if possible
	go func() {
		,  := .rawConn.openControlStream(&settingsFrame{
			Datagram:            ,
			Other:               ,
			MaxFieldSectionSize: int64(.maxResponseHeaderBytes),
		})
		if  != nil {
			if .logger != nil {
				.logger.Debug("setting up connection failed", "error", )
			}
			.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeInternalError), "")
			return
		}
	}()
	return 
}

// OpenRequestStream opens a new request stream on the HTTP/3 connection.
func ( *ClientConn) ( context.Context) (*RequestStream, error) {
	return .openRequestStream(, .requestWriter, nil, .disableCompression, .maxResponseHeaderBytes)
}

func ( *ClientConn) (
	 context.Context,
	 *requestWriter,
	 chan<- struct{},
	 bool,
	 int,
) (*RequestStream, error) {
	.streamMx.Lock()
	 := .maxStreamID
	var  quic.StreamID
	if .lastStreamID == invalidStreamID {
		 = 0
	} else {
		 = .lastStreamID + 4
	}
	.streamMx.Unlock()
	// Streams with stream ID equal to or greater than the stream ID carried in the GOAWAY frame
	// will be rejected, see section 5.2 of RFC 9114.
	if  != invalidStreamID &&  >=  {
		return nil, errGoAway
	}

	,  := .conn.OpenStreamSync()
	if  != nil {
		return nil, 
	}

	.streamMx.Lock()
	// take the maximum here, as multiple OpenStreamSync calls might have returned concurrently
	if .lastStreamID == invalidStreamID {
		.lastStreamID = .StreamID()
	} else {
		.lastStreamID = max(.lastStreamID, .StreamID())
	}
	// check again, in case a (or another) GOAWAY frame was received
	 = .maxStreamID
	.streamMx.Unlock()

	if  != invalidStreamID && .StreamID() >=  {
		.CancelRead(quic.StreamErrorCode(ErrCodeRequestCanceled))
		.CancelWrite(quic.StreamErrorCode(ErrCodeRequestCanceled))
		return nil, errGoAway
	}

	 := .rawConn.TrackStream()
	 := &http.Response{}
	 := httptrace.ContextClientTrace()
	return newRequestStream(
		newStream(, .rawConn, , func( io.Reader,  *headersFrame) error {
			,  := decodeTrailers(, , , .decoder, .qlogger, .StreamID())
			if  != nil {
				return 
			}
			.Trailer = 
			return nil
		}, .qlogger),
		,
		,
		.decoder,
		,
		,
		,
	), nil
}

func ( *ClientConn) ( *quic.ReceiveStream) {
	.rawConn.handleUnidirectionalStream(, false)
}

func ( *ClientConn) ( *quic.ReceiveStream,  *frameParser) {
	for {
		,  := .ParseNext(.qlogger)
		if  != nil {
			var  *quic.StreamError
			if  == io.EOF || errors.As(, &) {
				.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeClosedCriticalStream), "")
				return
			}
			.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameError), "")
			return
		}
		// GOAWAY is the only frame allowed at this point:
		// * unexpected frames are ignored by the frame parser
		// * we don't support any extension that might add support for more frames
		,  := .(*goAwayFrame)
		if ! {
			.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameUnexpected), "")
			return
		}
		if .StreamID%4 != 0 { // client-initiated, bidirectional streams
			.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeIDError), "")
			return
		}
		.streamMx.Lock()
		// the server is not allowed to increase the Stream ID in subsequent GOAWAY frames
		if .maxStreamID != invalidStreamID && .StreamID > .maxStreamID {
			.streamMx.Unlock()
			.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeIDError), "")
			return
		}
		.maxStreamID = .StreamID
		.streamMx.Unlock()

		 := .rawConn.hasActiveStreams()
		// immediately close the connection if there are currently no active requests
		if ! {
			.CloseWithError(quic.ApplicationErrorCode(ErrCodeNoError), "")
			return
		}
	}
}

func ( *ClientConn) () {
	.streamMx.Lock()
	defer .streamMx.Unlock()

	// The server is performing a graceful shutdown.
	if .maxStreamID != invalidStreamID {
		.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeNoError), "")
	}
}

// RoundTrip executes a request and returns a response
func ( *ClientConn) ( *http.Request) (*http.Response, error) {
	,  := .roundTrip()
	if  != nil && .Context().Err() != nil {
		// if the context was canceled, return the context cancellation error
		 = .Context().Err()
	}
	return , 
}

func ( *ClientConn) ( *http.Request) (*http.Response, error) {
	// Immediately send out this request, if this is a 0-RTT request.
	switch .Method {
	case MethodGet0RTT:
		// don't modify the original request
		 := *
		 = &
		.Method = http.MethodGet
	case MethodHead0RTT:
		// don't modify the original request
		 := *
		 = &
		.Method = http.MethodHead
	default:
		// wait for the handshake to complete
		select {
		case <-.conn.HandshakeComplete():
		case <-.Context().Done():
			return nil, .Context().Err()
		}
	}

	// It is only possible to send an Extended CONNECT request once the SETTINGS were received.
	// See section 3 of RFC 8441.
	if isExtendedConnectRequest() {
		 := .conn.Context()
		// wait for the server's SETTINGS frame to arrive
		select {
		case <-.rawConn.ReceivedSettings():
		case <-.Done():
			return nil, context.Cause()
		}
		if !.rawConn.Settings().EnableExtendedConnect {
			return nil, errors.New("http3: server didn't enable Extended CONNECT")
		}
	}

	 := make(chan struct{})
	,  := .openRequestStream(
		.Context(),
		.requestWriter,
		,
		.disableCompression,
		.maxResponseHeaderBytes,
	)
	if  != nil {
		return nil, &errConnUnusable{e: }
	}

	// Request Cancellation:
	// This go routine keeps running even after RoundTripOpt() returns.
	// It is shut down when the application is done processing the body.
	 := make(chan struct{})
	go func() {
		defer close()
		select {
		case <-.Context().Done():
			.CancelWrite(quic.StreamErrorCode(ErrCodeRequestCanceled))
			.CancelRead(quic.StreamErrorCode(ErrCodeRequestCanceled))
		case <-:
		}
	}()

	,  := .doRequest(, )
	if  != nil { // if any error occurred
		close()
		<-
		return nil, maybeReplaceError()
	}
	return , maybeReplaceError()
}

// ReceivedSettings returns a channel that is closed once the server's HTTP/3 settings were received.
// Settings can be obtained from the Settings method after the channel was closed.
func ( *ClientConn) () <-chan struct{} {
	return .rawConn.ReceivedSettings()
}

// Settings returns the HTTP/3 settings for this connection.
// It is only valid to call this function after the channel returned by ReceivedSettings was closed.
func ( *ClientConn) () *Settings {
	return .rawConn.Settings()
}

// CloseWithError closes the connection with the given error code and message.
// It is invalid to call this function after the connection was closed.
func ( *ClientConn) ( quic.ApplicationErrorCode,  string) error {
	return .conn.CloseWithError(, )
}

// Context returns a context that is cancelled when the connection is closed.
func ( *ClientConn) () context.Context {
	return .conn.Context()
}

// cancelingReader reads from the io.Reader.
// It cancels writing on the stream if any error other than io.EOF occurs.
type cancelingReader struct {
	r   io.Reader
	str *RequestStream
}

func ( *cancelingReader) ( []byte) (int, error) {
	,  := .r.Read()
	if  != nil &&  != io.EOF {
		.str.CancelWrite(quic.StreamErrorCode(ErrCodeRequestCanceled))
	}
	return , 
}

func ( *ClientConn) ( *RequestStream,  io.ReadCloser,  int64) error {
	defer .Close()
	 := make([]byte, bodyCopyBufferSize)
	 := &cancelingReader{str: , r: }
	if  == -1 {
		,  := io.CopyBuffer(, , )
		return 
	}

	// make sure we don't send more bytes than the content length
	,  := io.CopyBuffer(, io.LimitReader(, ), )
	if  != nil {
		return 
	}
	var  int64
	,  = io.CopyBuffer(io.Discard, , )
	 += 
	if  >  {
		.CancelWrite(quic.StreamErrorCode(ErrCodeRequestCanceled))
		return fmt.Errorf("http: ContentLength=%d with Body length %d", , )
	}
	return 
}

func ( *ClientConn) ( *http.Request,  *RequestStream) (*http.Response, error) {
	 := httptrace.ContextClientTrace(.Context())
	var  bool
	if  := .sendRequestHeader();  != nil {
		traceWroteRequest(, )
		if .logger != nil {
			.logger.Debug("error writing request", "error", )
		}
		 = true
	}
	if ! {
		if .Body == nil {
			traceWroteRequest(, nil)
			.Close()
		} else {
			// send the request body asynchronously
			go func() {
				defer .Close()
				 := int64(-1)
				// According to the documentation for http.Request.ContentLength,
				// a value of 0 with a non-nil Body is also treated as unknown content length.
				if .ContentLength > 0 {
					 = .ContentLength
				}
				 := .sendRequestBody(, .Body, )
				traceWroteRequest(, )
				if  != nil {
					if .logger != nil {
						.logger.Debug("error writing request", "error", )
					}
					return
				}

				if len(.Trailer) > 0 {
					if  := .sendRequestTrailer();  != nil {
						if .logger != nil {
							.logger.Debug("error writing trailers", "error", )
						}
					}
				}
			}()
		}
	}

	// copy from net/http: support 1xx responses
	var  int // number of informational 1xx headers received
	var  *http.Response
	for {
		var  error
		,  = .ReadResponse()
		if  != nil {
			return nil, 
		}
		 := .StatusCode
		 := 100 <=  &&  <= 199
		// treat 101 as a terminal status, see https://github.com/golang/go/issues/26161
		 :=  &&  != http.StatusSwitchingProtocols
		if  {
			++
			if  > max1xxResponses {
				.CancelRead(quic.StreamErrorCode(ErrCodeExcessiveLoad))
				.CancelWrite(quic.StreamErrorCode(ErrCodeExcessiveLoad))
				return nil, errors.New("http3: too many 1xx informational responses")
			}
			traceGot1xxResponse(, , textproto.MIMEHeader(.Header))
			if  == http.StatusContinue {
				traceGot100Continue()
			}
			continue
		}
		break
	}
	 := .conn.ConnectionState().TLS
	.TLS = &
	.Request = 
	return , nil
}

// RawClientConn is a low-level HTTP/3 client connection.
// It allows the application to take control of the stream accept loops,
// giving the application the ability to handle streams originating from the server.
type RawClientConn struct {
	*ClientConn
}

// HandleUnidirectionalStream handles an incoming unidirectional stream.
func ( *RawClientConn) ( *quic.ReceiveStream) {
	.rawConn.handleUnidirectionalStream(, false)
}

// HandleBidirectionalStream handles an incoming bidirectional stream.
func ( *ClientConn) ( *quic.Stream) {
	// According to RFC 9114, the server is not allowed to open bidirectional streams.
	.rawConn.CloseWithError(
		quic.ApplicationErrorCode(ErrCodeStreamCreationError),
		fmt.Sprintf("server opened bidirectional stream %d", .StreamID()),
	)
}