// Copyright 2011 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 spdy import ( "bytes" "compress/zlib" "encoding/base64" "io" "io/ioutil" "net/http" "reflect" "testing" ) var HeadersFixture = http.Header{ "Url": []string{"http://www.google.com/"}, "Method": []string{"get"}, "Version": []string{"http/1.1"}, } func TestHeaderParsing(t *testing.T) { var headerValueBlockBuf bytes.Buffer writeHeaderValueBlock(&headerValueBlockBuf, HeadersFixture) const bogusStreamId = 1 newHeaders, err := parseHeaderValueBlock(&headerValueBlockBuf, bogusStreamId) if err != nil { t.Fatal("parseHeaderValueBlock:", err) } if !reflect.DeepEqual(HeadersFixture, newHeaders) { t.Fatal("got: ", newHeaders, "\nwant: ", HeadersFixture) } } func TestCreateParseSynStreamFrameCompressionDisable(t *testing.T) { buffer := new(bytes.Buffer) // Fixture framer for no compression test. framer := &Framer{ headerCompressionDisabled: true, w: buffer, headerBuf: new(bytes.Buffer), r: buffer, } synStreamFrame := SynStreamFrame{ CFHeader: ControlFrameHeader{ version: Version, frameType: TypeSynStream, }, StreamId: 2, Headers: HeadersFixture, } if err := framer.WriteFrame(&synStreamFrame); err != nil { t.Fatal("WriteFrame without compression:", err) } frame, err := framer.ReadFrame() if err != nil { t.Fatal("ReadFrame without compression:", err) } parsedSynStreamFrame, ok := frame.(*SynStreamFrame) if !ok { t.Fatal("Parsed incorrect frame type:", frame) } if !reflect.DeepEqual(synStreamFrame, *parsedSynStreamFrame) { t.Fatal("got: ", *parsedSynStreamFrame, "\nwant: ", synStreamFrame) } } func TestCreateParseSynStreamFrameCompressionEnable(t *testing.T) { buffer := new(bytes.Buffer) framer, err := NewFramer(buffer, buffer) synStreamFrame := SynStreamFrame{ CFHeader: ControlFrameHeader{ version: Version, frameType: TypeSynStream, }, StreamId: 2, Headers: HeadersFixture, } if err != nil { t.Fatal("Failed to create new framer:", err) } if err := framer.WriteFrame(&synStreamFrame); err != nil { t.Fatal("WriteFrame with compression:", err) } frame, err := framer.ReadFrame() if err != nil { t.Fatal("ReadFrame with compression:", err) } parsedSynStreamFrame, ok := frame.(*SynStreamFrame) if !ok { t.Fatal("Parsed incorrect frame type:", frame) } if !reflect.DeepEqual(synStreamFrame, *parsedSynStreamFrame) { t.Fatal("got: ", *parsedSynStreamFrame, "\nwant: ", synStreamFrame) } } func TestCreateParseSynReplyFrameCompressionDisable(t *testing.T) { buffer := new(bytes.Buffer) framer := &Framer{ headerCompressionDisabled: true, w: buffer, headerBuf: new(bytes.Buffer), r: buffer, } synReplyFrame := SynReplyFrame{ CFHeader: ControlFrameHeader{ version: Version, frameType: TypeSynReply, }, StreamId: 2, Headers: HeadersFixture, } if err := framer.WriteFrame(&synReplyFrame); err != nil { t.Fatal("WriteFrame without compression:", err) } frame, err := framer.ReadFrame() if err != nil { t.Fatal("ReadFrame without compression:", err) } parsedSynReplyFrame, ok := frame.(*SynReplyFrame) if !ok { t.Fatal("Parsed incorrect frame type:", frame) } if !reflect.DeepEqual(synReplyFrame, *parsedSynReplyFrame) { t.Fatal("got: ", *parsedSynReplyFrame, "\nwant: ", synReplyFrame) } } func TestCreateParseSynReplyFrameCompressionEnable(t *testing.T) { buffer := new(bytes.Buffer) framer, err := NewFramer(buffer, buffer) synReplyFrame := SynReplyFrame{ CFHeader: ControlFrameHeader{ version: Version, frameType: TypeSynReply, }, StreamId: 2, Headers: HeadersFixture, } if err != nil { t.Fatal("Failed to create new framer:", err) } if err := framer.WriteFrame(&synReplyFrame); err != nil { t.Fatal("WriteFrame with compression:", err) } frame, err := framer.ReadFrame() if err != nil { t.Fatal("ReadFrame with compression:", err) } parsedSynReplyFrame, ok := frame.(*SynReplyFrame) if !ok { t.Fatal("Parsed incorrect frame type:", frame) } if !reflect.DeepEqual(synReplyFrame, *parsedSynReplyFrame) { t.Fatal("got: ", *parsedSynReplyFrame, "\nwant: ", synReplyFrame) } } func TestCreateParseRstStream(t *testing.T) { buffer := new(bytes.Buffer) framer, err := NewFramer(buffer, buffer) if err != nil { t.Fatal("Failed to create new framer:", err) } rstStreamFrame := RstStreamFrame{ CFHeader: ControlFrameHeader{ version: Version, frameType: TypeRstStream, }, StreamId: 1, Status: InvalidStream, } if err := framer.WriteFrame(&rstStreamFrame); err != nil { t.Fatal("WriteFrame:", err) } frame, err := framer.ReadFrame() if err != nil { t.Fatal("ReadFrame:", err) } parsedRstStreamFrame, ok := frame.(*RstStreamFrame) if !ok { t.Fatal("Parsed incorrect frame type:", frame) } if !reflect.DeepEqual(rstStreamFrame, *parsedRstStreamFrame) { t.Fatal("got: ", *parsedRstStreamFrame, "\nwant: ", rstStreamFrame) } } func TestCreateParseSettings(t *testing.T) { buffer := new(bytes.Buffer) framer, err := NewFramer(buffer, buffer) if err != nil { t.Fatal("Failed to create new framer:", err) } settingsFrame := SettingsFrame{ CFHeader: ControlFrameHeader{ version: Version, frameType: TypeSettings, }, FlagIdValues: []SettingsFlagIdValue{ {FlagSettingsPersistValue, SettingsCurrentCwnd, 10}, {FlagSettingsPersisted, SettingsUploadBandwidth, 1}, }, } if err := framer.WriteFrame(&settingsFrame); err != nil { t.Fatal("WriteFrame:", err) } frame, err := framer.ReadFrame() if err != nil { t.Fatal("ReadFrame:", err) } parsedSettingsFrame, ok := frame.(*SettingsFrame) if !ok { t.Fatal("Parsed incorrect frame type:", frame) } if !reflect.DeepEqual(settingsFrame, *parsedSettingsFrame) { t.Fatal("got: ", *parsedSettingsFrame, "\nwant: ", settingsFrame) } } func TestCreateParsePing(t *testing.T) { buffer := new(bytes.Buffer) framer, err := NewFramer(buffer, buffer) if err != nil { t.Fatal("Failed to create new framer:", err) } pingFrame := PingFrame{ CFHeader: ControlFrameHeader{ version: Version, frameType: TypePing, }, Id: 31337, } if err := framer.WriteFrame(&pingFrame); err != nil { t.Fatal("WriteFrame:", err) } if pingFrame.CFHeader.Flags != 0 { t.Fatal("Incorrect frame type:", pingFrame) } frame, err := framer.ReadFrame() if err != nil { t.Fatal("ReadFrame:", err) } parsedPingFrame, ok := frame.(*PingFrame) if !ok { t.Fatal("Parsed incorrect frame type:", frame) } if parsedPingFrame.CFHeader.Flags != 0 { t.Fatal("Parsed incorrect frame type:", parsedPingFrame) } if !reflect.DeepEqual(pingFrame, *parsedPingFrame) { t.Fatal("got: ", *parsedPingFrame, "\nwant: ", pingFrame) } } func TestCreateParseGoAway(t *testing.T) { buffer := new(bytes.Buffer) framer, err := NewFramer(buffer, buffer) if err != nil { t.Fatal("Failed to create new framer:", err) } goAwayFrame := GoAwayFrame{ CFHeader: ControlFrameHeader{ version: Version, frameType: TypeGoAway, }, LastGoodStreamId: 31337, Status: 1, } if err := framer.WriteFrame(&goAwayFrame); err != nil { t.Fatal("WriteFrame:", err) } if goAwayFrame.CFHeader.Flags != 0 { t.Fatal("Incorrect frame type:", goAwayFrame) } if goAwayFrame.CFHeader.length != 8 { t.Fatal("Incorrect frame type:", goAwayFrame) } frame, err := framer.ReadFrame() if err != nil { t.Fatal("ReadFrame:", err) } parsedGoAwayFrame, ok := frame.(*GoAwayFrame) if !ok { t.Fatal("Parsed incorrect frame type:", frame) } if parsedGoAwayFrame.CFHeader.Flags != 0 { t.Fatal("Incorrect frame type:", parsedGoAwayFrame) } if parsedGoAwayFrame.CFHeader.length != 8 { t.Fatal("Incorrect frame type:", parsedGoAwayFrame) } if !reflect.DeepEqual(goAwayFrame, *parsedGoAwayFrame) { t.Fatal("got: ", *parsedGoAwayFrame, "\nwant: ", goAwayFrame) } } func TestCreateParseHeadersFrame(t *testing.T) { buffer := new(bytes.Buffer) framer := &Framer{ headerCompressionDisabled: true, w: buffer, headerBuf: new(bytes.Buffer), r: buffer, } headersFrame := HeadersFrame{ CFHeader: ControlFrameHeader{ version: Version, frameType: TypeHeaders, }, StreamId: 2, } headersFrame.Headers = HeadersFixture if err := framer.WriteFrame(&headersFrame); err != nil { t.Fatal("WriteFrame without compression:", err) } frame, err := framer.ReadFrame() if err != nil { t.Fatal("ReadFrame without compression:", err) } parsedHeadersFrame, ok := frame.(*HeadersFrame) if !ok { t.Fatal("Parsed incorrect frame type:", frame) } if !reflect.DeepEqual(headersFrame, *parsedHeadersFrame) { t.Fatal("got: ", *parsedHeadersFrame, "\nwant: ", headersFrame) } } func TestCreateParseHeadersFrameCompressionEnable(t *testing.T) { buffer := new(bytes.Buffer) headersFrame := HeadersFrame{ CFHeader: ControlFrameHeader{ version: Version, frameType: TypeHeaders, }, StreamId: 2, } headersFrame.Headers = HeadersFixture framer, err := NewFramer(buffer, buffer) if err := framer.WriteFrame(&headersFrame); err != nil { t.Fatal("WriteFrame with compression:", err) } frame, err := framer.ReadFrame() if err != nil { t.Fatal("ReadFrame with compression:", err) } parsedHeadersFrame, ok := frame.(*HeadersFrame) if !ok { t.Fatal("Parsed incorrect frame type:", frame) } if !reflect.DeepEqual(headersFrame, *parsedHeadersFrame) { t.Fatal("got: ", *parsedHeadersFrame, "\nwant: ", headersFrame) } } func TestCreateParseWindowUpdateFrame(t *testing.T) { buffer := new(bytes.Buffer) framer, err := NewFramer(buffer, buffer) if err != nil { t.Fatal("Failed to create new framer:", err) } windowUpdateFrame := WindowUpdateFrame{ CFHeader: ControlFrameHeader{ version: Version, frameType: TypeWindowUpdate, }, StreamId: 31337, DeltaWindowSize: 1, } if err := framer.WriteFrame(&windowUpdateFrame); err != nil { t.Fatal("WriteFrame:", err) } if windowUpdateFrame.CFHeader.Flags != 0 { t.Fatal("Incorrect frame type:", windowUpdateFrame) } if windowUpdateFrame.CFHeader.length != 8 { t.Fatal("Incorrect frame type:", windowUpdateFrame) } frame, err := framer.ReadFrame() if err != nil { t.Fatal("ReadFrame:", err) } parsedWindowUpdateFrame, ok := frame.(*WindowUpdateFrame) if !ok { t.Fatal("Parsed incorrect frame type:", frame) } if parsedWindowUpdateFrame.CFHeader.Flags != 0 { t.Fatal("Incorrect frame type:", parsedWindowUpdateFrame) } if parsedWindowUpdateFrame.CFHeader.length != 8 { t.Fatal("Incorrect frame type:", parsedWindowUpdateFrame) } if !reflect.DeepEqual(windowUpdateFrame, *parsedWindowUpdateFrame) { t.Fatal("got: ", *parsedWindowUpdateFrame, "\nwant: ", windowUpdateFrame) } } func TestCreateParseDataFrame(t *testing.T) { buffer := new(bytes.Buffer) framer, err := NewFramer(buffer, buffer) if err != nil { t.Fatal("Failed to create new framer:", err) } dataFrame := DataFrame{ StreamId: 1, Data: []byte{'h', 'e', 'l', 'l', 'o'}, } if err := framer.WriteFrame(&dataFrame); err != nil { t.Fatal("WriteFrame:", err) } frame, err := framer.ReadFrame() if err != nil { t.Fatal("ReadFrame:", err) } parsedDataFrame, ok := frame.(*DataFrame) if !ok { t.Fatal("Parsed incorrect frame type:", frame) } if !reflect.DeepEqual(dataFrame, *parsedDataFrame) { t.Fatal("got: ", *parsedDataFrame, "\nwant: ", dataFrame) } } func TestCompressionContextAcrossFrames(t *testing.T) { buffer := new(bytes.Buffer) framer, err := NewFramer(buffer, buffer) if err != nil { t.Fatal("Failed to create new framer:", err) } headersFrame := HeadersFrame{ CFHeader: ControlFrameHeader{ version: Version, frameType: TypeHeaders, }, StreamId: 2, Headers: HeadersFixture, } if err := framer.WriteFrame(&headersFrame); err != nil { t.Fatal("WriteFrame (HEADERS):", err) } synStreamFrame := SynStreamFrame{ ControlFrameHeader{ Version, TypeSynStream, 0, // Flags 0, // length }, 2, // StreamId 0, // AssociatedTOStreamID 0, // Priority 1, // Slot nil, // Headers } synStreamFrame.Headers = HeadersFixture if err := framer.WriteFrame(&synStreamFrame); err != nil { t.Fatal("WriteFrame (SYN_STREAM):", err) } frame, err := framer.ReadFrame() if err != nil { t.Fatal("ReadFrame (HEADERS):", err, buffer.Bytes()) } parsedHeadersFrame, ok := frame.(*HeadersFrame) if !ok { t.Fatalf("expected HeadersFrame; got %T %v", frame, frame) } if !reflect.DeepEqual(headersFrame, *parsedHeadersFrame) { t.Fatal("got: ", *parsedHeadersFrame, "\nwant: ", headersFrame) } frame, err = framer.ReadFrame() if err != nil { t.Fatal("ReadFrame (SYN_STREAM):", err, buffer.Bytes()) } parsedSynStreamFrame, ok := frame.(*SynStreamFrame) if !ok { t.Fatalf("expected SynStreamFrame; got %T %v", frame, frame) } if !reflect.DeepEqual(synStreamFrame, *parsedSynStreamFrame) { t.Fatal("got: ", *parsedSynStreamFrame, "\nwant: ", synStreamFrame) } } func TestMultipleSPDYFrames(t *testing.T) { // Initialize the framers. pr1, pw1 := io.Pipe() pr2, pw2 := io.Pipe() writer, err := NewFramer(pw1, pr2) if err != nil { t.Fatal("Failed to create writer:", err) } reader, err := NewFramer(pw2, pr1) if err != nil { t.Fatal("Failed to create reader:", err) } // Set up the frames we're actually transferring. headersFrame := HeadersFrame{ CFHeader: ControlFrameHeader{ version: Version, frameType: TypeHeaders, }, StreamId: 2, Headers: HeadersFixture, } synStreamFrame := SynStreamFrame{ CFHeader: ControlFrameHeader{ version: Version, frameType: TypeSynStream, }, StreamId: 2, Headers: HeadersFixture, } // Start the goroutines to write the frames. go func() { if err := writer.WriteFrame(&headersFrame); err != nil { t.Fatal("WriteFrame (HEADERS): ", err) } if err := writer.WriteFrame(&synStreamFrame); err != nil { t.Fatal("WriteFrame (SYN_STREAM): ", err) } }() // Read the frames and verify they look as expected. frame, err := reader.ReadFrame() if err != nil { t.Fatal("ReadFrame (HEADERS): ", err) } parsedHeadersFrame, ok := frame.(*HeadersFrame) if !ok { t.Fatal("Parsed incorrect frame type:", frame) } if !reflect.DeepEqual(headersFrame, *parsedHeadersFrame) { t.Fatal("got: ", *parsedHeadersFrame, "\nwant: ", headersFrame) } frame, err = reader.ReadFrame() if err != nil { t.Fatal("ReadFrame (SYN_STREAM):", err) } parsedSynStreamFrame, ok := frame.(*SynStreamFrame) if !ok { t.Fatal("Parsed incorrect frame type.") } if !reflect.DeepEqual(synStreamFrame, *parsedSynStreamFrame) { t.Fatal("got: ", *parsedSynStreamFrame, "\nwant: ", synStreamFrame) } } func TestReadMalformedZlibHeader(t *testing.T) { // These were constructed by corrupting the first byte of the zlib // header after writing. malformedStructs := map[string]string{ "SynStreamFrame": "gAIAAQAAABgAAAACAAAAAAAAF/nfolGyYmAAAAAA//8=", "SynReplyFrame": "gAIAAgAAABQAAAACAAAX+d+iUbJiYAAAAAD//w==", "HeadersFrame": "gAIACAAAABQAAAACAAAX+d+iUbJiYAAAAAD//w==", } for name, bad := range malformedStructs { b, err := base64.StdEncoding.DecodeString(bad) if err != nil { t.Errorf("Unable to decode base64 encoded frame %s: %v", name, err) } buf := bytes.NewBuffer(b) reader, err := NewFramer(buf, buf) if err != nil { t.Fatalf("NewFramer: %v", err) } _, err = reader.ReadFrame() if err != zlib.ErrHeader { t.Errorf("Frame %s, expected: %#v, actual: %#v", name, zlib.ErrHeader, err) } } } // TODO: these tests are too weak for updating SPDY spec. Fix me. type zeroStream struct { frame Frame encoded string } var streamIdZeroFrames = map[string]zeroStream{ "SynStreamFrame": { &SynStreamFrame{StreamId: 0}, "gAIAAQAAABgAAAAAAAAAAAAAePnfolGyYmAAAAAA//8=", }, "SynReplyFrame": { &SynReplyFrame{StreamId: 0}, "gAIAAgAAABQAAAAAAAB4+d+iUbJiYAAAAAD//w==", }, "RstStreamFrame": { &RstStreamFrame{StreamId: 0}, "gAIAAwAAAAgAAAAAAAAAAA==", }, "HeadersFrame": { &HeadersFrame{StreamId: 0}, "gAIACAAAABQAAAAAAAB4+d+iUbJiYAAAAAD//w==", }, "DataFrame": { &DataFrame{StreamId: 0}, "AAAAAAAAAAA=", }, "PingFrame": { &PingFrame{Id: 0}, "gAIABgAAAAQAAAAA", }, } func TestNoZeroStreamId(t *testing.T) { t.Log("skipping") // TODO: update to work with SPDY3 return for name, f := range streamIdZeroFrames { b, err := base64.StdEncoding.DecodeString(f.encoded) if err != nil { t.Errorf("Unable to decode base64 encoded frame %s: %v", f, err) continue } framer, err := NewFramer(ioutil.Discard, bytes.NewReader(b)) if err != nil { t.Fatalf("NewFramer: %v", err) } err = framer.WriteFrame(f.frame) checkZeroStreamId(t, name, "WriteFrame", err) _, err = framer.ReadFrame() checkZeroStreamId(t, name, "ReadFrame", err) } } func checkZeroStreamId(t *testing.T, frame string, method string, err error) { if err == nil { t.Errorf("%s ZeroStreamId, no error on %s", method, frame) return } eerr, ok := err.(*Error) if !ok || eerr.Err != ZeroStreamId { t.Errorf("%s ZeroStreamId, incorrect error %#v, frame %s", method, eerr, frame) } }