mirror of https://github.com/grpc/grpc.git
commit
bf3c463cb4
9 changed files with 658 additions and 0 deletions
@ -0,0 +1,9 @@ |
||||
HTTP/2 Interop Tests |
||||
==== |
||||
|
||||
This is a suite of tests that check a server to see if it plays nicely with other HTTP/2 clients. To run, just type: |
||||
|
||||
`go test -spec :1234` |
||||
|
||||
Where ":1234" is the ip:port of a running server. |
||||
|
@ -0,0 +1,6 @@ |
||||
// http2interop project doc.go
|
||||
|
||||
/* |
||||
http2interop document |
||||
*/ |
||||
package http2interop |
@ -0,0 +1,11 @@ |
||||
package http2interop |
||||
|
||||
import ( |
||||
"io" |
||||
) |
||||
|
||||
type Frame interface { |
||||
GetHeader() *FrameHeader |
||||
ParsePayload(io.Reader) error |
||||
MarshalBinary() ([]byte, error) |
||||
} |
@ -0,0 +1,109 @@ |
||||
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 |
||||
binary.BigEndian.PutUint32(buf[5:], uint32(fh.StreamID)) |
||||
|
||||
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" |
||||
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 |
||||
) |
@ -0,0 +1,245 @@ |
||||
package http2interop |
||||
|
||||
import ( |
||||
"crypto/tls" |
||||
"fmt" |
||||
"io" |
||||
"log" |
||||
) |
||||
|
||||
const ( |
||||
Preface = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" |
||||
) |
||||
|
||||
func parseFrame(r io.Reader) (Frame, error) { |
||||
fh := FrameHeader{} |
||||
if err := fh.Parse(r); err != nil { |
||||
return nil, err |
||||
} |
||||
var f Frame |
||||
switch fh.Type { |
||||
case PingFrameType: |
||||
f = &PingFrame{ |
||||
Header: fh, |
||||
} |
||||
case SettingsFrameType: |
||||
f = &SettingsFrame{ |
||||
Header: fh, |
||||
} |
||||
default: |
||||
f = &UnknownFrame{ |
||||
Header: fh, |
||||
} |
||||
} |
||||
if err := f.ParsePayload(r); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return f, nil |
||||
} |
||||
|
||||
func streamFrame(w io.Writer, f Frame) error { |
||||
raw, err := f.MarshalBinary() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if _, err := w.Write(raw); err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func getHttp2Conn(addr string) (*tls.Conn, error) { |
||||
config := &tls.Config{ |
||||
InsecureSkipVerify: true, |
||||
NextProtos: []string{"h2"}, |
||||
} |
||||
|
||||
conn, err := tls.Dial("tcp", addr, config) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return conn, nil |
||||
} |
||||
|
||||
func testClientShortSettings(addr string, length int) error { |
||||
c, err := getHttp2Conn(addr) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer c.Close() |
||||
|
||||
if _, err := c.Write([]byte(Preface)); err != nil { |
||||
return err |
||||
} |
||||
|
||||
// Bad, settings, non multiple of 6
|
||||
sf := &UnknownFrame{ |
||||
Header: FrameHeader{ |
||||
Type: SettingsFrameType, |
||||
}, |
||||
Data: make([]byte, length), |
||||
} |
||||
if err := streamFrame(c, sf); err != nil { |
||||
return err |
||||
} |
||||
|
||||
for { |
||||
frame, err := parseFrame(c) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
log.Println(frame) |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func testClientPrefaceWithStreamId(addr string) error { |
||||
c, err := getHttp2Conn(addr) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer c.Close() |
||||
|
||||
// Good so far
|
||||
if _, err := c.Write([]byte(Preface)); err != nil { |
||||
return err |
||||
} |
||||
|
||||
// Bad, settings do not have ids
|
||||
sf := &SettingsFrame{ |
||||
Header: FrameHeader{ |
||||
StreamID: 1, |
||||
}, |
||||
} |
||||
if err := streamFrame(c, sf); err != nil { |
||||
return err |
||||
} |
||||
|
||||
for { |
||||
frame, err := parseFrame(c) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
log.Println(frame) |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func testUnknownFrameType(addr string) error { |
||||
c, err := getHttp2Conn(addr) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer c.Close() |
||||
|
||||
if _, err := c.Write([]byte(Preface)); err != nil { |
||||
return err |
||||
} |
||||
|
||||
// Send some settings, which are part of the client preface
|
||||
sf := &SettingsFrame{} |
||||
if err := streamFrame(c, sf); err != nil { |
||||
return err |
||||
} |
||||
|
||||
// Write a bunch of invalid frame types.
|
||||
for ft := ContinuationFrameType + 1; ft != 0; ft++ { |
||||
fh := &UnknownFrame{ |
||||
Header: FrameHeader{ |
||||
Type: ft, |
||||
}, |
||||
} |
||||
if err := streamFrame(c, fh); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
pf := &PingFrame{ |
||||
Data: []byte("01234567"), |
||||
} |
||||
if err := streamFrame(c, pf); err != nil { |
||||
return err |
||||
} |
||||
|
||||
for { |
||||
frame, err := parseFrame(c) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if npf, ok := frame.(*PingFrame); !ok { |
||||
continue |
||||
} else { |
||||
if string(npf.Data) != string(pf.Data) || npf.Header.Flags&PING_ACK == 0 { |
||||
return fmt.Errorf("Bad ping %+v", *npf) |
||||
} |
||||
return nil |
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func testShortPreface(addr string, prefacePrefix string) error { |
||||
c, err := getHttp2Conn(addr) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer c.Close() |
||||
|
||||
if _, err := c.Write([]byte(prefacePrefix)); err != nil { |
||||
return err |
||||
} |
||||
|
||||
buf := make([]byte, 256) |
||||
for ; err == nil; _, err = c.Read(buf) { |
||||
} |
||||
// TODO: maybe check for a GOAWAY?
|
||||
return err |
||||
} |
||||
|
||||
func testTLSMaxVersion(addr string, version uint16) error { |
||||
config := &tls.Config{ |
||||
InsecureSkipVerify: true, |
||||
NextProtos: []string{"h2"}, |
||||
MaxVersion: version, |
||||
} |
||||
conn, err := tls.Dial("tcp", addr, config) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer conn.Close() |
||||
|
||||
buf := make([]byte, 256) |
||||
if n, err := conn.Read(buf); err != nil { |
||||
if n != 0 { |
||||
return fmt.Errorf("Expected no bytes to be read, but was %d", n) |
||||
} |
||||
return err |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func testTLSApplicationProtocol(addr string) error { |
||||
config := &tls.Config{ |
||||
InsecureSkipVerify: true, |
||||
NextProtos: []string{"h2c"}, |
||||
} |
||||
conn, err := tls.Dial("tcp", addr, config) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer conn.Close() |
||||
|
||||
buf := make([]byte, 256) |
||||
if n, err := conn.Read(buf); err != nil { |
||||
if n != 0 { |
||||
return fmt.Errorf("Expected no bytes to be read, but was %d", n) |
||||
} |
||||
return err |
||||
} |
||||
return nil |
||||
} |
@ -0,0 +1,50 @@ |
||||
package http2interop |
||||
|
||||
import ( |
||||
"crypto/tls" |
||||
"flag" |
||||
"io" |
||||
"os" |
||||
"testing" |
||||
) |
||||
|
||||
var ( |
||||
serverSpec = flag.String("spec", ":50051", "The server spec to test") |
||||
) |
||||
|
||||
func TestShortPreface(t *testing.T) { |
||||
for i := 0; i < len(Preface)-1; i++ { |
||||
if err := testShortPreface(*serverSpec, Preface[:i]+"X"); err != io.EOF { |
||||
t.Error("Expected an EOF but was", err) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestUnknownFrameType(t *testing.T) { |
||||
if err := testUnknownFrameType(*serverSpec); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
} |
||||
|
||||
func TestTLSApplicationProtocol(t *testing.T) { |
||||
if err := testTLSApplicationProtocol(*serverSpec); err != io.EOF { |
||||
t.Fatal("Expected an EOF but was", err) |
||||
} |
||||
} |
||||
|
||||
func TestTLSMaxVersion(t *testing.T) { |
||||
if err := testTLSMaxVersion(*serverSpec, tls.VersionTLS11); err != io.EOF { |
||||
t.Fatal("Expected an EOF but was", err) |
||||
} |
||||
} |
||||
|
||||
func TestClientPrefaceWithStreamId(t *testing.T) { |
||||
if err := testClientPrefaceWithStreamId(*serverSpec); err != io.EOF { |
||||
t.Fatal("Expected an EOF but was", err) |
||||
} |
||||
} |
||||
|
||||
func TestMain(m *testing.M) { |
||||
flag.Parse() |
||||
os.Exit(m.Run()) |
||||
} |
@ -0,0 +1,65 @@ |
||||
package http2interop |
||||
|
||||
import ( |
||||
"fmt" |
||||
"io" |
||||
) |
||||
|
||||
type PingFrame struct { |
||||
Header FrameHeader |
||||
Data []byte |
||||
} |
||||
|
||||
const ( |
||||
PING_ACK = 0x01 |
||||
) |
||||
|
||||
func (f *PingFrame) GetHeader() *FrameHeader { |
||||
return &f.Header |
||||
} |
||||
|
||||
func (f *PingFrame) ParsePayload(r io.Reader) error { |
||||
raw := make([]byte, f.Header.Length) |
||||
if _, err := io.ReadFull(r, raw); err != nil { |
||||
return err |
||||
} |
||||
return f.UnmarshalPayload(raw) |
||||
} |
||||
|
||||
func (f *PingFrame) UnmarshalPayload(raw []byte) error { |
||||
if f.Header.Length != len(raw) { |
||||
return fmt.Errorf("Invalid Payload length %d != %d", f.Header.Length, len(raw)) |
||||
} |
||||
if f.Header.Length != 8 { |
||||
return fmt.Errorf("Invalid Payload length %d", f.Header.Length) |
||||
} |
||||
|
||||
f.Data = []byte(string(raw)) |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func (f *PingFrame) MarshalPayload() ([]byte, error) { |
||||
if len(f.Data) != 8 { |
||||
return nil, fmt.Errorf("Invalid Payload length %d", len(f.Data)) |
||||
} |
||||
return []byte(string(f.Data)), nil |
||||
} |
||||
|
||||
func (f *PingFrame) MarshalBinary() ([]byte, error) { |
||||
payload, err := f.MarshalPayload() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
f.Header.Length = len(payload) |
||||
f.Header.Type = PingFrameType |
||||
header, err := f.Header.MarshalBinary() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
header = append(header, payload...) |
||||
|
||||
return header, nil |
||||
} |
@ -0,0 +1,109 @@ |
||||
package http2interop |
||||
|
||||
import ( |
||||
"encoding/binary" |
||||
"fmt" |
||||
"io" |
||||
) |
||||
|
||||
const ( |
||||
SETTINGS_ACK = 1 |
||||
) |
||||
|
||||
type SettingsFrame struct { |
||||
Header FrameHeader |
||||
Params []SettingsParameter |
||||
} |
||||
|
||||
type SettingsIdentifier uint16 |
||||
|
||||
const ( |
||||
SettingsHeaderTableSize SettingsIdentifier = 1 |
||||
SettingsEnablePush SettingsIdentifier = 2 |
||||
SettingsMaxConcurrentStreams SettingsIdentifier = 3 |
||||
SettingsInitialWindowSize SettingsIdentifier = 4 |
||||
SettingsMaxFrameSize SettingsIdentifier = 5 |
||||
SettingsMaxHeaderListSize SettingsIdentifier = 6 |
||||
) |
||||
|
||||
func (si SettingsIdentifier) String() string { |
||||
switch si { |
||||
case SettingsHeaderTableSize: |
||||
return "HEADER_TABLE_SIZE" |
||||
case SettingsEnablePush: |
||||
return "ENABLE_PUSH" |
||||
case SettingsMaxConcurrentStreams: |
||||
return "MAX_CONCURRENT_STREAMS" |
||||
case SettingsInitialWindowSize: |
||||
return "INITIAL_WINDOW_SIZE" |
||||
case SettingsMaxFrameSize: |
||||
return "MAX_FRAME_SIZE" |
||||
case SettingsMaxHeaderListSize: |
||||
return "MAX_HEADER_LIST_SIZE" |
||||
default: |
||||
return fmt.Sprintf("UNKNOWN(%d)", uint16(si)) |
||||
} |
||||
} |
||||
|
||||
type SettingsParameter struct { |
||||
Identifier SettingsIdentifier |
||||
Value uint32 |
||||
} |
||||
|
||||
func (f *SettingsFrame) GetHeader() *FrameHeader { |
||||
return &f.Header |
||||
} |
||||
|
||||
func (f *SettingsFrame) ParsePayload(r io.Reader) error { |
||||
raw := make([]byte, f.Header.Length) |
||||
if _, err := io.ReadFull(r, raw); err != nil { |
||||
return err |
||||
} |
||||
return f.UnmarshalPayload(raw) |
||||
} |
||||
|
||||
func (f *SettingsFrame) UnmarshalPayload(raw []byte) error { |
||||
if f.Header.Length != len(raw) { |
||||
return fmt.Errorf("Invalid Payload length %d != %d", f.Header.Length, len(raw)) |
||||
} |
||||
|
||||
if f.Header.Length%6 != 0 { |
||||
return fmt.Errorf("Invalid Payload length %d", f.Header.Length) |
||||
} |
||||
|
||||
f.Params = make([]SettingsParameter, 0, f.Header.Length/6) |
||||
for i := 0; i < len(raw); i += 6 { |
||||
f.Params = append(f.Params, SettingsParameter{ |
||||
Identifier: SettingsIdentifier(binary.BigEndian.Uint16(raw[i : i+2])), |
||||
Value: binary.BigEndian.Uint32(raw[i+2 : i+6]), |
||||
}) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (f *SettingsFrame) MarshalPayload() ([]byte, error) { |
||||
raw := make([]byte, 0, len(f.Params)*6) |
||||
for i, p := range f.Params { |
||||
binary.BigEndian.PutUint16(raw[i*6:i*6+2], uint16(p.Identifier)) |
||||
binary.BigEndian.PutUint32(raw[i*6+2:i*6+6], p.Value) |
||||
} |
||||
return raw, nil |
||||
} |
||||
|
||||
func (f *SettingsFrame) MarshalBinary() ([]byte, error) { |
||||
payload, err := f.MarshalPayload() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
f.Header.Length = len(payload) |
||||
f.Header.Type = SettingsFrameType |
||||
header, err := f.Header.MarshalBinary() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
header = append(header, payload...) |
||||
|
||||
return header, nil |
||||
} |
@ -0,0 +1,54 @@ |
||||
package http2interop |
||||
|
||||
import ( |
||||
"fmt" |
||||
"io" |
||||
) |
||||
|
||||
type UnknownFrame struct { |
||||
Header FrameHeader |
||||
Data []byte |
||||
} |
||||
|
||||
func (f *UnknownFrame) GetHeader() *FrameHeader { |
||||
return &f.Header |
||||
} |
||||
|
||||
func (f *UnknownFrame) ParsePayload(r io.Reader) error { |
||||
raw := make([]byte, f.Header.Length) |
||||
if _, err := io.ReadFull(r, raw); err != nil { |
||||
return err |
||||
} |
||||
return f.UnmarshalPayload(raw) |
||||
} |
||||
|
||||
func (f *UnknownFrame) UnmarshalPayload(raw []byte) error { |
||||
if f.Header.Length != len(raw) { |
||||
return fmt.Errorf("Invalid Payload length %d != %d", f.Header.Length, len(raw)) |
||||
} |
||||
|
||||
f.Data = []byte(string(raw)) |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func (f *UnknownFrame) MarshalPayload() ([]byte, error) { |
||||
return []byte(string(f.Data)), nil |
||||
} |
||||
|
||||
func (f *UnknownFrame) MarshalBinary() ([]byte, error) { |
||||
f.Header.Length = len(f.Data) |
||||
buf, err := f.Header.MarshalBinary() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
payload, err := f.MarshalPayload() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
buf = append(buf, payload...) |
||||
|
||||
return buf, nil |
||||
} |
Loading…
Reference in new issue