package http2
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"log"
"strings"
"sync"
"golang.org/x/net/http/httpguts"
"golang.org/x/net/http2/hpack"
)
const frameHeaderLen = 9
var padZeros = make ([]byte , 255 )
type FrameType uint8
const (
FrameData FrameType = 0x0
FrameHeaders FrameType = 0x1
FramePriority FrameType = 0x2
FrameRSTStream FrameType = 0x3
FrameSettings FrameType = 0x4
FramePushPromise FrameType = 0x5
FramePing FrameType = 0x6
FrameGoAway FrameType = 0x7
FrameWindowUpdate FrameType = 0x8
FrameContinuation FrameType = 0x9
)
var frameNames = [...]string {
FrameData : "DATA" ,
FrameHeaders : "HEADERS" ,
FramePriority : "PRIORITY" ,
FrameRSTStream : "RST_STREAM" ,
FrameSettings : "SETTINGS" ,
FramePushPromise : "PUSH_PROMISE" ,
FramePing : "PING" ,
FrameGoAway : "GOAWAY" ,
FrameWindowUpdate : "WINDOW_UPDATE" ,
FrameContinuation : "CONTINUATION" ,
}
func (t FrameType ) String () string {
if int (t ) < len (frameNames ) {
return frameNames [t ]
}
return fmt .Sprintf ("UNKNOWN_FRAME_TYPE_%d" , t )
}
type Flags uint8
func (f Flags ) Has (v Flags ) bool {
return (f & v ) == v
}
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
FlagPushPromiseEndHeaders Flags = 0x4
FlagPushPromisePadded Flags = 0x8
)
var flagName = map [FrameType ]map [Flags ]string {
FrameData : {
FlagDataEndStream : "END_STREAM" ,
FlagDataPadded : "PADDED" ,
},
FrameHeaders : {
FlagHeadersEndStream : "END_STREAM" ,
FlagHeadersEndHeaders : "END_HEADERS" ,
FlagHeadersPadded : "PADDED" ,
FlagHeadersPriority : "PRIORITY" ,
},
FrameSettings : {
FlagSettingsAck : "ACK" ,
},
FramePing : {
FlagPingAck : "ACK" ,
},
FrameContinuation : {
FlagContinuationEndHeaders : "END_HEADERS" ,
},
FramePushPromise : {
FlagPushPromiseEndHeaders : "END_HEADERS" ,
FlagPushPromisePadded : "PADDED" ,
},
}
type frameParser func (fc *frameCache , fh FrameHeader , countError func (string ), payload []byte ) (Frame , error )
var frameParsers = [...]frameParser {
FrameData : parseDataFrame ,
FrameHeaders : parseHeadersFrame ,
FramePriority : parsePriorityFrame ,
FrameRSTStream : parseRSTStreamFrame ,
FrameSettings : parseSettingsFrame ,
FramePushPromise : parsePushPromise ,
FramePing : parsePingFrame ,
FrameGoAway : parseGoAwayFrame ,
FrameWindowUpdate : parseWindowUpdateFrame ,
FrameContinuation : parseContinuationFrame ,
}
func typeFrameParser(t FrameType ) frameParser {
if int (t ) < len (frameParsers ) {
return frameParsers [t ]
}
return parseUnknownFrame
}
type FrameHeader struct {
valid bool
Type FrameType
Flags Flags
Length uint32
StreamID uint32
}
func (h FrameHeader ) Header () FrameHeader { return h }
func (h FrameHeader ) String () string {
var buf bytes .Buffer
buf .WriteString ("[FrameHeader " )
h .writeDebug (&buf )
buf .WriteByte (']' )
return buf .String ()
}
func (h FrameHeader ) writeDebug (buf *bytes .Buffer ) {
buf .WriteString (h .Type .String ())
if h .Flags != 0 {
buf .WriteString (" flags=" )
set := 0
for i := uint8 (0 ); i < 8 ; i ++ {
if h .Flags &(1 <<i ) == 0 {
continue
}
set ++
if set > 1 {
buf .WriteByte ('|' )
}
name := flagName [h .Type ][Flags (1 <<i )]
if name != "" {
buf .WriteString (name )
} else {
fmt .Fprintf (buf , "0x%x" , 1 <<i )
}
}
}
if h .StreamID != 0 {
fmt .Fprintf (buf , " stream=%d" , h .StreamID )
}
fmt .Fprintf (buf , " len=%d" , h .Length )
}
func (h *FrameHeader ) checkValid () {
if !h .valid {
panic ("Frame accessor called on non-owned Frame" )
}
}
func (h *FrameHeader ) invalidate () { h .valid = false }
var fhBytes = sync .Pool {
New : func () interface {} {
buf := make ([]byte , frameHeaderLen )
return &buf
},
}
func invalidHTTP1LookingFrameHeader() FrameHeader {
fh , _ := readFrameHeader (make ([]byte , frameHeaderLen ), strings .NewReader ("HTTP/1.1 " ))
return fh
}
func ReadFrameHeader (r io .Reader ) (FrameHeader , error ) {
bufp := fhBytes .Get ().(*[]byte )
defer fhBytes .Put (bufp )
return readFrameHeader (*bufp , r )
}
func readFrameHeader(buf []byte , r io .Reader ) (FrameHeader , error ) {
_ , err := io .ReadFull (r , buf [:frameHeaderLen ])
if err != nil {
return FrameHeader {}, err
}
return FrameHeader {
Length : (uint32 (buf [0 ])<<16 | uint32 (buf [1 ])<<8 | uint32 (buf [2 ])),
Type : FrameType (buf [3 ]),
Flags : Flags (buf [4 ]),
StreamID : binary .BigEndian .Uint32 (buf [5 :]) & (1 <<31 - 1 ),
valid : true ,
}, nil
}
type Frame interface {
Header () FrameHeader
invalidate()
}
type Framer struct {
r io .Reader
lastFrame Frame
errDetail error
countError func (errToken string )
lastHeaderStream uint32
lastFrameType FrameType
maxReadSize uint32
headerBuf [frameHeaderLen ]byte
getReadBuf func (size uint32 ) []byte
readBuf []byte
maxWriteSize uint32
w io .Writer
wbuf []byte
AllowIllegalWrites bool
AllowIllegalReads bool
ReadMetaHeaders *hpack .Decoder
MaxHeaderListSize uint32
logReads, logWrites bool
debugFramer *Framer
debugFramerBuf *bytes .Buffer
debugReadLoggerf func (string , ...interface {})
debugWriteLoggerf func (string , ...interface {})
frameCache *frameCache
}
func (fr *Framer ) maxHeaderListSize () uint32 {
if fr .MaxHeaderListSize == 0 {
return 16 << 20
}
return fr .MaxHeaderListSize
}
func (f *Framer ) startWrite (ftype FrameType , flags Flags , streamID uint32 ) {
f .wbuf = append (f .wbuf [:0 ],
0 ,
0 ,
0 ,
byte (ftype ),
byte (flags ),
byte (streamID >>24 ),
byte (streamID >>16 ),
byte (streamID >>8 ),
byte (streamID ))
}
func (f *Framer ) endWrite () error {
length := len (f .wbuf ) - frameHeaderLen
if length >= (1 << 24 ) {
return ErrFrameTooLarge
}
_ = append (f .wbuf [:0 ],
byte (length >>16 ),
byte (length >>8 ),
byte (length ))
if f .logWrites {
f .logWrite ()
}
n , err := f .w .Write (f .wbuf )
if err == nil && n != len (f .wbuf ) {
err = io .ErrShortWrite
}
return err
}
func (f *Framer ) logWrite () {
if f .debugFramer == nil {
f .debugFramerBuf = new (bytes .Buffer )
f .debugFramer = NewFramer (nil , f .debugFramerBuf )
f .debugFramer .logReads = false
f .debugFramer .AllowIllegalReads = true
}
f .debugFramerBuf .Write (f .wbuf )
fr , err := f .debugFramer .ReadFrame ()
if err != nil {
f .debugWriteLoggerf ("http2: Framer %p: failed to decode just-written frame" , f )
return
}
f .debugWriteLoggerf ("http2: Framer %p: wrote %v" , f , summarizeFrame (fr ))
}
func (f *Framer ) writeByte (v byte ) { f .wbuf = append (f .wbuf , v ) }
func (f *Framer ) writeBytes (v []byte ) { f .wbuf = append (f .wbuf , v ...) }
func (f *Framer ) writeUint16 (v uint16 ) { f .wbuf = append (f .wbuf , byte (v >>8 ), byte (v )) }
func (f *Framer ) writeUint32 (v uint32 ) {
f .wbuf = append (f .wbuf , byte (v >>24 ), byte (v >>16 ), byte (v >>8 ), byte (v ))
}
const (
minMaxFrameSize = 1 << 14
maxFrameSize = 1 <<24 - 1
)
func (fr *Framer ) SetReuseFrames () {
if fr .frameCache != nil {
return
}
fr .frameCache = &frameCache {}
}
type frameCache struct {
dataFrame DataFrame
}
func (fc *frameCache ) getDataFrame () *DataFrame {
if fc == nil {
return &DataFrame {}
}
return &fc .dataFrame
}
func NewFramer (w io .Writer , r io .Reader ) *Framer {
fr := &Framer {
w : w ,
r : r ,
countError : func (string ) {},
logReads : logFrameReads ,
logWrites : logFrameWrites ,
debugReadLoggerf : log .Printf ,
debugWriteLoggerf : log .Printf ,
}
fr .getReadBuf = func (size uint32 ) []byte {
if cap (fr .readBuf ) >= int (size ) {
return fr .readBuf [:size ]
}
fr .readBuf = make ([]byte , size )
return fr .readBuf
}
fr .SetMaxReadFrameSize (maxFrameSize )
return fr
}
func (fr *Framer ) SetMaxReadFrameSize (v uint32 ) {
if v > maxFrameSize {
v = maxFrameSize
}
fr .maxReadSize = v
}
func (fr *Framer ) ErrorDetail () error {
return fr .errDetail
}
var ErrFrameTooLarge = errors .New ("http2: frame too large" )
func terminalReadFrameError(err error ) bool {
if _ , ok := err .(StreamError ); ok {
return false
}
return err != nil
}
func (fr *Framer ) ReadFrameHeader () (FrameHeader , error ) {
fr .errDetail = nil
fh , err := readFrameHeader (fr .headerBuf [:], fr .r )
if err != nil {
return fh , err
}
if fh .Length > fr .maxReadSize {
if fh == invalidHTTP1LookingFrameHeader () {
return fh , fmt .Errorf ("http2: failed reading the frame payload: %w, note that the frame header looked like an HTTP/1.1 header" , ErrFrameTooLarge )
}
return fh , ErrFrameTooLarge
}
if err := fr .checkFrameOrder (fh ); err != nil {
return fh , err
}
return fh , nil
}
func (fr *Framer ) ReadFrameForHeader (fh FrameHeader ) (Frame , error ) {
if fr .lastFrame != nil {
fr .lastFrame .invalidate ()
}
payload := fr .getReadBuf (fh .Length )
if _ , err := io .ReadFull (fr .r , payload ); err != nil {
if fh == invalidHTTP1LookingFrameHeader () {
return nil , fmt .Errorf ("http2: failed reading the frame payload: %w, note that the frame header looked like an HTTP/1.1 header" , err )
}
return nil , err
}
f , err := typeFrameParser (fh .Type )(fr .frameCache , fh , fr .countError , payload )
if err != nil {
if ce , ok := err .(connError ); ok {
return nil , fr .connError (ce .Code , ce .Reason )
}
return nil , err
}
fr .lastFrame = f
if fr .logReads {
fr .debugReadLoggerf ("http2: Framer %p: read %v" , fr , summarizeFrame (f ))
}
if fh .Type == FrameHeaders && fr .ReadMetaHeaders != nil {
return fr .readMetaFrame (f .(*HeadersFrame ))
}
return f , nil
}
func (fr *Framer ) ReadFrame () (Frame , error ) {
fh , err := fr .ReadFrameHeader ()
if err != nil {
return nil , err
}
return fr .ReadFrameForHeader (fh )
}
func (fr *Framer ) connError (code ErrCode , reason string ) error {
fr .errDetail = errors .New (reason )
return ConnectionError (code )
}
func (fr *Framer ) checkFrameOrder (fh FrameHeader ) error {
lastType := fr .lastFrameType
fr .lastFrameType = fh .Type
if fr .AllowIllegalReads {
return nil
}
if fr .lastHeaderStream != 0 {
if fh .Type != FrameContinuation {
return fr .connError (ErrCodeProtocol ,
fmt .Sprintf ("got %s for stream %d; expected CONTINUATION following %s for stream %d" ,
fh .Type , fh .StreamID ,
lastType , fr .lastHeaderStream ))
}
if fh .StreamID != fr .lastHeaderStream {
return fr .connError (ErrCodeProtocol ,
fmt .Sprintf ("got CONTINUATION for stream %d; expected stream %d" ,
fh .StreamID , fr .lastHeaderStream ))
}
} else if fh .Type == FrameContinuation {
return fr .connError (ErrCodeProtocol , fmt .Sprintf ("unexpected CONTINUATION for stream %d" , fh .StreamID ))
}
switch fh .Type {
case FrameHeaders , FrameContinuation :
if fh .Flags .Has (FlagHeadersEndHeaders ) {
fr .lastHeaderStream = 0
} else {
fr .lastHeaderStream = fh .StreamID
}
}
return nil
}
type DataFrame struct {
FrameHeader
data []byte
}
func (f *DataFrame ) StreamEnded () bool {
return f .FrameHeader .Flags .Has (FlagDataEndStream )
}
func (f *DataFrame ) Data () []byte {
f .checkValid ()
return f .data
}
func parseDataFrame(fc *frameCache , fh FrameHeader , countError func (string ), payload []byte ) (Frame , error ) {
if fh .StreamID == 0 {
countError ("frame_data_stream_0" )
return nil , connError {ErrCodeProtocol , "DATA frame with stream ID 0" }
}
f := fc .getDataFrame ()
f .FrameHeader = fh
var padSize byte
if fh .Flags .Has (FlagDataPadded ) {
var err error
payload , padSize , err = readByte (payload )
if err != nil {
countError ("frame_data_pad_byte_short" )
return nil , err
}
}
if int (padSize ) > len (payload ) {
countError ("frame_data_pad_too_big" )
return nil , connError {ErrCodeProtocol , "pad size larger than data payload" }
}
f .data = payload [:len (payload )-int (padSize )]
return f , nil
}
var (
errStreamID = errors .New ("invalid stream ID" )
errDepStreamID = errors .New ("invalid dependent stream ID" )
errPadLength = errors .New ("pad length too large" )
errPadBytes = errors .New ("padding bytes must all be zeros unless AllowIllegalWrites is enabled" )
)
func validStreamIDOrZero(streamID uint32 ) bool {
return streamID &(1 <<31 ) == 0
}
func validStreamID(streamID uint32 ) bool {
return streamID != 0 && streamID &(1 <<31 ) == 0
}
func (f *Framer ) WriteData (streamID uint32 , endStream bool , data []byte ) error {
return f .WriteDataPadded (streamID , endStream , data , nil )
}
func (f *Framer ) WriteDataPadded (streamID uint32 , endStream bool , data , pad []byte ) error {
if err := f .startWriteDataPadded (streamID , endStream , data , pad ); err != nil {
return err
}
return f .endWrite ()
}
func (f *Framer ) startWriteDataPadded (streamID uint32 , endStream bool , data , pad []byte ) error {
if !validStreamID (streamID ) && !f .AllowIllegalWrites {
return errStreamID
}
if len (pad ) > 0 {
if len (pad ) > 255 {
return errPadLength
}
if !f .AllowIllegalWrites {
for _ , b := range pad {
if b != 0 {
return errPadBytes
}
}
}
}
var flags Flags
if endStream {
flags |= FlagDataEndStream
}
if pad != nil {
flags |= FlagDataPadded
}
f .startWrite (FrameData , flags , streamID )
if pad != nil {
f .wbuf = append (f .wbuf , byte (len (pad )))
}
f .wbuf = append (f .wbuf , data ...)
f .wbuf = append (f .wbuf , pad ...)
return nil
}
type SettingsFrame struct {
FrameHeader
p []byte
}
func parseSettingsFrame(_ *frameCache , fh FrameHeader , countError func (string ), p []byte ) (Frame , error ) {
if fh .Flags .Has (FlagSettingsAck ) && fh .Length > 0 {
countError ("frame_settings_ack_with_length" )
return nil , ConnectionError (ErrCodeFrameSize )
}
if fh .StreamID != 0 {
countError ("frame_settings_has_stream" )
return nil , ConnectionError (ErrCodeProtocol )
}
if len (p )%6 != 0 {
countError ("frame_settings_mod_6" )
return nil , ConnectionError (ErrCodeFrameSize )
}
f := &SettingsFrame {FrameHeader : fh , p : p }
if v , ok := f .Value (SettingInitialWindowSize ); ok && v > (1 <<31 )-1 {
countError ("frame_settings_window_size_too_big" )
return nil , ConnectionError (ErrCodeFlowControl )
}
return f , nil
}
func (f *SettingsFrame ) IsAck () bool {
return f .FrameHeader .Flags .Has (FlagSettingsAck )
}
func (f *SettingsFrame ) Value (id SettingID ) (v uint32 , ok bool ) {
f .checkValid ()
for i := 0 ; i < f .NumSettings (); i ++ {
if s := f .Setting (i ); s .ID == id {
return s .Val , true
}
}
return 0 , false
}
func (f *SettingsFrame ) Setting (i int ) Setting {
buf := f .p
return Setting {
ID : SettingID (binary .BigEndian .Uint16 (buf [i *6 : i *6 +2 ])),
Val : binary .BigEndian .Uint32 (buf [i *6 +2 : i *6 +6 ]),
}
}
func (f *SettingsFrame ) NumSettings () int { return len (f .p ) / 6 }
func (f *SettingsFrame ) HasDuplicates () bool {
num := f .NumSettings ()
if num == 0 {
return false
}
if num < 10 {
for i := 0 ; i < num ; i ++ {
idi := f .Setting (i ).ID
for j := i + 1 ; j < num ; j ++ {
idj := f .Setting (j ).ID
if idi == idj {
return true
}
}
}
return false
}
seen := map [SettingID ]bool {}
for i := 0 ; i < num ; i ++ {
id := f .Setting (i ).ID
if seen [id ] {
return true
}
seen [id ] = true
}
return false
}
func (f *SettingsFrame ) ForeachSetting (fn func (Setting ) error ) error {
f .checkValid ()
for i := 0 ; i < f .NumSettings (); i ++ {
if err := fn (f .Setting (i )); err != nil {
return err
}
}
return nil
}
func (f *Framer ) WriteSettings (settings ...Setting ) error {
f .startWrite (FrameSettings , 0 , 0 )
for _ , s := range settings {
f .writeUint16 (uint16 (s .ID ))
f .writeUint32 (s .Val )
}
return f .endWrite ()
}
func (f *Framer ) WriteSettingsAck () error {
f .startWrite (FrameSettings , FlagSettingsAck , 0 )
return f .endWrite ()
}
type PingFrame struct {
FrameHeader
Data [8 ]byte
}
func (f *PingFrame ) IsAck () bool { return f .Flags .Has (FlagPingAck ) }
func parsePingFrame(_ *frameCache , fh FrameHeader , countError func (string ), payload []byte ) (Frame , error ) {
if len (payload ) != 8 {
countError ("frame_ping_length" )
return nil , ConnectionError (ErrCodeFrameSize )
}
if fh .StreamID != 0 {
countError ("frame_ping_has_stream" )
return nil , ConnectionError (ErrCodeProtocol )
}
f := &PingFrame {FrameHeader : fh }
copy (f .Data [:], payload )
return f , nil
}
func (f *Framer ) WritePing (ack bool , data [8 ]byte ) error {
var flags Flags
if ack {
flags = FlagPingAck
}
f .startWrite (FramePing , flags , 0 )
f .writeBytes (data [:])
return f .endWrite ()
}
type GoAwayFrame struct {
FrameHeader
LastStreamID uint32
ErrCode ErrCode
debugData []byte
}
func (f *GoAwayFrame ) DebugData () []byte {
f .checkValid ()
return f .debugData
}
func parseGoAwayFrame(_ *frameCache , fh FrameHeader , countError func (string ), p []byte ) (Frame , error ) {
if fh .StreamID != 0 {
countError ("frame_goaway_has_stream" )
return nil , ConnectionError (ErrCodeProtocol )
}
if len (p ) < 8 {
countError ("frame_goaway_short" )
return nil , ConnectionError (ErrCodeFrameSize )
}
return &GoAwayFrame {
FrameHeader : fh ,
LastStreamID : binary .BigEndian .Uint32 (p [:4 ]) & (1 <<31 - 1 ),
ErrCode : ErrCode (binary .BigEndian .Uint32 (p [4 :8 ])),
debugData : p [8 :],
}, nil
}
func (f *Framer ) WriteGoAway (maxStreamID uint32 , code ErrCode , debugData []byte ) error {
f .startWrite (FrameGoAway , 0 , 0 )
f .writeUint32 (maxStreamID & (1 <<31 - 1 ))
f .writeUint32 (uint32 (code ))
f .writeBytes (debugData )
return f .endWrite ()
}
type UnknownFrame struct {
FrameHeader
p []byte
}
func (f *UnknownFrame ) Payload () []byte {
f .checkValid ()
return f .p
}
func parseUnknownFrame(_ *frameCache , fh FrameHeader , countError func (string ), p []byte ) (Frame , error ) {
return &UnknownFrame {fh , p }, nil
}
type WindowUpdateFrame struct {
FrameHeader
Increment uint32
}
func parseWindowUpdateFrame(_ *frameCache , fh FrameHeader , countError func (string ), p []byte ) (Frame , error ) {
if len (p ) != 4 {
countError ("frame_windowupdate_bad_len" )
return nil , ConnectionError (ErrCodeFrameSize )
}
inc := binary .BigEndian .Uint32 (p [:4 ]) & 0x7fffffff
if inc == 0 {
if fh .StreamID == 0 {
countError ("frame_windowupdate_zero_inc_conn" )
return nil , ConnectionError (ErrCodeProtocol )
}
countError ("frame_windowupdate_zero_inc_stream" )
return nil , streamError (fh .StreamID , ErrCodeProtocol )
}
return &WindowUpdateFrame {
FrameHeader : fh ,
Increment : inc ,
}, nil
}
func (f *Framer ) WriteWindowUpdate (streamID , incr uint32 ) error {
if (incr < 1 || incr > 2147483647 ) && !f .AllowIllegalWrites {
return errors .New ("illegal window increment value" )
}
f .startWrite (FrameWindowUpdate , 0 , streamID )
f .writeUint32 (incr )
return f .endWrite ()
}
type HeadersFrame struct {
FrameHeader
Priority PriorityParam
headerFragBuf []byte
}
func (f *HeadersFrame ) HeaderBlockFragment () []byte {
f .checkValid ()
return f .headerFragBuf
}
func (f *HeadersFrame ) HeadersEnded () bool {
return f .FrameHeader .Flags .Has (FlagHeadersEndHeaders )
}
func (f *HeadersFrame ) StreamEnded () bool {
return f .FrameHeader .Flags .Has (FlagHeadersEndStream )
}
func (f *HeadersFrame ) HasPriority () bool {
return f .FrameHeader .Flags .Has (FlagHeadersPriority )
}
func parseHeadersFrame(_ *frameCache , fh FrameHeader , countError func (string ), p []byte ) (_ Frame , err error ) {
hf := &HeadersFrame {
FrameHeader : fh ,
}
if fh .StreamID == 0 {
countError ("frame_headers_zero_stream" )
return nil , connError {ErrCodeProtocol , "HEADERS frame with stream ID 0" }
}
var padLength uint8
if fh .Flags .Has (FlagHeadersPadded ) {
if p , padLength , err = readByte (p ); err != nil {
countError ("frame_headers_pad_short" )
return
}
}
if fh .Flags .Has (FlagHeadersPriority ) {
var v uint32
p , v , err = readUint32 (p )
if err != nil {
countError ("frame_headers_prio_short" )
return nil , err
}
hf .Priority .StreamDep = v & 0x7fffffff
hf .Priority .Exclusive = (v != hf .Priority .StreamDep )
p , hf .Priority .Weight , err = readByte (p )
if err != nil {
countError ("frame_headers_prio_weight_short" )
return nil , err
}
}
if len (p )-int (padLength ) < 0 {
countError ("frame_headers_pad_too_big" )
return nil , streamError (fh .StreamID , ErrCodeProtocol )
}
hf .headerFragBuf = p [:len (p )-int (padLength )]
return hf , nil
}
type HeadersFrameParam struct {
StreamID uint32
BlockFragment []byte
EndStream bool
EndHeaders bool
PadLength uint8
Priority PriorityParam
}
func (f *Framer ) WriteHeaders (p HeadersFrameParam ) error {
if !validStreamID (p .StreamID ) && !f .AllowIllegalWrites {
return errStreamID
}
var flags Flags
if p .PadLength != 0 {
flags |= FlagHeadersPadded
}
if p .EndStream {
flags |= FlagHeadersEndStream
}
if p .EndHeaders {
flags |= FlagHeadersEndHeaders
}
if !p .Priority .IsZero () {
flags |= FlagHeadersPriority
}
f .startWrite (FrameHeaders , flags , p .StreamID )
if p .PadLength != 0 {
f .writeByte (p .PadLength )
}
if !p .Priority .IsZero () {
v := p .Priority .StreamDep
if !validStreamIDOrZero (v ) && !f .AllowIllegalWrites {
return errDepStreamID
}
if p .Priority .Exclusive {
v |= 1 << 31
}
f .writeUint32 (v )
f .writeByte (p .Priority .Weight )
}
f .wbuf = append (f .wbuf , p .BlockFragment ...)
f .wbuf = append (f .wbuf , padZeros [:p .PadLength ]...)
return f .endWrite ()
}
type PriorityFrame struct {
FrameHeader
PriorityParam
}
var defaultRFC9218Priority = PriorityParam {
incremental : 0 ,
urgency : 3 ,
}
type PriorityParam struct {
StreamDep uint32
Exclusive bool
Weight uint8
urgency uint8
incremental uint8
}
func (p PriorityParam ) IsZero () bool {
return p == PriorityParam {}
}
func parsePriorityFrame(_ *frameCache , fh FrameHeader , countError func (string ), payload []byte ) (Frame , error ) {
if fh .StreamID == 0 {
countError ("frame_priority_zero_stream" )
return nil , connError {ErrCodeProtocol , "PRIORITY frame with stream ID 0" }
}
if len (payload ) != 5 {
countError ("frame_priority_bad_length" )
return nil , connError {ErrCodeFrameSize , fmt .Sprintf ("PRIORITY frame payload size was %d; want 5" , len (payload ))}
}
v := binary .BigEndian .Uint32 (payload [:4 ])
streamID := v & 0x7fffffff
return &PriorityFrame {
FrameHeader : fh ,
PriorityParam : PriorityParam {
Weight : payload [4 ],
StreamDep : streamID ,
Exclusive : streamID != v ,
},
}, nil
}
func (f *Framer ) WritePriority (streamID uint32 , p PriorityParam ) error {
if !validStreamID (streamID ) && !f .AllowIllegalWrites {
return errStreamID
}
if !validStreamIDOrZero (p .StreamDep ) {
return errDepStreamID
}
f .startWrite (FramePriority , 0 , streamID )
v := p .StreamDep
if p .Exclusive {
v |= 1 << 31
}
f .writeUint32 (v )
f .writeByte (p .Weight )
return f .endWrite ()
}
type RSTStreamFrame struct {
FrameHeader
ErrCode ErrCode
}
func parseRSTStreamFrame(_ *frameCache , fh FrameHeader , countError func (string ), p []byte ) (Frame , error ) {
if len (p ) != 4 {
countError ("frame_rststream_bad_len" )
return nil , ConnectionError (ErrCodeFrameSize )
}
if fh .StreamID == 0 {
countError ("frame_rststream_zero_stream" )
return nil , ConnectionError (ErrCodeProtocol )
}
return &RSTStreamFrame {fh , ErrCode (binary .BigEndian .Uint32 (p [:4 ]))}, nil
}
func (f *Framer ) WriteRSTStream (streamID uint32 , code ErrCode ) error {
if !validStreamID (streamID ) && !f .AllowIllegalWrites {
return errStreamID
}
f .startWrite (FrameRSTStream , 0 , streamID )
f .writeUint32 (uint32 (code ))
return f .endWrite ()
}
type ContinuationFrame struct {
FrameHeader
headerFragBuf []byte
}
func parseContinuationFrame(_ *frameCache , fh FrameHeader , countError func (string ), p []byte ) (Frame , error ) {
if fh .StreamID == 0 {
countError ("frame_continuation_zero_stream" )
return nil , connError {ErrCodeProtocol , "CONTINUATION frame with stream ID 0" }
}
return &ContinuationFrame {fh , p }, nil
}
func (f *ContinuationFrame ) HeaderBlockFragment () []byte {
f .checkValid ()
return f .headerFragBuf
}
func (f *ContinuationFrame ) HeadersEnded () bool {
return f .FrameHeader .Flags .Has (FlagContinuationEndHeaders )
}
func (f *Framer ) WriteContinuation (streamID uint32 , endHeaders bool , headerBlockFragment []byte ) error {
if !validStreamID (streamID ) && !f .AllowIllegalWrites {
return errStreamID
}
var flags Flags
if endHeaders {
flags |= FlagContinuationEndHeaders
}
f .startWrite (FrameContinuation , flags , streamID )
f .wbuf = append (f .wbuf , headerBlockFragment ...)
return f .endWrite ()
}
type PushPromiseFrame struct {
FrameHeader
PromiseID uint32
headerFragBuf []byte
}
func (f *PushPromiseFrame ) HeaderBlockFragment () []byte {
f .checkValid ()
return f .headerFragBuf
}
func (f *PushPromiseFrame ) HeadersEnded () bool {
return f .FrameHeader .Flags .Has (FlagPushPromiseEndHeaders )
}
func parsePushPromise(_ *frameCache , fh FrameHeader , countError func (string ), p []byte ) (_ Frame , err error ) {
pp := &PushPromiseFrame {
FrameHeader : fh ,
}
if pp .StreamID == 0 {
countError ("frame_pushpromise_zero_stream" )
return nil , ConnectionError (ErrCodeProtocol )
}
var padLength uint8
if fh .Flags .Has (FlagPushPromisePadded ) {
if p , padLength , err = readByte (p ); err != nil {
countError ("frame_pushpromise_pad_short" )
return
}
}
p , pp .PromiseID , err = readUint32 (p )
if err != nil {
countError ("frame_pushpromise_promiseid_short" )
return
}
pp .PromiseID = pp .PromiseID & (1 <<31 - 1 )
if int (padLength ) > len (p ) {
countError ("frame_pushpromise_pad_too_big" )
return nil , ConnectionError (ErrCodeProtocol )
}
pp .headerFragBuf = p [:len (p )-int (padLength )]
return pp , nil
}
type PushPromiseParam struct {
StreamID uint32
PromiseID uint32
BlockFragment []byte
EndHeaders bool
PadLength uint8
}
func (f *Framer ) WritePushPromise (p PushPromiseParam ) error {
if !validStreamID (p .StreamID ) && !f .AllowIllegalWrites {
return errStreamID
}
var flags Flags
if p .PadLength != 0 {
flags |= FlagPushPromisePadded
}
if p .EndHeaders {
flags |= FlagPushPromiseEndHeaders
}
f .startWrite (FramePushPromise , flags , p .StreamID )
if p .PadLength != 0 {
f .writeByte (p .PadLength )
}
if !validStreamID (p .PromiseID ) && !f .AllowIllegalWrites {
return errStreamID
}
f .writeUint32 (p .PromiseID )
f .wbuf = append (f .wbuf , p .BlockFragment ...)
f .wbuf = append (f .wbuf , padZeros [:p .PadLength ]...)
return f .endWrite ()
}
func (f *Framer ) WriteRawFrame (t FrameType , flags Flags , streamID uint32 , payload []byte ) error {
f .startWrite (t , flags , streamID )
f .writeBytes (payload )
return f .endWrite ()
}
func readByte(p []byte ) (remain []byte , b byte , err error ) {
if len (p ) == 0 {
return nil , 0 , io .ErrUnexpectedEOF
}
return p [1 :], p [0 ], nil
}
func readUint32(p []byte ) (remain []byte , v uint32 , err error ) {
if len (p ) < 4 {
return nil , 0 , io .ErrUnexpectedEOF
}
return p [4 :], binary .BigEndian .Uint32 (p [:4 ]), nil
}
type streamEnder interface {
StreamEnded() bool
}
type headersEnder interface {
HeadersEnded() bool
}
type headersOrContinuation interface {
headersEnder
HeaderBlockFragment() []byte
}
type MetaHeadersFrame struct {
*HeadersFrame
Fields []hpack .HeaderField
Truncated bool
}
func (mh *MetaHeadersFrame ) PseudoValue (pseudo string ) string {
for _ , hf := range mh .Fields {
if !hf .IsPseudo () {
return ""
}
if hf .Name [1 :] == pseudo {
return hf .Value
}
}
return ""
}
func (mh *MetaHeadersFrame ) RegularFields () []hpack .HeaderField {
for i , hf := range mh .Fields {
if !hf .IsPseudo () {
return mh .Fields [i :]
}
}
return nil
}
func (mh *MetaHeadersFrame ) PseudoFields () []hpack .HeaderField {
for i , hf := range mh .Fields {
if !hf .IsPseudo () {
return mh .Fields [:i ]
}
}
return mh .Fields
}
func (mh *MetaHeadersFrame ) checkPseudos () error {
var isRequest , isResponse bool
pf := mh .PseudoFields ()
for i , hf := range pf {
switch hf .Name {
case ":method" , ":path" , ":scheme" , ":authority" , ":protocol" :
isRequest = true
case ":status" :
isResponse = true
default :
return pseudoHeaderError (hf .Name )
}
for _ , hf2 := range pf [:i ] {
if hf .Name == hf2 .Name {
return duplicatePseudoHeaderError (hf .Name )
}
}
}
if isRequest && isResponse {
return errMixPseudoHeaderTypes
}
return nil
}
func (fr *Framer ) maxHeaderStringLen () int {
v := int (fr .maxHeaderListSize ())
if v < 0 {
return 0
}
return v
}
func (fr *Framer ) readMetaFrame (hf *HeadersFrame ) (Frame , error ) {
if fr .AllowIllegalReads {
return nil , errors .New ("illegal use of AllowIllegalReads with ReadMetaHeaders" )
}
mh := &MetaHeadersFrame {
HeadersFrame : hf ,
}
var remainSize = fr .maxHeaderListSize ()
var sawRegular bool
var invalid error
hdec := fr .ReadMetaHeaders
hdec .SetEmitEnabled (true )
hdec .SetMaxStringLength (fr .maxHeaderStringLen ())
hdec .SetEmitFunc (func (hf hpack .HeaderField ) {
if VerboseLogs && fr .logReads {
fr .debugReadLoggerf ("http2: decoded hpack field %+v" , hf )
}
if !httpguts .ValidHeaderFieldValue (hf .Value ) {
invalid = headerFieldValueError (hf .Name )
}
isPseudo := strings .HasPrefix (hf .Name , ":" )
if isPseudo {
if sawRegular {
invalid = errPseudoAfterRegular
}
} else {
sawRegular = true
if !validWireHeaderFieldName (hf .Name ) {
invalid = headerFieldNameError (hf .Name )
}
}
if invalid != nil {
hdec .SetEmitEnabled (false )
return
}
size := hf .Size ()
if size > remainSize {
hdec .SetEmitEnabled (false )
mh .Truncated = true
remainSize = 0
return
}
remainSize -= size
mh .Fields = append (mh .Fields , hf )
})
defer hdec .SetEmitFunc (func (hf hpack .HeaderField ) {})
var hc headersOrContinuation = hf
for {
frag := hc .HeaderBlockFragment ()
if int64 (len (frag )) > int64 (2 *remainSize ) {
if VerboseLogs {
log .Printf ("http2: header list too large" )
}
return mh , ConnectionError (ErrCodeProtocol )
}
if invalid != nil {
if VerboseLogs {
log .Printf ("http2: invalid header: %v" , invalid )
}
return mh , ConnectionError (ErrCodeProtocol )
}
if _ , err := hdec .Write (frag ); err != nil {
return mh , ConnectionError (ErrCodeCompression )
}
if hc .HeadersEnded () {
break
}
if f , err := fr .ReadFrame (); err != nil {
return nil , err
} else {
hc = f .(*ContinuationFrame )
}
}
mh .HeadersFrame .headerFragBuf = nil
mh .HeadersFrame .invalidate ()
if err := hdec .Close (); err != nil {
return mh , ConnectionError (ErrCodeCompression )
}
if invalid != nil {
fr .errDetail = invalid
if VerboseLogs {
log .Printf ("http2: invalid header: %v" , invalid )
}
return nil , StreamError {mh .StreamID , ErrCodeProtocol , invalid }
}
if err := mh .checkPseudos (); err != nil {
fr .errDetail = err
if VerboseLogs {
log .Printf ("http2: invalid pseudo headers: %v" , err )
}
return nil , StreamError {mh .StreamID , ErrCodeProtocol , err }
}
return mh , nil
}
func summarizeFrame(f Frame ) string {
var buf bytes .Buffer
f .Header ().writeDebug (&buf )
switch f := f .(type ) {
case *SettingsFrame :
n := 0
f .ForeachSetting (func (s Setting ) error {
n ++
if n == 1 {
buf .WriteString (", settings:" )
}
fmt .Fprintf (&buf , " %v=%v," , s .ID , s .Val )
return nil
})
if n > 0 {
buf .Truncate (buf .Len () - 1 )
}
case *DataFrame :
data := f .Data ()
const max = 256
if len (data ) > max {
data = data [:max ]
}
fmt .Fprintf (&buf , " data=%q" , data )
if len (f .Data ()) > max {
fmt .Fprintf (&buf , " (%d bytes omitted)" , len (f .Data ())-max )
}
case *WindowUpdateFrame :
if f .StreamID == 0 {
buf .WriteString (" (conn)" )
}
fmt .Fprintf (&buf , " incr=%v" , f .Increment )
case *PingFrame :
fmt .Fprintf (&buf , " ping=%q" , f .Data [:])
case *GoAwayFrame :
fmt .Fprintf (&buf , " LastStreamID=%v ErrCode=%v Debug=%q" ,
f .LastStreamID , f .ErrCode , f .debugData )
case *RSTStreamFrame :
fmt .Fprintf (&buf , " ErrCode=%v" , f .ErrCode )
}
return buf .String ()
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .