Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions internal/transport/grpchttp2/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
*
* Copyright 2024 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 grpchttp2

// ErrCode represents an HTTP/2 Error Code. Error codes are 32-bit fields
// that are used in [RST_STREAM] and [GOAWAY] frames to convey the reasons for
// the stream or connection error. See [HTTP/2 Error Code] for definitions of
// each of the following error codes.
//
// [HTTP/2 Error Code]: https://httpwg.org/specs/rfc7540.html#ErrorCodes
// [RST_STREAM]: https://httpwg.org/specs/rfc7540.html#RST_STREAM
// [GOAWAY]: https://httpwg.org/specs/rfc7540.html#GOAWAY
type ErrCode uint32

const (
ErrCodeNoError ErrCode = 0x0
ErrCodeProtocol ErrCode = 0x1
ErrCodeInternal ErrCode = 0x2
ErrCodeFlowControl ErrCode = 0x3
ErrCodeSettingsTimeout ErrCode = 0x4
ErrCodeStreamClosed ErrCode = 0x5
ErrCodeFrameSize ErrCode = 0x6
ErrCodeRefusedStream ErrCode = 0x7
ErrCodeCancel ErrCode = 0x8
ErrCodeCompression ErrCode = 0x9
ErrCodeConnect ErrCode = 0xa
ErrCodeEnhanceYourCalm ErrCode = 0xb
ErrCodeIndaequateSecurity ErrCode = 0xc
ErrCodeHTTP11Required ErrCode = 0xd
)

var errorCodeNames = map[ErrCode]string{
ErrCodeNoError: "NO_ERROR",
ErrCodeProtocol: "PROTOCOL_ERROR",
ErrCodeInternal: "INTERNAL_ERROR",
ErrCodeFlowControl: "FLOW_CONTROL_ERROR",
ErrCodeSettingsTimeout: "SETTINGS_TIMEOUT",
ErrCodeStreamClosed: "STREAM_CLOSED",
ErrCodeFrameSize: "FRAME_SIZE_ERROR",
ErrCodeRefusedStream: "REFUSED_STREAM",
ErrCodeCancel: "CANCEL",
ErrCodeCompression: "COMPRESSION_ERROR",
ErrCodeConnect: "CONNECT_ERROR",
ErrCodeEnhanceYourCalm: "ENHANCE_YOUR_CALM",
ErrCodeIndaequateSecurity: "INADEQUATE_SECURITY",
ErrCodeHTTP11Required: "HTTP_1_1_REQUIRED",
}

func (err ErrCode) String() string {
if v, ok := errorCodeNames[err]; ok {
return v
}
return "INTERNAL_ERROR"
}
246 changes: 246 additions & 0 deletions internal/transport/grpchttp2/framer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
/*
*
* Copyright 2024 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 grpchttp2 defines HTTP/2 types and a framer API and implementation.
package grpchttp2

import "golang.org/x/net/http2/hpack"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bummer. We cannot fully get rid of x/net/http2 to keep hpack

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's true. But Ricardo did mention this in his design, and also x/net/http2/hpack is a separate package and I would assume this dependency would be much smaller than x/net/http2.


// FrameType represents the type of an HTTP/2 Frame.
// See [Frame Type].
//
// [Frame Type]: https://httpwg.org/specs/rfc7540.html#FrameType
type FrameType uint8

const (
FrameTypeData FrameType = 0x0
FrameTypeHeaders FrameType = 0x1
FrameTypePriority FrameType = 0x2
FrameTypeRSTStream FrameType = 0x3
FrameTypeSettings FrameType = 0x4
FrameTypePushPromise FrameType = 0x5
FrameTypePing FrameType = 0x6
FrameTypeGoAway FrameType = 0x7
FrameTypeWindowUpdate FrameType = 0x8
FrameTypeContinuation FrameType = 0x9
)

// Flags represents one or more flags set on an HTTP/2 Frame.
type Flags uint8

const (
FlagDataEndStream Flags = 0x1
FlagDataPadded Flags = 0x8
FlagHeadersEndStream Flags = 0x1
FlagHeadersEndHeaders Flags = 0x4
FlagHeadersPadded Flags = 0x8
FlagHeadersPriority Flags = 0x20
FlagSettingsAck Flags = 0x1
FlagPingAck Flags = 0x1
FlagContinuationEndHeaders Flags = 0x4
)

// Setting represents the id and value pair of an HTTP/2 setting.
// See [Setting Format].
//
// [Setting Format]: https://httpwg.org/specs/rfc7540.html#SettingFormat
type Setting struct {
ID SettingID
Value uint32
}

// SettingID represents the id of an HTTP/2 setting.
// See [Setting Values].
//
// [Setting Values]: https://httpwg.org/specs/rfc7540.html#SettingValues
type SettingID uint16

const (
SettingsHeaderTableSize SettingID = 0x1
SettingsEnablePush SettingID = 0x2
SettingsMaxConcurrentStreams SettingID = 0x3
SettingsInitialWindowSize SettingID = 0x4
SettingsMaxFrameSize SettingID = 0x5
SettingsMaxHeaderListSize SettingID = 0x6
)

// FrameHeader is the 9 byte header of any HTTP/2 Frame.
// See [Frame Header].
//
// [Frame Header]: https://httpwg.org/specs/rfc7540.html#FrameHeader
type FrameHeader struct {
// Size is the size of the frame's payload without the 9 header bytes.
// As per the HTTP/2 spec, size can be up to 3 bytes, but only frames
// up to 16KB can be processed without agreement.
Size uint32
// Type is a byte that represents the Frame Type.
Type FrameType
// Flags is a byte representing the flags set on this Frame.
Flags Flags
// StreamID is the ID for the stream which this frame is for. If the
// frame is connection specific instead of stream specific, the
// streamID is 0.
StreamID uint32
}

// Frame represents an HTTP/2 Frame.
type Frame interface {
Header() *FrameHeader
}

type DataFrame struct {
hdr *FrameHeader
free func()
Data []byte
}

func (f *DataFrame) Header() *FrameHeader {
return f.hdr
}

func (f *DataFrame) Free() {
if f.free != nil {
f.free()
}
}

type HeadersFrame struct {
hdr *FrameHeader
free func()
HdrBlock []byte
}

func (f *HeadersFrame) Header() *FrameHeader {
return f.hdr
}

func (f *HeadersFrame) Free() {
if f.free != nil {
f.free()
}
}

type RSTStreamFrame struct {
hdr *FrameHeader
Code ErrCode
}

func (f *RSTStreamFrame) Header() *FrameHeader {
return f.hdr
}

type SettingsFrame struct {
hdr *FrameHeader
free func()
settings []Setting
}

func (f *SettingsFrame) Header() *FrameHeader {
return f.hdr
}

type PingFrame struct {
hdr *FrameHeader
free func()
Data []byte
}

func (f *PingFrame) Header() *FrameHeader {
return f.hdr
}

func (f *PingFrame) Free() {
if f.free != nil {
f.free()
}
}

type GoAwayFrame struct {
hdr *FrameHeader
free func()
LastStreamID uint32
Code ErrCode
DebugData []byte
}

func (f *GoAwayFrame) Header() *FrameHeader {
return f.hdr
}

func (f *GoAwayFrame) Free() {
if f.free != nil {
f.free()
}
}

type WindowUpdateFrame struct {
hdr *FrameHeader
Inc uint32
}

func (f *WindowUpdateFrame) Header() *FrameHeader {
return f.hdr
}

type ContinuationFrame struct {
hdr *FrameHeader
free func()
HdrBlock []byte
}

func (f *ContinuationFrame) Header() *FrameHeader {
return f.hdr
}

func (f *ContinuationFrame) Free() {
if f.free != nil {
f.free()
}
}

// MetaHeadersFrame is not a Frame Type that appears on the HTTP/2 Spec. It is
// a representation of the merging and decoding of all the Headers and
// Continuation frames on a Stream.
type MetaHeadersFrame struct {
hdr *FrameHeader
Fields []hpack.HeaderField
}

func (f *MetaHeadersFrame) Header() *FrameHeader {
return f.hdr
}

// Framer represents a Framer used in gRPC-Go.
type Framer interface {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we need a NewFramer function in this package. I would let you decide whether you want to add it as part of this PR or a later PR.

We are missing coverage for all the methods on the structs introduced in the PR. Using this framer to test the different frame types might be ideal here imo.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! A NewFramer function is definitely going to be included. I thought since this implies that a struct implementing the interface was needed, and implementation is a separate stage of the project, it would be included in a future PR.

However, if you think it should be implemented here, it definitely is possible.

// SetMetaDecoder will set a decoder for the framer. When the decoder is
// set, ReadFrame will parse the header values, merging all Headers and
// Continuation frames.
SetMetaDecoder(d *hpack.Decoder)
// ReadFrame returns an HTTP/2 Frame. It is the caller's responsibility to
// free the frame once it is done using it.
ReadFrame() (Frame, error)
WriteData(streamID uint32, endStream bool, data ...[]byte) error
WriteHeaders(streamID uint32, endStream, endHeaders bool, headerBlock ...[]byte) error
WriteRSTStream(streamID uint32, code ErrCode) error
WriteSettings(settings ...Setting) error
WriteSettingsAck() error
WritePing(ack bool, data [8]byte) error
WriteGoAway(maxStreamID uint32, code ErrCode, debugData []byte) error
WriteWindowUpdate(streamID, inc uint32) error
WriteContinuation(streamID uint32, endHeaders bool, headerBlock ...[]byte) error
}