* * 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 (
	
	
	
	
	
	
	
	
	
	
	
	

	
	
	
	spb 
	
	
	
	
)

http2MaxFrameLen specifies the max length of a HTTP2 frame.
http://http2.github.io/http2-spec/#SettingValues
HTTPStatusConvTab is the HTTP status code to gRPC error code conversion table.
400 Bad Request - INTERNAL.
401 Unauthorized - UNAUTHENTICATED.
403 Forbidden - PERMISSION_DENIED.
404 Not Found - UNIMPLEMENTED.
429 Too Many Requests - UNAVAILABLE.
502 Bad Gateway - UNAVAILABLE.
503 Service Unavailable - UNAVAILABLE.
504 Gateway timeout - UNAVAILABLE.
statusGen caches the stream status received from the trailer the server sent. Client side only. Do not access directly. After all trailers are parsed, use the status method to retrieve the status.
rawStatusCode and rawStatusMsg are set from the raw trailer fields and are not intended for direct access outside of parsing.
Server side only fields.
key-value metadata map from the peer.
isGRPC field indicates whether the peer is speaking gRPC (otherwise HTTP). We are in gRPC mode (peer speaking gRPC) if: * We are client side and have already received a HEADER frame that indicates gRPC peer. * The header contains valid a content-type, i.e. a string starts with "application/grpc" And we should handle error specific to gRPC. Otherwise (i.e. a content-type string starts without "application/grpc", or does not exist), we are in HTTP fallback mode, and should handle error specific to HTTP.
decodeState configures decoding criteria and records the decoded data.
whether decoding on server side or not
Records the states during HPACK decoding. It will be filled with info parsed from HTTP HEADERS frame once decodeHeader function has been invoked and returned.
isReservedHeader checks whether hdr belongs to HTTP2 headers reserved by gRPC protocol. Any other headers are classified as the user-specified metadata.
func ( 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 ( string) bool {
	switch  {
	case ":authority", "user-agent":
		return true
	default:
		return false
	}
}

func ( *decodeState) () *status.Status {
No status-details were provided; generate status using code/msg.
Input was padded, or padding was not necessary.
		return base64.StdEncoding.DecodeString()
	}
	return base64.RawStdEncoding.DecodeString()
}

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

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

frame.Truncated is set to true when framer detects that the current header list size hits MaxHeaderListSize limit.
	if .Truncated {
		return status.Error(codes.Internal, "peer header list size exceeded limit")
	}

	for ,  := range .Fields {
		.processHeaderField()
	}

	if .data.isGRPC {
		if .data.grpcErr != nil {
			return .data.grpcErr
		}
		if .serverSide {
			return nil
		}
gRPC status doesn't exist. Set rawStatusCode to be unknown and return nil error. So that, if the stream has ended this Unknown status will be propagated to the user. Otherwise, it will be ignored. In which case, status from a later trailer, that has StreamEnded flag set, is propagated.
			 := int(codes.Unknown)
			.data.rawStatusCode = &
		}
		return nil
	}
HTTP fallback mode
	if .data.httpErr != nil {
		return .data.httpErr
	}

	var (
		 = codes.Internal // when header does not include HTTP status, return INTERNAL
		   bool
	)

	if .data.httpStatus != nil {
		,  = HTTPStatusConvTab[*(.data.httpStatus)]
		if ! {
			 = codes.Unknown
		}
	}

	return status.Error(, .constructHTTPErrMsg())
}
constructErrMsg constructs error message to be returned in HTTP fallback mode. Format: HTTP status code and its corresponding message + content-type error message.
func ( *decodeState) () string {
	var  []string

	if .data.httpStatus == nil {
		 = append(, "malformed header: missing HTTP status")
	} else {
		 = append(, fmt.Sprintf("%s: HTTP status code %d", http.StatusText(*(.data.httpStatus)), *.data.httpStatus))
	}

	if .data.contentTypeErr == "" {
		 = append(, "transport: missing content-type field")
	} else {
		 = append(, .data.contentTypeErr)
	}

	return strings.Join(, "; ")
}

func ( *decodeState) (,  string) {
	if .data.mdata == nil {
		.data.mdata = make(map[string][]string)
	}
	.data.mdata[] = append(.data.mdata[], )
}

func ( *decodeState) ( hpack.HeaderField) {
	switch .Name {
	case "content-type":
		,  := grpcutil.ContentSubtype(.Value)
		if ! {
			.data.contentTypeErr = fmt.Sprintf("transport: received the unexpected content-type %q", .Value)
			return
		}
TODO: do we want to propagate the whole content-type in the metadata, or come up with a way to just propagate the content-subtype if it was set? ie {"content-type": "application/grpc+proto"} or {"content-subtype": "proto"} in the metadata?
		.addMetadata(.Name, .Value)
		.data.isGRPC = true
	case "grpc-encoding":
		.data.encoding = .Value
	case "grpc-status":
		,  := strconv.Atoi(.Value)
		if  != nil {
			.data.grpcErr = status.Errorf(codes.Internal, "transport: malformed grpc-status: %v", )
			return
		}
		.data.rawStatusCode = &
	case "grpc-message":
		.data.rawStatusMsg = decodeGrpcMessage(.Value)
	case "grpc-status-details-bin":
		,  := decodeBinHeader(.Value)
		if  != nil {
			.data.grpcErr = status.Errorf(codes.Internal, "transport: malformed grpc-status-details-bin: %v", )
			return
		}
		 := &spb.Status{}
		if  := proto.Unmarshal(, );  != nil {
			.data.grpcErr = status.Errorf(codes.Internal, "transport: malformed grpc-status-details-bin: %v", )
			return
		}
		.data.statusGen = status.FromProto()
	case "grpc-timeout":
		.data.timeoutSet = true
		var  error
		if .data.timeout,  = decodeTimeout(.Value);  != nil {
			.data.grpcErr = status.Errorf(codes.Internal, "transport: malformed time-out: %v", )
		}
	case ":path":
		.data.method = .Value
	case ":status":
		,  := strconv.Atoi(.Value)
		if  != nil {
			.data.httpErr = status.Errorf(codes.Internal, "transport: malformed http-status: %v", )
			return
		}
		.data.httpStatus = &
	case "grpc-tags-bin":
		,  := decodeBinHeader(.Value)
		if  != nil {
			.data.grpcErr = status.Errorf(codes.Internal, "transport: malformed grpc-tags-bin: %v", )
			return
		}
		.data.statsTags = 
		.addMetadata(.Name, string())
	case "grpc-trace-bin":
		,  := decodeBinHeader(.Value)
		if  != nil {
			.data.grpcErr = status.Errorf(codes.Internal, "transport: malformed grpc-trace-bin: %v", )
			return
		}
		.data.statsTrace = 
		.addMetadata(.Name, string())
	default:
		if isReservedHeader(.Name) && !isWhitelistedHeader(.Name) {
			break
		}
		,  := decodeMetadataHeader(.Name, .Value)
		if  != nil {
			if logger.V(logLevel) {
				logger.Errorf("Failed to decode metadata header (%q, %q): %v", .Name, .Value, )
			}
			return
		}
		.addMetadata(.Name, )
	}
}

type timeoutUnit uint8

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

func ( 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 ( string) (time.Duration, error) {
	 := len()
	if  < 2 {
		return 0, fmt.Errorf("transport: timeout string is too short: %q", )
	}
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.ParseInt([:-1], 10, 64)
	if  != nil {
		return 0, 
	}
	const  = math.MaxInt64 / int64(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 ( string) string {
	if  == "" {
		return ""
	}
	 := len()
	for  := 0;  < ; ++ {
		 := []
		if !( >= spaceByte &&  <= tildeByte &&  != percentByte) {
			return encodeGrpcMessageUnchecked()
		}
	}
	return 
}

func ( string) string {
	var  bytes.Buffer
	for len() > 0 {
		,  := utf8.DecodeRuneInString()
		for ,  := range []byte(string()) {
If size > 1, r is not ascii. Always do percent encoding.
				.WriteString(fmt.Sprintf("%%%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 {
				.WriteString(fmt.Sprintf("%%%02X", ))
			}
		}
		 = [:]
	}
	return .String()
}
decodeGrpcMessage decodes the msg encoded by encodeGrpcMessage.
func ( string) string {
	if  == "" {
		return ""
	}
	 := len()
	for  := 0;  < ; ++ {
		if [] == percentByte && +2 <  {
			return decodeGrpcMessageUnchecked()
		}
	}
	return 
}

func ( string) string {
	var  bytes.Buffer
	 := 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 {
	buf       []byte
	offset    int
	batchSize int
	conn      net.Conn
	err       error

	onFlush func()
}

func ( net.Conn,  int) *bufWriter {
	return &bufWriter{
		buf:       make([]byte, *2),
		batchSize: ,
		conn:      ,
	}
}

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

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

type framer struct {
	writer *bufWriter
	fr     *http2.Framer
}

func ( net.Conn, ,  int,  uint32) *framer {
	if  < 0 {
		 = 0
	}
	var  io.Reader = 
	if  > 0 {
		 = bufio.NewReaderSize(, )
	}
	 := newBufWriter(, )
	 := &framer{
		writer: ,
		fr:     http2.NewFramer(, ),
	}
Opt-in to Frame reuse API on framer to reduce garbage. Frames aren't safe to read from after a subsequent call to ReadFrame.