|
|
|
package http2interop
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/binary"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
)
|
|
|
|
|
|
|
|
type FrameHeader struct {
|
|
|
|
Length int
|
|
|
|
Type FrameType
|
|
|
|
Flags byte
|
|
|
|
Reserved Reserved
|
|
|
|
StreamID
|
|
|
|
}
|
|
|
|
|
|
|
|
type Reserved bool
|
|
|
|
|
|
|
|
func (r Reserved) String() string {
|
|
|
|
if r {
|
|
|
|
return "R"
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fh *FrameHeader) Parse(r io.Reader) error {
|
|
|
|
buf := make([]byte, 9)
|
|
|
|
if _, err := io.ReadFull(r, buf); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return fh.UnmarshalBinary(buf)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fh *FrameHeader) UnmarshalBinary(b []byte) error {
|
|
|
|
if len(b) != 9 {
|
|
|
|
return fmt.Errorf("Invalid frame header length %d", len(b))
|
|
|
|
}
|
|
|
|
*fh = FrameHeader{
|
|
|
|
Length: int(b[0])<<16 | int(b[1])<<8 | int(b[2]),
|
|
|
|
Type: FrameType(b[3]),
|
|
|
|
Flags: b[4],
|
|
|
|
Reserved: Reserved(b[5]>>7 == 1),
|
|
|
|
StreamID: StreamID(binary.BigEndian.Uint32(b[5:9]) & 0x7fffffff),
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fh *FrameHeader) MarshalBinary() ([]byte, error) {
|
|
|
|
buf := make([]byte, 9, 9+fh.Length)
|
|
|
|
|
|
|
|
if fh.Length > 0xFFFFFF || fh.Length < 0 {
|
|
|
|
return nil, fmt.Errorf("Invalid frame header length: %d", fh.Length)
|
|
|
|
}
|
|
|
|
if fh.StreamID < 0 {
|
|
|
|
return nil, fmt.Errorf("Invalid Stream ID: %v", fh.StreamID)
|
|
|
|
}
|
|
|
|
|
|
|
|
buf[0], buf[1], buf[2] = byte(fh.Length>>16), byte(fh.Length>>8), byte(fh.Length)
|
|
|
|
buf[3] = byte(fh.Type)
|
|
|
|
buf[4] = fh.Flags
|
|
|
|
var res uint32
|
|
|
|
if fh.Reserved {
|
|
|
|
res = 0x80000000
|
|
|
|
}
|
|
|
|
binary.BigEndian.PutUint32(buf[5:], uint32(fh.StreamID)|res)
|
|
|
|
|
|
|
|
return buf, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type StreamID int32
|
|
|
|
|
|
|
|
type FrameType byte
|
|
|
|
|
|
|
|
func (ft FrameType) String() string {
|
|
|
|
switch ft {
|
|
|
|
case DataFrameType:
|
|
|
|
return "DATA"
|
|
|
|
case HeadersFrameType:
|
|
|
|
return "HEADERS"
|
|
|
|
case PriorityFrameType:
|
|
|
|
return "PRIORITY"
|
|
|
|
case ResetStreamFrameType:
|
|
|
|
return "RST_STREAM"
|
|
|
|
case SettingsFrameType:
|
|
|
|
return "SETTINGS"
|
|
|
|
case PushPromiseFrameType:
|
|
|
|
return "PUSH_PROMISE"
|
|
|
|
case PingFrameType:
|
|
|
|
return "PING"
|
|
|
|
case GoAwayFrameType:
|
|
|
|
return "GOAWAY"
|
|
|
|
case WindowUpdateFrameType:
|
|
|
|
return "WINDOW_UPDATE"
|
|
|
|
case ContinuationFrameType:
|
|
|
|
return "CONTINUATION"
|
|
|
|
case HTTP1FrameType:
|
|
|
|
return "HTTP/1.? (Bad)"
|
|
|
|
default:
|
|
|
|
return fmt.Sprintf("UNKNOWN(%d)", byte(ft))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Types
|
|
|
|
const (
|
|
|
|
DataFrameType FrameType = 0
|
|
|
|
HeadersFrameType FrameType = 1
|
|
|
|
PriorityFrameType FrameType = 2
|
|
|
|
ResetStreamFrameType FrameType = 3
|
|
|
|
SettingsFrameType FrameType = 4
|
|
|
|
PushPromiseFrameType FrameType = 5
|
|
|
|
PingFrameType FrameType = 6
|
|
|
|
GoAwayFrameType FrameType = 7
|
|
|
|
WindowUpdateFrameType FrameType = 8
|
|
|
|
ContinuationFrameType FrameType = 9
|
|
|
|
|
|
|
|
// HTTP1FrameType is not a real type, but rather a convenient way to check if the response
|
|
|
|
// is an http response. The type of a frame header is the 4th byte, which in an http1
|
|
|
|
// response will be "HTTP/1.1 200 OK" or something like that. The character for "P" is 80.
|
|
|
|
HTTP1FrameType FrameType = 80
|
|
|
|
)
|