/* Copyright 2014-2021 Docker Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // 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 != nil { t.Fatal("Failed to create new framer:", err) } 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.Error("WriteFrame (HEADERS):", err) } if err := writer.WriteFrame(&synStreamFrame); err != nil { t.Error("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.Skip("TODO: update to work with SPDY3") 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) } }