OSDN Git Service

Hulk did something
[bytom/vapor.git] / vendor / golang.org / x / net / http2 / frame_test.go
diff --git a/vendor/golang.org/x/net/http2/frame_test.go b/vendor/golang.org/x/net/http2/frame_test.go
new file mode 100644 (file)
index 0000000..37266bc
--- /dev/null
@@ -0,0 +1,1191 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package http2
+
+import (
+       "bytes"
+       "fmt"
+       "io"
+       "reflect"
+       "strings"
+       "testing"
+       "unsafe"
+
+       "golang.org/x/net/http2/hpack"
+)
+
+func testFramer() (*Framer, *bytes.Buffer) {
+       buf := new(bytes.Buffer)
+       return NewFramer(buf, buf), buf
+}
+
+func TestFrameSizes(t *testing.T) {
+       // Catch people rearranging the FrameHeader fields.
+       if got, want := int(unsafe.Sizeof(FrameHeader{})), 12; got != want {
+               t.Errorf("FrameHeader size = %d; want %d", got, want)
+       }
+}
+
+func TestFrameTypeString(t *testing.T) {
+       tests := []struct {
+               ft   FrameType
+               want string
+       }{
+               {FrameData, "DATA"},
+               {FramePing, "PING"},
+               {FrameGoAway, "GOAWAY"},
+               {0xf, "UNKNOWN_FRAME_TYPE_15"},
+       }
+
+       for i, tt := range tests {
+               got := tt.ft.String()
+               if got != tt.want {
+                       t.Errorf("%d. String(FrameType %d) = %q; want %q", i, int(tt.ft), got, tt.want)
+               }
+       }
+}
+
+func TestWriteRST(t *testing.T) {
+       fr, buf := testFramer()
+       var streamID uint32 = 1<<24 + 2<<16 + 3<<8 + 4
+       var errCode uint32 = 7<<24 + 6<<16 + 5<<8 + 4
+       fr.WriteRSTStream(streamID, ErrCode(errCode))
+       const wantEnc = "\x00\x00\x04\x03\x00\x01\x02\x03\x04\x07\x06\x05\x04"
+       if buf.String() != wantEnc {
+               t.Errorf("encoded as %q; want %q", buf.Bytes(), wantEnc)
+       }
+       f, err := fr.ReadFrame()
+       if err != nil {
+               t.Fatal(err)
+       }
+       want := &RSTStreamFrame{
+               FrameHeader: FrameHeader{
+                       valid:    true,
+                       Type:     0x3,
+                       Flags:    0x0,
+                       Length:   0x4,
+                       StreamID: 0x1020304,
+               },
+               ErrCode: 0x7060504,
+       }
+       if !reflect.DeepEqual(f, want) {
+               t.Errorf("parsed back %#v; want %#v", f, want)
+       }
+}
+
+func TestWriteData(t *testing.T) {
+       fr, buf := testFramer()
+       var streamID uint32 = 1<<24 + 2<<16 + 3<<8 + 4
+       data := []byte("ABC")
+       fr.WriteData(streamID, true, data)
+       const wantEnc = "\x00\x00\x03\x00\x01\x01\x02\x03\x04ABC"
+       if buf.String() != wantEnc {
+               t.Errorf("encoded as %q; want %q", buf.Bytes(), wantEnc)
+       }
+       f, err := fr.ReadFrame()
+       if err != nil {
+               t.Fatal(err)
+       }
+       df, ok := f.(*DataFrame)
+       if !ok {
+               t.Fatalf("got %T; want *DataFrame", f)
+       }
+       if !bytes.Equal(df.Data(), data) {
+               t.Errorf("got %q; want %q", df.Data(), data)
+       }
+       if f.Header().Flags&1 == 0 {
+               t.Errorf("didn't see END_STREAM flag")
+       }
+}
+
+func TestWriteDataPadded(t *testing.T) {
+       tests := [...]struct {
+               streamID   uint32
+               endStream  bool
+               data       []byte
+               pad        []byte
+               wantHeader FrameHeader
+       }{
+               // Unpadded:
+               0: {
+                       streamID:  1,
+                       endStream: true,
+                       data:      []byte("foo"),
+                       pad:       nil,
+                       wantHeader: FrameHeader{
+                               Type:     FrameData,
+                               Flags:    FlagDataEndStream,
+                               Length:   3,
+                               StreamID: 1,
+                       },
+               },
+
+               // Padded bit set, but no padding:
+               1: {
+                       streamID:  1,
+                       endStream: true,
+                       data:      []byte("foo"),
+                       pad:       []byte{},
+                       wantHeader: FrameHeader{
+                               Type:     FrameData,
+                               Flags:    FlagDataEndStream | FlagDataPadded,
+                               Length:   4,
+                               StreamID: 1,
+                       },
+               },
+
+               // Padded bit set, with padding:
+               2: {
+                       streamID:  1,
+                       endStream: false,
+                       data:      []byte("foo"),
+                       pad:       []byte{0, 0, 0},
+                       wantHeader: FrameHeader{
+                               Type:     FrameData,
+                               Flags:    FlagDataPadded,
+                               Length:   7,
+                               StreamID: 1,
+                       },
+               },
+       }
+       for i, tt := range tests {
+               fr, _ := testFramer()
+               fr.WriteDataPadded(tt.streamID, tt.endStream, tt.data, tt.pad)
+               f, err := fr.ReadFrame()
+               if err != nil {
+                       t.Errorf("%d. ReadFrame: %v", i, err)
+                       continue
+               }
+               got := f.Header()
+               tt.wantHeader.valid = true
+               if got != tt.wantHeader {
+                       t.Errorf("%d. read %+v; want %+v", i, got, tt.wantHeader)
+                       continue
+               }
+               df := f.(*DataFrame)
+               if !bytes.Equal(df.Data(), tt.data) {
+                       t.Errorf("%d. got %q; want %q", i, df.Data(), tt.data)
+               }
+       }
+}
+
+func TestWriteHeaders(t *testing.T) {
+       tests := []struct {
+               name      string
+               p         HeadersFrameParam
+               wantEnc   string
+               wantFrame *HeadersFrame
+       }{
+               {
+                       "basic",
+                       HeadersFrameParam{
+                               StreamID:      42,
+                               BlockFragment: []byte("abc"),
+                               Priority:      PriorityParam{},
+                       },
+                       "\x00\x00\x03\x01\x00\x00\x00\x00*abc",
+                       &HeadersFrame{
+                               FrameHeader: FrameHeader{
+                                       valid:    true,
+                                       StreamID: 42,
+                                       Type:     FrameHeaders,
+                                       Length:   uint32(len("abc")),
+                               },
+                               Priority:      PriorityParam{},
+                               headerFragBuf: []byte("abc"),
+                       },
+               },
+               {
+                       "basic + end flags",
+                       HeadersFrameParam{
+                               StreamID:      42,
+                               BlockFragment: []byte("abc"),
+                               EndStream:     true,
+                               EndHeaders:    true,
+                               Priority:      PriorityParam{},
+                       },
+                       "\x00\x00\x03\x01\x05\x00\x00\x00*abc",
+                       &HeadersFrame{
+                               FrameHeader: FrameHeader{
+                                       valid:    true,
+                                       StreamID: 42,
+                                       Type:     FrameHeaders,
+                                       Flags:    FlagHeadersEndStream | FlagHeadersEndHeaders,
+                                       Length:   uint32(len("abc")),
+                               },
+                               Priority:      PriorityParam{},
+                               headerFragBuf: []byte("abc"),
+                       },
+               },
+               {
+                       "with padding",
+                       HeadersFrameParam{
+                               StreamID:      42,
+                               BlockFragment: []byte("abc"),
+                               EndStream:     true,
+                               EndHeaders:    true,
+                               PadLength:     5,
+                               Priority:      PriorityParam{},
+                       },
+                       "\x00\x00\t\x01\r\x00\x00\x00*\x05abc\x00\x00\x00\x00\x00",
+                       &HeadersFrame{
+                               FrameHeader: FrameHeader{
+                                       valid:    true,
+                                       StreamID: 42,
+                                       Type:     FrameHeaders,
+                                       Flags:    FlagHeadersEndStream | FlagHeadersEndHeaders | FlagHeadersPadded,
+                                       Length:   uint32(1 + len("abc") + 5), // pad length + contents + padding
+                               },
+                               Priority:      PriorityParam{},
+                               headerFragBuf: []byte("abc"),
+                       },
+               },
+               {
+                       "with priority",
+                       HeadersFrameParam{
+                               StreamID:      42,
+                               BlockFragment: []byte("abc"),
+                               EndStream:     true,
+                               EndHeaders:    true,
+                               PadLength:     2,
+                               Priority: PriorityParam{
+                                       StreamDep: 15,
+                                       Exclusive: true,
+                                       Weight:    127,
+                               },
+                       },
+                       "\x00\x00\v\x01-\x00\x00\x00*\x02\x80\x00\x00\x0f\u007fabc\x00\x00",
+                       &HeadersFrame{
+                               FrameHeader: FrameHeader{
+                                       valid:    true,
+                                       StreamID: 42,
+                                       Type:     FrameHeaders,
+                                       Flags:    FlagHeadersEndStream | FlagHeadersEndHeaders | FlagHeadersPadded | FlagHeadersPriority,
+                                       Length:   uint32(1 + 5 + len("abc") + 2), // pad length + priority + contents + padding
+                               },
+                               Priority: PriorityParam{
+                                       StreamDep: 15,
+                                       Exclusive: true,
+                                       Weight:    127,
+                               },
+                               headerFragBuf: []byte("abc"),
+                       },
+               },
+               {
+                       "with priority stream dep zero", // golang.org/issue/15444
+                       HeadersFrameParam{
+                               StreamID:      42,
+                               BlockFragment: []byte("abc"),
+                               EndStream:     true,
+                               EndHeaders:    true,
+                               PadLength:     2,
+                               Priority: PriorityParam{
+                                       StreamDep: 0,
+                                       Exclusive: true,
+                                       Weight:    127,
+                               },
+                       },
+                       "\x00\x00\v\x01-\x00\x00\x00*\x02\x80\x00\x00\x00\u007fabc\x00\x00",
+                       &HeadersFrame{
+                               FrameHeader: FrameHeader{
+                                       valid:    true,
+                                       StreamID: 42,
+                                       Type:     FrameHeaders,
+                                       Flags:    FlagHeadersEndStream | FlagHeadersEndHeaders | FlagHeadersPadded | FlagHeadersPriority,
+                                       Length:   uint32(1 + 5 + len("abc") + 2), // pad length + priority + contents + padding
+                               },
+                               Priority: PriorityParam{
+                                       StreamDep: 0,
+                                       Exclusive: true,
+                                       Weight:    127,
+                               },
+                               headerFragBuf: []byte("abc"),
+                       },
+               },
+       }
+       for _, tt := range tests {
+               fr, buf := testFramer()
+               if err := fr.WriteHeaders(tt.p); err != nil {
+                       t.Errorf("test %q: %v", tt.name, err)
+                       continue
+               }
+               if buf.String() != tt.wantEnc {
+                       t.Errorf("test %q: encoded %q; want %q", tt.name, buf.Bytes(), tt.wantEnc)
+               }
+               f, err := fr.ReadFrame()
+               if err != nil {
+                       t.Errorf("test %q: failed to read the frame back: %v", tt.name, err)
+                       continue
+               }
+               if !reflect.DeepEqual(f, tt.wantFrame) {
+                       t.Errorf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tt.name, f, tt.wantFrame)
+               }
+       }
+}
+
+func TestWriteInvalidStreamDep(t *testing.T) {
+       fr, _ := testFramer()
+       err := fr.WriteHeaders(HeadersFrameParam{
+               StreamID: 42,
+               Priority: PriorityParam{
+                       StreamDep: 1 << 31,
+               },
+       })
+       if err != errDepStreamID {
+               t.Errorf("header error = %v; want %q", err, errDepStreamID)
+       }
+
+       err = fr.WritePriority(2, PriorityParam{StreamDep: 1 << 31})
+       if err != errDepStreamID {
+               t.Errorf("priority error = %v; want %q", err, errDepStreamID)
+       }
+}
+
+func TestWriteContinuation(t *testing.T) {
+       const streamID = 42
+       tests := []struct {
+               name string
+               end  bool
+               frag []byte
+
+               wantFrame *ContinuationFrame
+       }{
+               {
+                       "not end",
+                       false,
+                       []byte("abc"),
+                       &ContinuationFrame{
+                               FrameHeader: FrameHeader{
+                                       valid:    true,
+                                       StreamID: streamID,
+                                       Type:     FrameContinuation,
+                                       Length:   uint32(len("abc")),
+                               },
+                               headerFragBuf: []byte("abc"),
+                       },
+               },
+               {
+                       "end",
+                       true,
+                       []byte("def"),
+                       &ContinuationFrame{
+                               FrameHeader: FrameHeader{
+                                       valid:    true,
+                                       StreamID: streamID,
+                                       Type:     FrameContinuation,
+                                       Flags:    FlagContinuationEndHeaders,
+                                       Length:   uint32(len("def")),
+                               },
+                               headerFragBuf: []byte("def"),
+                       },
+               },
+       }
+       for _, tt := range tests {
+               fr, _ := testFramer()
+               if err := fr.WriteContinuation(streamID, tt.end, tt.frag); err != nil {
+                       t.Errorf("test %q: %v", tt.name, err)
+                       continue
+               }
+               fr.AllowIllegalReads = true
+               f, err := fr.ReadFrame()
+               if err != nil {
+                       t.Errorf("test %q: failed to read the frame back: %v", tt.name, err)
+                       continue
+               }
+               if !reflect.DeepEqual(f, tt.wantFrame) {
+                       t.Errorf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tt.name, f, tt.wantFrame)
+               }
+       }
+}
+
+func TestWritePriority(t *testing.T) {
+       const streamID = 42
+       tests := []struct {
+               name      string
+               priority  PriorityParam
+               wantFrame *PriorityFrame
+       }{
+               {
+                       "not exclusive",
+                       PriorityParam{
+                               StreamDep: 2,
+                               Exclusive: false,
+                               Weight:    127,
+                       },
+                       &PriorityFrame{
+                               FrameHeader{
+                                       valid:    true,
+                                       StreamID: streamID,
+                                       Type:     FramePriority,
+                                       Length:   5,
+                               },
+                               PriorityParam{
+                                       StreamDep: 2,
+                                       Exclusive: false,
+                                       Weight:    127,
+                               },
+                       },
+               },
+
+               {
+                       "exclusive",
+                       PriorityParam{
+                               StreamDep: 3,
+                               Exclusive: true,
+                               Weight:    77,
+                       },
+                       &PriorityFrame{
+                               FrameHeader{
+                                       valid:    true,
+                                       StreamID: streamID,
+                                       Type:     FramePriority,
+                                       Length:   5,
+                               },
+                               PriorityParam{
+                                       StreamDep: 3,
+                                       Exclusive: true,
+                                       Weight:    77,
+                               },
+                       },
+               },
+       }
+       for _, tt := range tests {
+               fr, _ := testFramer()
+               if err := fr.WritePriority(streamID, tt.priority); err != nil {
+                       t.Errorf("test %q: %v", tt.name, err)
+                       continue
+               }
+               f, err := fr.ReadFrame()
+               if err != nil {
+                       t.Errorf("test %q: failed to read the frame back: %v", tt.name, err)
+                       continue
+               }
+               if !reflect.DeepEqual(f, tt.wantFrame) {
+                       t.Errorf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tt.name, f, tt.wantFrame)
+               }
+       }
+}
+
+func TestWriteSettings(t *testing.T) {
+       fr, buf := testFramer()
+       settings := []Setting{{1, 2}, {3, 4}}
+       fr.WriteSettings(settings...)
+       const wantEnc = "\x00\x00\f\x04\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x02\x00\x03\x00\x00\x00\x04"
+       if buf.String() != wantEnc {
+               t.Errorf("encoded as %q; want %q", buf.Bytes(), wantEnc)
+       }
+       f, err := fr.ReadFrame()
+       if err != nil {
+               t.Fatal(err)
+       }
+       sf, ok := f.(*SettingsFrame)
+       if !ok {
+               t.Fatalf("Got a %T; want a SettingsFrame", f)
+       }
+       var got []Setting
+       sf.ForeachSetting(func(s Setting) error {
+               got = append(got, s)
+               valBack, ok := sf.Value(s.ID)
+               if !ok || valBack != s.Val {
+                       t.Errorf("Value(%d) = %v, %v; want %v, true", s.ID, valBack, ok, s.Val)
+               }
+               return nil
+       })
+       if !reflect.DeepEqual(settings, got) {
+               t.Errorf("Read settings %+v != written settings %+v", got, settings)
+       }
+}
+
+func TestWriteSettingsAck(t *testing.T) {
+       fr, buf := testFramer()
+       fr.WriteSettingsAck()
+       const wantEnc = "\x00\x00\x00\x04\x01\x00\x00\x00\x00"
+       if buf.String() != wantEnc {
+               t.Errorf("encoded as %q; want %q", buf.Bytes(), wantEnc)
+       }
+}
+
+func TestWriteWindowUpdate(t *testing.T) {
+       fr, buf := testFramer()
+       const streamID = 1<<24 + 2<<16 + 3<<8 + 4
+       const incr = 7<<24 + 6<<16 + 5<<8 + 4
+       if err := fr.WriteWindowUpdate(streamID, incr); err != nil {
+               t.Fatal(err)
+       }
+       const wantEnc = "\x00\x00\x04\x08\x00\x01\x02\x03\x04\x07\x06\x05\x04"
+       if buf.String() != wantEnc {
+               t.Errorf("encoded as %q; want %q", buf.Bytes(), wantEnc)
+       }
+       f, err := fr.ReadFrame()
+       if err != nil {
+               t.Fatal(err)
+       }
+       want := &WindowUpdateFrame{
+               FrameHeader: FrameHeader{
+                       valid:    true,
+                       Type:     0x8,
+                       Flags:    0x0,
+                       Length:   0x4,
+                       StreamID: 0x1020304,
+               },
+               Increment: 0x7060504,
+       }
+       if !reflect.DeepEqual(f, want) {
+               t.Errorf("parsed back %#v; want %#v", f, want)
+       }
+}
+
+func TestWritePing(t *testing.T)    { testWritePing(t, false) }
+func TestWritePingAck(t *testing.T) { testWritePing(t, true) }
+
+func testWritePing(t *testing.T, ack bool) {
+       fr, buf := testFramer()
+       if err := fr.WritePing(ack, [8]byte{1, 2, 3, 4, 5, 6, 7, 8}); err != nil {
+               t.Fatal(err)
+       }
+       var wantFlags Flags
+       if ack {
+               wantFlags = FlagPingAck
+       }
+       var wantEnc = "\x00\x00\x08\x06" + string(wantFlags) + "\x00\x00\x00\x00" + "\x01\x02\x03\x04\x05\x06\x07\x08"
+       if buf.String() != wantEnc {
+               t.Errorf("encoded as %q; want %q", buf.Bytes(), wantEnc)
+       }
+
+       f, err := fr.ReadFrame()
+       if err != nil {
+               t.Fatal(err)
+       }
+       want := &PingFrame{
+               FrameHeader: FrameHeader{
+                       valid:    true,
+                       Type:     0x6,
+                       Flags:    wantFlags,
+                       Length:   0x8,
+                       StreamID: 0,
+               },
+               Data: [8]byte{1, 2, 3, 4, 5, 6, 7, 8},
+       }
+       if !reflect.DeepEqual(f, want) {
+               t.Errorf("parsed back %#v; want %#v", f, want)
+       }
+}
+
+func TestReadFrameHeader(t *testing.T) {
+       tests := []struct {
+               in   string
+               want FrameHeader
+       }{
+               {in: "\x00\x00\x00" + "\x00" + "\x00" + "\x00\x00\x00\x00", want: FrameHeader{}},
+               {in: "\x01\x02\x03" + "\x04" + "\x05" + "\x06\x07\x08\x09", want: FrameHeader{
+                       Length: 66051, Type: 4, Flags: 5, StreamID: 101124105,
+               }},
+               // Ignore high bit:
+               {in: "\xff\xff\xff" + "\xff" + "\xff" + "\xff\xff\xff\xff", want: FrameHeader{
+                       Length: 16777215, Type: 255, Flags: 255, StreamID: 2147483647}},
+               {in: "\xff\xff\xff" + "\xff" + "\xff" + "\x7f\xff\xff\xff", want: FrameHeader{
+                       Length: 16777215, Type: 255, Flags: 255, StreamID: 2147483647}},
+       }
+       for i, tt := range tests {
+               got, err := readFrameHeader(make([]byte, 9), strings.NewReader(tt.in))
+               if err != nil {
+                       t.Errorf("%d. readFrameHeader(%q) = %v", i, tt.in, err)
+                       continue
+               }
+               tt.want.valid = true
+               if got != tt.want {
+                       t.Errorf("%d. readFrameHeader(%q) = %+v; want %+v", i, tt.in, got, tt.want)
+               }
+       }
+}
+
+func TestReadWriteFrameHeader(t *testing.T) {
+       tests := []struct {
+               len      uint32
+               typ      FrameType
+               flags    Flags
+               streamID uint32
+       }{
+               {len: 0, typ: 255, flags: 1, streamID: 0},
+               {len: 0, typ: 255, flags: 1, streamID: 1},
+               {len: 0, typ: 255, flags: 1, streamID: 255},
+               {len: 0, typ: 255, flags: 1, streamID: 256},
+               {len: 0, typ: 255, flags: 1, streamID: 65535},
+               {len: 0, typ: 255, flags: 1, streamID: 65536},
+
+               {len: 0, typ: 1, flags: 255, streamID: 1},
+               {len: 255, typ: 1, flags: 255, streamID: 1},
+               {len: 256, typ: 1, flags: 255, streamID: 1},
+               {len: 65535, typ: 1, flags: 255, streamID: 1},
+               {len: 65536, typ: 1, flags: 255, streamID: 1},
+               {len: 16777215, typ: 1, flags: 255, streamID: 1},
+       }
+       for _, tt := range tests {
+               fr, buf := testFramer()
+               fr.startWrite(tt.typ, tt.flags, tt.streamID)
+               fr.writeBytes(make([]byte, tt.len))
+               fr.endWrite()
+               fh, err := ReadFrameHeader(buf)
+               if err != nil {
+                       t.Errorf("ReadFrameHeader(%+v) = %v", tt, err)
+                       continue
+               }
+               if fh.Type != tt.typ || fh.Flags != tt.flags || fh.Length != tt.len || fh.StreamID != tt.streamID {
+                       t.Errorf("ReadFrameHeader(%+v) = %+v; mismatch", tt, fh)
+               }
+       }
+
+}
+
+func TestWriteTooLargeFrame(t *testing.T) {
+       fr, _ := testFramer()
+       fr.startWrite(0, 1, 1)
+       fr.writeBytes(make([]byte, 1<<24))
+       err := fr.endWrite()
+       if err != ErrFrameTooLarge {
+               t.Errorf("endWrite = %v; want errFrameTooLarge", err)
+       }
+}
+
+func TestWriteGoAway(t *testing.T) {
+       const debug = "foo"
+       fr, buf := testFramer()
+       if err := fr.WriteGoAway(0x01020304, 0x05060708, []byte(debug)); err != nil {
+               t.Fatal(err)
+       }
+       const wantEnc = "\x00\x00\v\a\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08" + debug
+       if buf.String() != wantEnc {
+               t.Errorf("encoded as %q; want %q", buf.Bytes(), wantEnc)
+       }
+       f, err := fr.ReadFrame()
+       if err != nil {
+               t.Fatal(err)
+       }
+       want := &GoAwayFrame{
+               FrameHeader: FrameHeader{
+                       valid:    true,
+                       Type:     0x7,
+                       Flags:    0,
+                       Length:   uint32(4 + 4 + len(debug)),
+                       StreamID: 0,
+               },
+               LastStreamID: 0x01020304,
+               ErrCode:      0x05060708,
+               debugData:    []byte(debug),
+       }
+       if !reflect.DeepEqual(f, want) {
+               t.Fatalf("parsed back:\n%#v\nwant:\n%#v", f, want)
+       }
+       if got := string(f.(*GoAwayFrame).DebugData()); got != debug {
+               t.Errorf("debug data = %q; want %q", got, debug)
+       }
+}
+
+func TestWritePushPromise(t *testing.T) {
+       pp := PushPromiseParam{
+               StreamID:      42,
+               PromiseID:     42,
+               BlockFragment: []byte("abc"),
+       }
+       fr, buf := testFramer()
+       if err := fr.WritePushPromise(pp); err != nil {
+               t.Fatal(err)
+       }
+       const wantEnc = "\x00\x00\x07\x05\x00\x00\x00\x00*\x00\x00\x00*abc"
+       if buf.String() != wantEnc {
+               t.Errorf("encoded as %q; want %q", buf.Bytes(), wantEnc)
+       }
+       f, err := fr.ReadFrame()
+       if err != nil {
+               t.Fatal(err)
+       }
+       _, ok := f.(*PushPromiseFrame)
+       if !ok {
+               t.Fatalf("got %T; want *PushPromiseFrame", f)
+       }
+       want := &PushPromiseFrame{
+               FrameHeader: FrameHeader{
+                       valid:    true,
+                       Type:     0x5,
+                       Flags:    0x0,
+                       Length:   0x7,
+                       StreamID: 42,
+               },
+               PromiseID:     42,
+               headerFragBuf: []byte("abc"),
+       }
+       if !reflect.DeepEqual(f, want) {
+               t.Fatalf("parsed back:\n%#v\nwant:\n%#v", f, want)
+       }
+}
+
+// test checkFrameOrder and that HEADERS and CONTINUATION frames can't be intermingled.
+func TestReadFrameOrder(t *testing.T) {
+       head := func(f *Framer, id uint32, end bool) {
+               f.WriteHeaders(HeadersFrameParam{
+                       StreamID:      id,
+                       BlockFragment: []byte("foo"), // unused, but non-empty
+                       EndHeaders:    end,
+               })
+       }
+       cont := func(f *Framer, id uint32, end bool) {
+               f.WriteContinuation(id, end, []byte("foo"))
+       }
+
+       tests := [...]struct {
+               name    string
+               w       func(*Framer)
+               atLeast int
+               wantErr string
+       }{
+               0: {
+                       w: func(f *Framer) {
+                               head(f, 1, true)
+                       },
+               },
+               1: {
+                       w: func(f *Framer) {
+                               head(f, 1, true)
+                               head(f, 2, true)
+                       },
+               },
+               2: {
+                       wantErr: "got HEADERS for stream 2; expected CONTINUATION following HEADERS for stream 1",
+                       w: func(f *Framer) {
+                               head(f, 1, false)
+                               head(f, 2, true)
+                       },
+               },
+               3: {
+                       wantErr: "got DATA for stream 1; expected CONTINUATION following HEADERS for stream 1",
+                       w: func(f *Framer) {
+                               head(f, 1, false)
+                       },
+               },
+               4: {
+                       w: func(f *Framer) {
+                               head(f, 1, false)
+                               cont(f, 1, true)
+                               head(f, 2, true)
+                       },
+               },
+               5: {
+                       wantErr: "got CONTINUATION for stream 2; expected stream 1",
+                       w: func(f *Framer) {
+                               head(f, 1, false)
+                               cont(f, 2, true)
+                               head(f, 2, true)
+                       },
+               },
+               6: {
+                       wantErr: "unexpected CONTINUATION for stream 1",
+                       w: func(f *Framer) {
+                               cont(f, 1, true)
+                       },
+               },
+               7: {
+                       wantErr: "unexpected CONTINUATION for stream 1",
+                       w: func(f *Framer) {
+                               cont(f, 1, false)
+                       },
+               },
+               8: {
+                       wantErr: "HEADERS frame with stream ID 0",
+                       w: func(f *Framer) {
+                               head(f, 0, true)
+                       },
+               },
+               9: {
+                       wantErr: "CONTINUATION frame with stream ID 0",
+                       w: func(f *Framer) {
+                               cont(f, 0, true)
+                       },
+               },
+               10: {
+                       wantErr: "unexpected CONTINUATION for stream 1",
+                       atLeast: 5,
+                       w: func(f *Framer) {
+                               head(f, 1, false)
+                               cont(f, 1, false)
+                               cont(f, 1, false)
+                               cont(f, 1, false)
+                               cont(f, 1, true)
+                               cont(f, 1, false)
+                       },
+               },
+       }
+       for i, tt := range tests {
+               buf := new(bytes.Buffer)
+               f := NewFramer(buf, buf)
+               f.AllowIllegalWrites = true
+               tt.w(f)
+               f.WriteData(1, true, nil) // to test transition away from last step
+
+               var err error
+               n := 0
+               var log bytes.Buffer
+               for {
+                       var got Frame
+                       got, err = f.ReadFrame()
+                       fmt.Fprintf(&log, "  read %v, %v\n", got, err)
+                       if err != nil {
+                               break
+                       }
+                       n++
+               }
+               if err == io.EOF {
+                       err = nil
+               }
+               ok := tt.wantErr == ""
+               if ok && err != nil {
+                       t.Errorf("%d. after %d good frames, ReadFrame = %v; want success\n%s", i, n, err, log.Bytes())
+                       continue
+               }
+               if !ok && err != ConnectionError(ErrCodeProtocol) {
+                       t.Errorf("%d. after %d good frames, ReadFrame = %v; want ConnectionError(ErrCodeProtocol)\n%s", i, n, err, log.Bytes())
+                       continue
+               }
+               if !((f.errDetail == nil && tt.wantErr == "") || (fmt.Sprint(f.errDetail) == tt.wantErr)) {
+                       t.Errorf("%d. framer eror = %q; want %q\n%s", i, f.errDetail, tt.wantErr, log.Bytes())
+               }
+               if n < tt.atLeast {
+                       t.Errorf("%d. framer only read %d frames; want at least %d\n%s", i, n, tt.atLeast, log.Bytes())
+               }
+       }
+}
+
+func TestMetaFrameHeader(t *testing.T) {
+       write := func(f *Framer, frags ...[]byte) {
+               for i, frag := range frags {
+                       end := (i == len(frags)-1)
+                       if i == 0 {
+                               f.WriteHeaders(HeadersFrameParam{
+                                       StreamID:      1,
+                                       BlockFragment: frag,
+                                       EndHeaders:    end,
+                               })
+                       } else {
+                               f.WriteContinuation(1, end, frag)
+                       }
+               }
+       }
+
+       want := func(flags Flags, length uint32, pairs ...string) *MetaHeadersFrame {
+               mh := &MetaHeadersFrame{
+                       HeadersFrame: &HeadersFrame{
+                               FrameHeader: FrameHeader{
+                                       Type:     FrameHeaders,
+                                       Flags:    flags,
+                                       Length:   length,
+                                       StreamID: 1,
+                               },
+                       },
+                       Fields: []hpack.HeaderField(nil),
+               }
+               for len(pairs) > 0 {
+                       mh.Fields = append(mh.Fields, hpack.HeaderField{
+                               Name:  pairs[0],
+                               Value: pairs[1],
+                       })
+                       pairs = pairs[2:]
+               }
+               return mh
+       }
+       truncated := func(mh *MetaHeadersFrame) *MetaHeadersFrame {
+               mh.Truncated = true
+               return mh
+       }
+
+       const noFlags Flags = 0
+
+       oneKBString := strings.Repeat("a", 1<<10)
+
+       tests := [...]struct {
+               name              string
+               w                 func(*Framer)
+               want              interface{} // *MetaHeaderFrame or error
+               wantErrReason     string
+               maxHeaderListSize uint32
+       }{
+               0: {
+                       name: "single_headers",
+                       w: func(f *Framer) {
+                               var he hpackEncoder
+                               all := he.encodeHeaderRaw(t, ":method", "GET", ":path", "/")
+                               write(f, all)
+                       },
+                       want: want(FlagHeadersEndHeaders, 2, ":method", "GET", ":path", "/"),
+               },
+               1: {
+                       name: "with_continuation",
+                       w: func(f *Framer) {
+                               var he hpackEncoder
+                               all := he.encodeHeaderRaw(t, ":method", "GET", ":path", "/", "foo", "bar")
+                               write(f, all[:1], all[1:])
+                       },
+                       want: want(noFlags, 1, ":method", "GET", ":path", "/", "foo", "bar"),
+               },
+               2: {
+                       name: "with_two_continuation",
+                       w: func(f *Framer) {
+                               var he hpackEncoder
+                               all := he.encodeHeaderRaw(t, ":method", "GET", ":path", "/", "foo", "bar")
+                               write(f, all[:2], all[2:4], all[4:])
+                       },
+                       want: want(noFlags, 2, ":method", "GET", ":path", "/", "foo", "bar"),
+               },
+               3: {
+                       name: "big_string_okay",
+                       w: func(f *Framer) {
+                               var he hpackEncoder
+                               all := he.encodeHeaderRaw(t, ":method", "GET", ":path", "/", "foo", oneKBString)
+                               write(f, all[:2], all[2:])
+                       },
+                       want: want(noFlags, 2, ":method", "GET", ":path", "/", "foo", oneKBString),
+               },
+               4: {
+                       name: "big_string_error",
+                       w: func(f *Framer) {
+                               var he hpackEncoder
+                               all := he.encodeHeaderRaw(t, ":method", "GET", ":path", "/", "foo", oneKBString)
+                               write(f, all[:2], all[2:])
+                       },
+                       maxHeaderListSize: (1 << 10) / 2,
+                       want:              ConnectionError(ErrCodeCompression),
+               },
+               5: {
+                       name: "max_header_list_truncated",
+                       w: func(f *Framer) {
+                               var he hpackEncoder
+                               var pairs = []string{":method", "GET", ":path", "/"}
+                               for i := 0; i < 100; i++ {
+                                       pairs = append(pairs, "foo", "bar")
+                               }
+                               all := he.encodeHeaderRaw(t, pairs...)
+                               write(f, all[:2], all[2:])
+                       },
+                       maxHeaderListSize: (1 << 10) / 2,
+                       want: truncated(want(noFlags, 2,
+                               ":method", "GET",
+                               ":path", "/",
+                               "foo", "bar",
+                               "foo", "bar",
+                               "foo", "bar",
+                               "foo", "bar",
+                               "foo", "bar",
+                               "foo", "bar",
+                               "foo", "bar",
+                               "foo", "bar",
+                               "foo", "bar",
+                               "foo", "bar",
+                               "foo", "bar", // 11
+                       )),
+               },
+               6: {
+                       name: "pseudo_order",
+                       w: func(f *Framer) {
+                               write(f, encodeHeaderRaw(t,
+                                       ":method", "GET",
+                                       "foo", "bar",
+                                       ":path", "/", // bogus
+                               ))
+                       },
+                       want:          streamError(1, ErrCodeProtocol),
+                       wantErrReason: "pseudo header field after regular",
+               },
+               7: {
+                       name: "pseudo_unknown",
+                       w: func(f *Framer) {
+                               write(f, encodeHeaderRaw(t,
+                                       ":unknown", "foo", // bogus
+                                       "foo", "bar",
+                               ))
+                       },
+                       want:          streamError(1, ErrCodeProtocol),
+                       wantErrReason: "invalid pseudo-header \":unknown\"",
+               },
+               8: {
+                       name: "pseudo_mix_request_response",
+                       w: func(f *Framer) {
+                               write(f, encodeHeaderRaw(t,
+                                       ":method", "GET",
+                                       ":status", "100",
+                               ))
+                       },
+                       want:          streamError(1, ErrCodeProtocol),
+                       wantErrReason: "mix of request and response pseudo headers",
+               },
+               9: {
+                       name: "pseudo_dup",
+                       w: func(f *Framer) {
+                               write(f, encodeHeaderRaw(t,
+                                       ":method", "GET",
+                                       ":method", "POST",
+                               ))
+                       },
+                       want:          streamError(1, ErrCodeProtocol),
+                       wantErrReason: "duplicate pseudo-header \":method\"",
+               },
+               10: {
+                       name: "trailer_okay_no_pseudo",
+                       w:    func(f *Framer) { write(f, encodeHeaderRaw(t, "foo", "bar")) },
+                       want: want(FlagHeadersEndHeaders, 8, "foo", "bar"),
+               },
+               11: {
+                       name:          "invalid_field_name",
+                       w:             func(f *Framer) { write(f, encodeHeaderRaw(t, "CapitalBad", "x")) },
+                       want:          streamError(1, ErrCodeProtocol),
+                       wantErrReason: "invalid header field name \"CapitalBad\"",
+               },
+               12: {
+                       name:          "invalid_field_value",
+                       w:             func(f *Framer) { write(f, encodeHeaderRaw(t, "key", "bad_null\x00")) },
+                       want:          streamError(1, ErrCodeProtocol),
+                       wantErrReason: "invalid header field value \"bad_null\\x00\"",
+               },
+       }
+       for i, tt := range tests {
+               buf := new(bytes.Buffer)
+               f := NewFramer(buf, buf)
+               f.ReadMetaHeaders = hpack.NewDecoder(initialHeaderTableSize, nil)
+               f.MaxHeaderListSize = tt.maxHeaderListSize
+               tt.w(f)
+
+               name := tt.name
+               if name == "" {
+                       name = fmt.Sprintf("test index %d", i)
+               }
+
+               var got interface{}
+               var err error
+               got, err = f.ReadFrame()
+               if err != nil {
+                       got = err
+
+                       // Ignore the StreamError.Cause field, if it matches the wantErrReason.
+                       // The test table above predates the Cause field.
+                       if se, ok := err.(StreamError); ok && se.Cause != nil && se.Cause.Error() == tt.wantErrReason {
+                               se.Cause = nil
+                               got = se
+                       }
+               }
+               if !reflect.DeepEqual(got, tt.want) {
+                       if mhg, ok := got.(*MetaHeadersFrame); ok {
+                               if mhw, ok := tt.want.(*MetaHeadersFrame); ok {
+                                       hg := mhg.HeadersFrame
+                                       hw := mhw.HeadersFrame
+                                       if hg != nil && hw != nil && !reflect.DeepEqual(*hg, *hw) {
+                                               t.Errorf("%s: headers differ:\n got: %+v\nwant: %+v\n", name, *hg, *hw)
+                                       }
+                               }
+                       }
+                       str := func(v interface{}) string {
+                               if _, ok := v.(error); ok {
+                                       return fmt.Sprintf("error %v", v)
+                               } else {
+                                       return fmt.Sprintf("value %#v", v)
+                               }
+                       }
+                       t.Errorf("%s:\n got: %v\nwant: %s", name, str(got), str(tt.want))
+               }
+               if tt.wantErrReason != "" && tt.wantErrReason != fmt.Sprint(f.errDetail) {
+                       t.Errorf("%s: got error reason %q; want %q", name, f.errDetail, tt.wantErrReason)
+               }
+       }
+}
+
+func TestSetReuseFrames(t *testing.T) {
+       fr, buf := testFramer()
+       fr.SetReuseFrames()
+
+       // Check that DataFrames are reused. Note that
+       // SetReuseFrames only currently implements reuse of DataFrames.
+       firstDf := readAndVerifyDataFrame("ABC", 3, fr, buf, t)
+
+       for i := 0; i < 10; i++ {
+               df := readAndVerifyDataFrame("XYZ", 3, fr, buf, t)
+               if df != firstDf {
+                       t.Errorf("Expected Framer to return references to the same DataFrame. Have %v and %v", &df, &firstDf)
+               }
+       }
+
+       for i := 0; i < 10; i++ {
+               df := readAndVerifyDataFrame("", 0, fr, buf, t)
+               if df != firstDf {
+                       t.Errorf("Expected Framer to return references to the same DataFrame. Have %v and %v", &df, &firstDf)
+               }
+       }
+
+       for i := 0; i < 10; i++ {
+               df := readAndVerifyDataFrame("HHH", 3, fr, buf, t)
+               if df != firstDf {
+                       t.Errorf("Expected Framer to return references to the same DataFrame. Have %v and %v", &df, &firstDf)
+               }
+       }
+}
+
+func TestSetReuseFramesMoreThanOnce(t *testing.T) {
+       fr, buf := testFramer()
+       fr.SetReuseFrames()
+
+       firstDf := readAndVerifyDataFrame("ABC", 3, fr, buf, t)
+       fr.SetReuseFrames()
+
+       for i := 0; i < 10; i++ {
+               df := readAndVerifyDataFrame("XYZ", 3, fr, buf, t)
+               // SetReuseFrames should be idempotent
+               fr.SetReuseFrames()
+               if df != firstDf {
+                       t.Errorf("Expected Framer to return references to the same DataFrame. Have %v and %v", &df, &firstDf)
+               }
+       }
+}
+
+func TestNoSetReuseFrames(t *testing.T) {
+       fr, buf := testFramer()
+       const numNewDataFrames = 10
+       dfSoFar := make([]interface{}, numNewDataFrames)
+
+       // Check that DataFrames are not reused if SetReuseFrames wasn't called.
+       // SetReuseFrames only currently implements reuse of DataFrames.
+       for i := 0; i < numNewDataFrames; i++ {
+               df := readAndVerifyDataFrame("XYZ", 3, fr, buf, t)
+               for _, item := range dfSoFar {
+                       if df == item {
+                               t.Errorf("Expected Framer to return new DataFrames since SetNoReuseFrames not set.")
+                       }
+               }
+               dfSoFar[i] = df
+       }
+}
+
+func readAndVerifyDataFrame(data string, length byte, fr *Framer, buf *bytes.Buffer, t *testing.T) *DataFrame {
+       var streamID uint32 = 1<<24 + 2<<16 + 3<<8 + 4
+       fr.WriteData(streamID, true, []byte(data))
+       wantEnc := "\x00\x00" + string(length) + "\x00\x01\x01\x02\x03\x04" + data
+       if buf.String() != wantEnc {
+               t.Errorf("encoded as %q; want %q", buf.Bytes(), wantEnc)
+       }
+       f, err := fr.ReadFrame()
+       if err != nil {
+               t.Fatal(err)
+       }
+       df, ok := f.(*DataFrame)
+       if !ok {
+               t.Fatalf("got %T; want *DataFrame", f)
+       }
+       if !bytes.Equal(df.Data(), []byte(data)) {
+               t.Errorf("got %q; want %q", df.Data(), []byte(data))
+       }
+       if f.Header().Flags&1 == 0 {
+               t.Errorf("didn't see END_STREAM flag")
+       }
+       return df
+}
+
+func encodeHeaderRaw(t *testing.T, pairs ...string) []byte {
+       var he hpackEncoder
+       return he.encodeHeaderRaw(t, pairs...)
+}