/*
 *
 * Copyright 2014 gRPC authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package transport

import (
	
	
	
	
	
	
	
	
	
	
	
	
	

	
	
	
	
)

const (
	// http2MaxFrameLen specifies the max length of a HTTP2 frame.
	http2MaxFrameLen = 16384 // 16KB frame
	// https://httpwg.org/specs/rfc7540.html#SettingValues
	http2InitHeaderTableSize = 4096
)

var (
	clientPreface   = []byte(http2.ClientPreface)
	http2ErrConvTab = map[http2.ErrCode]codes.Code{
		http2.ErrCodeNo:                 codes.Internal,
		http2.ErrCodeProtocol:           codes.Internal,
		http2.ErrCodeInternal:           codes.Internal,
		http2.ErrCodeFlowControl:        codes.ResourceExhausted,
		http2.ErrCodeSettingsTimeout:    codes.Internal,
		http2.ErrCodeStreamClosed:       codes.Internal,
		http2.ErrCodeFrameSize:          codes.Internal,
		http2.ErrCodeRefusedStream:      codes.Unavailable,
		http2.ErrCodeCancel:             codes.Canceled,
		http2.ErrCodeCompression:        codes.Internal,
		http2.ErrCodeConnect:            codes.Internal,
		http2.ErrCodeEnhanceYourCalm:    codes.ResourceExhausted,
		http2.ErrCodeInadequateSecurity: codes.PermissionDenied,
		http2.ErrCodeHTTP11Required:     codes.Internal,
	}
	// HTTPStatusConvTab is the HTTP status code to gRPC error code conversion table.
	HTTPStatusConvTab = map[int]codes.Code{
		// 400 Bad Request - INTERNAL.
		http.StatusBadRequest: codes.Internal,
		// 401 Unauthorized  - UNAUTHENTICATED.
		http.StatusUnauthorized: codes.Unauthenticated,
		// 403 Forbidden - PERMISSION_DENIED.
		http.StatusForbidden: codes.PermissionDenied,
		// 404 Not Found - UNIMPLEMENTED.
		http.StatusNotFound: codes.Unimplemented,
		// 429 Too Many Requests - UNAVAILABLE.
		http.StatusTooManyRequests: codes.Unavailable,
		// 502 Bad Gateway - UNAVAILABLE.
		http.StatusBadGateway: codes.Unavailable,
		// 503 Service Unavailable - UNAVAILABLE.
		http.StatusServiceUnavailable: codes.Unavailable,
		// 504 Gateway timeout - UNAVAILABLE.
		http.StatusGatewayTimeout: codes.Unavailable,
	}
)

var grpcStatusDetailsBinHeader = "grpc-status-details-bin"

// isReservedHeader checks whether hdr belongs to HTTP2 headers
// reserved by gRPC protocol. Any other headers are classified as the
// user-specified metadata.
func isReservedHeader( string) bool {
	if  != "" && [0] == ':' {
		return true
	}
	switch  {
	case "content-type",
		"user-agent",
		"grpc-message-type",
		"grpc-encoding",
		"grpc-message",
		"grpc-status",
		"grpc-timeout",
		// Intentionally exclude grpc-previous-rpc-attempts and
		// grpc-retry-pushback-ms, which are "reserved", but their API
		// intentionally works via metadata.
		"te":
		return true
	default:
		return false
	}
}

// isWhitelistedHeader checks whether hdr should be propagated into metadata
// visible to users, even though it is classified as "reserved", above.
func isWhitelistedHeader( string) bool {
	switch  {
	case ":authority", "user-agent":
		return true
	default:
		return false
	}
}

const binHdrSuffix = "-bin"

func encodeBinHeader( []byte) string {
	return base64.RawStdEncoding.EncodeToString()
}

func decodeBinHeader( string) ([]byte, error) {
	if len()%4 == 0 {
		// Input was padded, or padding was not necessary.
		return base64.StdEncoding.DecodeString()
	}
	return base64.RawStdEncoding.DecodeString()
}

func encodeMetadataHeader(,  string) string {
	if strings.HasSuffix(, binHdrSuffix) {
		return encodeBinHeader(([]byte)())
	}
	return 
}

func decodeMetadataHeader(,  string) (string, error) {
	if strings.HasSuffix(, binHdrSuffix) {
		,  := decodeBinHeader()
		return string(), 
	}
	return , nil
}

type timeoutUnit uint8

const (
	hour        timeoutUnit = 'H'
	minute      timeoutUnit = 'M'
	second      timeoutUnit = 'S'
	millisecond timeoutUnit = 'm'
	microsecond timeoutUnit = 'u'
	nanosecond  timeoutUnit = 'n'
)

func timeoutUnitToDuration( timeoutUnit) ( time.Duration,  bool) {
	switch  {
	case hour:
		return time.Hour, true
	case minute:
		return time.Minute, true
	case second:
		return time.Second, true
	case millisecond:
		return time.Millisecond, true
	case microsecond:
		return time.Microsecond, true
	case nanosecond:
		return time.Nanosecond, true
	default:
	}
	return
}

func decodeTimeout( string) (time.Duration, error) {
	 := len()
	if  < 2 {
		return 0, fmt.Errorf("transport: timeout string is too short: %q", )
	}
	if  > 9 {
		// Spec allows for 8 digits plus the unit.
		return 0, fmt.Errorf("transport: timeout string is too long: %q", )
	}
	 := timeoutUnit([-1])
	,  := timeoutUnitToDuration()
	if ! {
		return 0, fmt.Errorf("transport: timeout unit is not recognized: %q", )
	}
	,  := strconv.ParseUint([:-1], 10, 64)
	if  != nil {
		return 0, 
	}
	const  = math.MaxInt64 / uint64(time.Hour)
	if  == time.Hour &&  >  {
		// This timeout would overflow math.MaxInt64; clamp it.
		return time.Duration(math.MaxInt64), nil
	}
	return  * time.Duration(), nil
}

const (
	spaceByte   = ' '
	tildeByte   = '~'
	percentByte = '%'
)

// encodeGrpcMessage is used to encode status code in header field
// "grpc-message". It does percent encoding and also replaces invalid utf-8
// characters with Unicode replacement character.
//
// It checks to see if each individual byte in msg is an allowable byte, and
// then either percent encoding or passing it through. When percent encoding,
// the byte is converted into hexadecimal notation with a '%' prepended.
func encodeGrpcMessage( string) string {
	if  == "" {
		return ""
	}
	 := len()
	for  := 0;  < ; ++ {
		 := []
		if !( >= spaceByte &&  <= tildeByte &&  != percentByte) {
			return encodeGrpcMessageUnchecked()
		}
	}
	return 
}

func encodeGrpcMessageUnchecked( string) string {
	var  strings.Builder
	for len() > 0 {
		,  := utf8.DecodeRuneInString()
		for ,  := range []byte(string()) {
			if  > 1 {
				// If size > 1, r is not ascii. Always do percent encoding.
				fmt.Fprintf(&, "%%%02X", )
				continue
			}

			// The for loop is necessary even if size == 1. r could be
			// utf8.RuneError.
			//
			// fmt.Sprintf("%%%02X", utf8.RuneError) gives "%FFFD".
			if  >= spaceByte &&  <= tildeByte &&  != percentByte {
				.WriteByte()
			} else {
				fmt.Fprintf(&, "%%%02X", )
			}
		}
		 = [:]
	}
	return .String()
}

// decodeGrpcMessage decodes the msg encoded by encodeGrpcMessage.
func decodeGrpcMessage( string) string {
	if  == "" {
		return ""
	}
	 := len()
	for  := 0;  < ; ++ {
		if [] == percentByte && +2 <  {
			return decodeGrpcMessageUnchecked()
		}
	}
	return 
}

func decodeGrpcMessageUnchecked( string) string {
	var  strings.Builder
	 := len()
	for  := 0;  < ; ++ {
		 := []
		if  == percentByte && +2 <  {
			,  := strconv.ParseUint([+1:+3], 16, 8)
			if  != nil {
				.WriteByte()
			} else {
				.WriteByte(byte())
				 += 2
			}
		} else {
			.WriteByte()
		}
	}
	return .String()
}

type bufWriter struct {
	pool      *sync.Pool
	buf       []byte
	offset    int
	batchSize int
	conn      io.Writer
	err       error
}

func newBufWriter( io.Writer,  int,  *sync.Pool) *bufWriter {
	 := &bufWriter{
		batchSize: ,
		conn:      ,
		pool:      ,
	}
	// this indicates that we should use non shared buf
	if  == nil {
		.buf = make([]byte, )
	}
	return 
}

func ( *bufWriter) ( []byte) (int, error) {
	if .err != nil {
		return 0, .err
	}
	if .batchSize == 0 { // Buffer has been disabled.
		,  := .conn.Write()
		return , toIOError()
	}
	if .buf == nil {
		 := .pool.Get().(*[]byte)
		.buf = *
	}
	 := 0
	for len() > 0 {
		 := copy(.buf[.offset:], )
		 = [:]
		 += 
		.offset += 
		if .offset < .batchSize {
			continue
		}
		if  := .flushKeepBuffer();  != nil {
			return , 
		}
	}
	return , nil
}

func ( *bufWriter) () error {
	 := .flushKeepBuffer()
	// Only release the buffer if we are in a "shared" mode
	if .buf != nil && .pool != nil {
		 := .buf
		.pool.Put(&)
		.buf = nil
	}
	return 
}

func ( *bufWriter) () error {
	if .err != nil {
		return .err
	}
	if .offset == 0 {
		return nil
	}
	_, .err = .conn.Write(.buf[:.offset])
	.err = toIOError(.err)
	.offset = 0
	return .err
}

type ioError struct {
	error
}

func ( ioError) () error {
	return .error
}

func isIOError( error) bool {
	return errors.As(, &ioError{})
}

func toIOError( error) error {
	if  == nil {
		return nil
	}
	return ioError{error: }
}

type parsedDataFrame struct {
	http2.FrameHeader
	data mem.Buffer
}

func ( *parsedDataFrame) () bool {
	return .FrameHeader.Flags.Has(http2.FlagDataEndStream)
}

type framer struct {
	writer    *bufWriter
	fr        *http2.Framer
	headerBuf []byte // cached slice for framer headers to reduce heap allocs.
	reader    io.Reader
	dataFrame parsedDataFrame // Cached data frame to avoid heap allocations.
	pool      mem.BufferPool
	errDetail error
}

var writeBufferPoolMap = make(map[int]*sync.Pool)
var writeBufferMutex sync.Mutex

func newFramer( io.ReadWriter, ,  int,  bool,  uint32,  mem.BufferPool) *framer {
	if  < 0 {
		 = 0
	}
	var  io.Reader = 
	if  > 0 {
		 = bufio.NewReaderSize(, )
	}
	var  *sync.Pool
	if  {
		 = getWriteBufferPool()
	}
	 := newBufWriter(, , )
	 := &framer{
		writer: ,
		fr:     http2.NewFramer(, ),
		reader: ,
		pool:   ,
	}
	.fr.SetMaxReadFrameSize(http2MaxFrameLen)
	// Opt-in to Frame reuse API on framer to reduce garbage.
	// Frames aren't safe to read from after a subsequent call to ReadFrame.
	.fr.SetReuseFrames()
	.fr.MaxHeaderListSize = 
	.fr.ReadMetaHeaders = hpack.NewDecoder(http2InitHeaderTableSize, nil)
	return 
}

// writeData writes a DATA frame.
//
// It is the caller's responsibility not to violate the maximum frame size.
func ( *framer) ( uint32,  bool,  [][]byte) error {
	var  http2.Flags
	if  {
		 = http2.FlagDataEndStream
	}
	 := uint32(0)
	for ,  := range  {
		 += uint32(len())
	}
	// TODO: Replace the header write with the framer API being added in
	// https://github.com/golang/go/issues/66655.
	.headerBuf = append(.headerBuf[:0],
		byte(>>16),
		byte(>>8),
		byte(),
		byte(http2.FrameData),
		byte(),
		byte(>>24),
		byte(>>16),
		byte(>>8),
		byte())
	if ,  := .writer.Write(.headerBuf);  != nil {
		return 
	}
	for ,  := range  {
		if ,  := .writer.Write();  != nil {
			return 
		}
	}
	return nil
}

// readFrame reads a single frame. The returned Frame is only valid
// until the next call to readFrame.
func ( *framer) () (any, error) {
	.errDetail = nil
	,  := .fr.ReadFrameHeader()
	if  != nil {
		.errDetail = .fr.ErrorDetail()
		return nil, 
	}
	// Read the data frame directly from the underlying io.Reader to avoid
	// copies.
	if .Type == http2.FrameData {
		 = .readDataFrame()
		return &.dataFrame, 
	}
	,  := .fr.ReadFrameForHeader()
	if  != nil {
		.errDetail = .fr.ErrorDetail()
		return nil, 
	}
	return , 
}

// errorDetail returns a more detailed error of the last error
// returned by framer.readFrame. For instance, if readFrame
// returns a StreamError with code PROTOCOL_ERROR, errorDetail
// will say exactly what was invalid. errorDetail is not guaranteed
// to return a non-nil value.
// errorDetail is reset after the next call to readFrame.
func ( *framer) () error {
	return .errDetail
}

func ( *framer) ( http2.FrameHeader) ( error) {
	if .StreamID == 0 {
		// DATA frames MUST be associated with a stream. If a
		// DATA frame is received whose stream identifier
		// field is 0x0, the recipient MUST respond with a
		// connection error (Section 5.4.1) of type
		// PROTOCOL_ERROR.
		.errDetail = errors.New("DATA frame with stream ID 0")
		return http2.ConnectionError(http2.ErrCodeProtocol)
	}
	// Converting a *[]byte to a mem.SliceBuffer incurs a heap allocation. This
	// conversion is performed by mem.NewBuffer. To avoid the extra allocation
	// a []byte is allocated directly if required and cast to a mem.SliceBuffer.
	var  []byte
	// poolHandle is the pointer returned by the buffer pool (if it's used.).
	var  *[]byte
	 := !mem.IsBelowBufferPoolingThreshold(int(.Length))
	if  {
		 = .pool.Get(int(.Length))
		 = *
		defer func() {
			if  != nil {
				.pool.Put()
			}
		}()
	} else {
		 = make([]byte, int(.Length))
	}
	if .Flags.Has(http2.FlagDataPadded) {
		if .Length == 0 {
			return io.ErrUnexpectedEOF
		}
		// This initial 1-byte read can be inefficient for unbuffered readers,
		// but it allows the rest of the payload to be read directly to the
		// start of the destination slice. This makes it easy to return the
		// original slice back to the buffer pool.
		if ,  := io.ReadFull(.reader, [:1]);  != nil {
			return 
		}
		 := [0]
		 = [:len()-1]
		if int() > len() {
			// If the length of the padding is greater than the
			// length of the frame payload, the recipient MUST
			// treat this as a connection error.
			// Filed: https://github.com/http2/http2-spec/issues/610
			.errDetail = errors.New("pad size larger than data payload")
			return http2.ConnectionError(http2.ErrCodeProtocol)
		}
		if ,  := io.ReadFull(.reader, );  != nil {
			return 
		}
		 = [:len()-int()]
	} else if ,  := io.ReadFull(.reader, );  != nil {
		return 
	}

	.dataFrame.FrameHeader = 
	if  {
		// Update the handle to point to the (potentially re-sliced) buf.
		* = 
		.dataFrame.data = mem.NewBuffer(, .pool)
	} else {
		.dataFrame.data = mem.SliceBuffer()
	}
	return nil
}

func ( *parsedDataFrame) () http2.FrameHeader {
	return .FrameHeader
}

func getWriteBufferPool( int) *sync.Pool {
	writeBufferMutex.Lock()
	defer writeBufferMutex.Unlock()
	,  := writeBufferPoolMap[]
	if  {
		return 
	}
	 = &sync.Pool{
		New: func() any {
			 := make([]byte, )
			return &
		},
	}
	writeBufferPoolMap[] = 
	return 
}

// ParseDialTarget returns the network and address to pass to dialer.
func ( string) (string, string) {
	 := "tcp"
	 := strings.Index(, ":")
	 := strings.Index(, ":/")
	// handle unix:addr which will fail with url.Parse
	if  >= 0 &&  < 0 {
		if  := [0:];  == "unix" {
			return , [+1:]
		}
	}
	if  >= 0 {
		,  := url.Parse()
		if  != nil {
			return , 
		}
		 := .Scheme
		 := .Path
		if  == "unix" {
			if  == "" {
				 = .Host
			}
			return , 
		}
	}
	return , 
}