| // 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.Equal(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 (fh FrameHeader) Equal(b FrameHeader) bool { |
| return fh.valid == b.valid && |
| fh.Type == b.Type && |
| fh.Flags == b.Flags && |
| fh.Length == b.Length && |
| fh.StreamID == b.StreamID |
| } |
| |
| 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"), |
| }, |
| }, |
| { |
| "zero length", |
| HeadersFrameParam{ |
| StreamID: 42, |
| Priority: PriorityParam{}, |
| }, |
| "\x00\x00\x00\x01\x00\x00\x00\x00*", |
| &HeadersFrame{ |
| FrameHeader: FrameHeader{ |
| valid: true, |
| StreamID: 42, |
| Type: FrameHeaders, |
| Length: 0, |
| }, |
| Priority: PriorityParam{}, |
| }, |
| }, |
| } |
| 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.Equal(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...) |
| } |
| |
| func TestSettingsDuplicates(t *testing.T) { |
| tests := []struct { |
| settings []Setting |
| want bool |
| }{ |
| {nil, false}, |
| {[]Setting{{ID: 1}}, false}, |
| {[]Setting{{ID: 1}, {ID: 2}}, false}, |
| {[]Setting{{ID: 1}, {ID: 2}}, false}, |
| {[]Setting{{ID: 1}, {ID: 2}, {ID: 3}}, false}, |
| {[]Setting{{ID: 1}, {ID: 2}, {ID: 3}}, false}, |
| {[]Setting{{ID: 1}, {ID: 2}, {ID: 3}, {ID: 4}}, false}, |
| |
| {[]Setting{{ID: 1}, {ID: 2}, {ID: 3}, {ID: 2}}, true}, |
| {[]Setting{{ID: 4}, {ID: 2}, {ID: 3}, {ID: 4}}, true}, |
| |
| {[]Setting{ |
| {ID: 1}, {ID: 2}, {ID: 3}, {ID: 4}, |
| {ID: 5}, {ID: 6}, {ID: 7}, {ID: 8}, |
| {ID: 9}, {ID: 10}, {ID: 11}, {ID: 12}, |
| }, false}, |
| |
| {[]Setting{ |
| {ID: 1}, {ID: 2}, {ID: 3}, {ID: 4}, |
| {ID: 5}, {ID: 6}, {ID: 7}, {ID: 8}, |
| {ID: 9}, {ID: 10}, {ID: 11}, {ID: 11}, |
| }, true}, |
| } |
| for i, tt := range tests { |
| fr, _ := testFramer() |
| fr.WriteSettings(tt.settings...) |
| f, err := fr.ReadFrame() |
| if err != nil { |
| t.Fatalf("%d. ReadFrame: %v", i, err) |
| } |
| sf := f.(*SettingsFrame) |
| got := sf.HasDuplicates() |
| if got != tt.want { |
| t.Errorf("%d. HasDuplicates = %v; want %v", i, got, tt.want) |
| } |
| } |
| |
| } |