From: William Chan Date: Thu, 26 May 2011 16:54:54 +0000 (-0700) Subject: http/spdy: redo interfaces, flesh out implementation & frame types X-Git-Tag: weekly.2011-06-02~101 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=abb970ef57f3370f92c5079c073ec8a9414d5b17;p=gostls13.git http/spdy: redo interfaces, flesh out implementation & frame types Added a new Framer to handle reading/writing Frames. This is necessary since we have to maintain a compression context across streams. TODO: * Separate the types and read/write routines into different files. * Improve error handling. R=bradfitz, rsc CC=golang-dev https://golang.org/cl/4503042 --- diff --git a/src/pkg/http/spdy/Makefile b/src/pkg/http/spdy/Makefile index ff70d04375..e5842c2e47 100644 --- a/src/pkg/http/spdy/Makefile +++ b/src/pkg/http/spdy/Makefile @@ -6,6 +6,7 @@ include ../../../Make.inc TARG=http/spdy GOFILES=\ + framer.go\ protocol.go\ include ../../../Make.pkg diff --git a/src/pkg/http/spdy/framer.go b/src/pkg/http/spdy/framer.go new file mode 100644 index 0000000000..0ad2989d27 --- /dev/null +++ b/src/pkg/http/spdy/framer.go @@ -0,0 +1,422 @@ +// 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" + "encoding/binary" + "compress/zlib" + "http" + "io" + "os" + "strconv" + "strings" +) + +type FramerError int + +const ( + Internal FramerError = iota + InvalidControlFrame + UnlowercasedHeaderName + DuplicateHeaders + UnknownFrameType + InvalidDataFrame +) + +func (e FramerError) String() string { + switch e { + case Internal: + return "Internal" + case InvalidControlFrame: + return "InvalidControlFrame" + case UnlowercasedHeaderName: + return "UnlowercasedHeaderName" + case DuplicateHeaders: + return "DuplicateHeaders" + case UnknownFrameType: + return "UnknownFrameType" + case InvalidDataFrame: + return "InvalidDataFrame" + } + return "Error(" + strconv.Itoa(int(e)) + ")" +} + +// Framer handles serializing/deserializing SPDY frames, including compressing/ +// decompressing payloads. +type Framer struct { + headerCompressionDisabled bool + w io.Writer + headerBuf *bytes.Buffer + headerCompressor *zlib.Writer + r io.Reader + headerDecompressor io.ReadCloser +} + +// NewFramer allocates a new Framer for a given SPDY connection, repesented by +// a io.Writer and io.Reader. Note that Framer will read and write individual fields +// from/to the Reader and Writer, so the caller should pass in an appropriately +// buffered implementation to optimize performance. +func NewFramer(w io.Writer, r io.Reader) (*Framer, os.Error) { + compressBuf := new(bytes.Buffer) + compressor, err := zlib.NewWriterDict(compressBuf, zlib.BestCompression, []byte(HeaderDictionary)) + if err != nil { + return nil, err + } + framer := &Framer{ + w: w, + headerBuf: compressBuf, + headerCompressor: compressor, + r: r, + } + return framer, nil +} + +func (f *Framer) initHeaderDecompression() os.Error { + if f.headerDecompressor != nil { + return nil + } + decompressor, err := zlib.NewReaderDict(f.r, []byte(HeaderDictionary)) + if err != nil { + return err + } + f.headerDecompressor = decompressor + return nil +} + +// ReadFrame reads SPDY encoded data and returns a decompressed Frame. +func (f *Framer) ReadFrame() (Frame, os.Error) { + var firstWord uint32 + if err := binary.Read(f.r, binary.BigEndian, &firstWord); err != nil { + return nil, err + } + if (firstWord & 0x80000000) != 0 { + frameType := ControlFrameType(firstWord & 0xffff) + version := uint16(0x7fff & (firstWord >> 16)) + return f.parseControlFrame(version, frameType) + } + return f.parseDataFrame(firstWord & 0x7fffffff) +} + +func (f *Framer) parseControlFrame(version uint16, frameType ControlFrameType) (Frame, os.Error) { + var length uint32 + if err := binary.Read(f.r, binary.BigEndian, &length); err != nil { + return nil, err + } + flags := ControlFlags((length & 0xff000000) >> 24) + length &= 0xffffff + header := ControlFrameHeader{version, frameType, flags, length} + cframe, err := newControlFrame(frameType) + if err != nil { + return nil, err + } + if err = cframe.read(header, f); err != nil { + return nil, err + } + return cframe, nil +} + +func parseHeaderValueBlock(r io.Reader) (http.Header, os.Error) { + var numHeaders uint16 + if err := binary.Read(r, binary.BigEndian, &numHeaders); err != nil { + return nil, err + } + h := make(http.Header, int(numHeaders)) + for i := 0; i < int(numHeaders); i++ { + var length uint16 + if err := binary.Read(r, binary.BigEndian, &length); err != nil { + return nil, err + } + nameBytes := make([]byte, length) + if _, err := io.ReadFull(r, nameBytes); err != nil { + return nil, err + } + name := string(nameBytes) + if name != strings.ToLower(name) { + return nil, UnlowercasedHeaderName + } + if h[name] != nil { + return nil, DuplicateHeaders + } + if err := binary.Read(r, binary.BigEndian, &length); err != nil { + return nil, err + } + value := make([]byte, length) + if _, err := io.ReadFull(r, value); err != nil { + return nil, err + } + valueList := strings.Split(string(value), "\x00", -1) + for _, v := range valueList { + h.Add(name, v) + } + } + return h, nil +} + +func (f *Framer) readSynStreamFrame(h ControlFrameHeader, frame *SynStreamFrame) os.Error { + frame.CFHeader = h + var err os.Error + if err = binary.Read(f.r, binary.BigEndian, &frame.StreamId); err != nil { + return err + } + if err = binary.Read(f.r, binary.BigEndian, &frame.AssociatedToStreamId); err != nil { + return err + } + if err = binary.Read(f.r, binary.BigEndian, &frame.Priority); err != nil { + return err + } + frame.Priority >>= 14 + + reader := f.r + if !f.headerCompressionDisabled { + f.initHeaderDecompression() + reader = f.headerDecompressor + } + + frame.Headers, err = parseHeaderValueBlock(reader) + if err != nil { + return err + } + return nil +} + +func (f *Framer) readSynReplyFrame(h ControlFrameHeader, frame *SynReplyFrame) os.Error { + frame.CFHeader = h + var err os.Error + if err = binary.Read(f.r, binary.BigEndian, &frame.StreamId); err != nil { + return err + } + var unused uint16 + if err = binary.Read(f.r, binary.BigEndian, &unused); err != nil { + return err + } + reader := f.r + if !f.headerCompressionDisabled { + f.initHeaderDecompression() + reader = f.headerDecompressor + } + frame.Headers, err = parseHeaderValueBlock(reader) + if err != nil { + return err + } + return nil +} + +func (f *Framer) readHeadersFrame(h ControlFrameHeader, frame *HeadersFrame) os.Error { + frame.CFHeader = h + var err os.Error + if err = binary.Read(f.r, binary.BigEndian, &frame.StreamId); err != nil { + return err + } + var unused uint16 + if err = binary.Read(f.r, binary.BigEndian, &unused); err != nil { + return err + } + reader := f.r + if !f.headerCompressionDisabled { + f.initHeaderDecompression() + reader = f.headerDecompressor + } + frame.Headers, err = parseHeaderValueBlock(reader) + if err != nil { + return err + } + return nil +} + +func (f *Framer) parseDataFrame(streamId uint32) (*DataFrame, os.Error) { + var length uint32 + if err := binary.Read(f.r, binary.BigEndian, &length); err != nil { + return nil, err + } + var frame DataFrame + frame.StreamId = streamId + frame.Flags = DataFlags(length >> 24) + length &= 0xffffff + frame.Data = make([]byte, length) + // TODO(willchan): Support compressed data frames. + if _, err := io.ReadFull(f.r, frame.Data); err != nil { + return nil, err + } + return &frame, nil +} + +// WriteFrame writes a frame. +func (f *Framer) WriteFrame(frame Frame) os.Error { + return frame.write(f) +} + +func writeControlFrameHeader(w io.Writer, h ControlFrameHeader) os.Error { + if err := binary.Write(w, binary.BigEndian, 0x8000|h.version); err != nil { + return err + } + if err := binary.Write(w, binary.BigEndian, h.frameType); err != nil { + return err + } + flagsAndLength := (uint32(h.Flags) << 24) | h.length + if err := binary.Write(w, binary.BigEndian, flagsAndLength); err != nil { + return err + } + return nil +} + +func writeHeaderValueBlock(w io.Writer, h http.Header) (n int, err os.Error) { + n = 0 + if err = binary.Write(w, binary.BigEndian, uint16(len(h))); err != nil { + return + } + n += 2 + for name, values := range h { + if err = binary.Write(w, binary.BigEndian, uint16(len(name))); err != nil { + return + } + n += 2 + name = strings.ToLower(name) + if _, err = io.WriteString(w, name); err != nil { + return + } + n += len(name) + v := strings.Join(values, "\x00") + if err = binary.Write(w, binary.BigEndian, uint16(len(v))); err != nil { + return + } + n += 2 + if _, err = io.WriteString(w, v); err != nil { + return + } + n += len(v) + } + return +} + +func (f *Framer) writeSynStreamFrame(frame *SynStreamFrame) (err os.Error) { + // Marshal the headers. + var writer io.Writer = f.headerBuf + if !f.headerCompressionDisabled { + writer = f.headerCompressor + } + if _, err = writeHeaderValueBlock(writer, frame.Headers); err != nil { + return + } + if !f.headerCompressionDisabled { + f.headerCompressor.Flush() + } + + // Set ControlFrameHeader + frame.CFHeader.version = Version + frame.CFHeader.frameType = TypeSynStream + frame.CFHeader.length = uint32(len(f.headerBuf.Bytes()) + 10) + + // Serialize frame to Writer + if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil { + return err + } + if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil { + return err + } + if err = binary.Write(f.w, binary.BigEndian, frame.AssociatedToStreamId); err != nil { + return err + } + if err = binary.Write(f.w, binary.BigEndian, frame.Priority<<14); err != nil { + return err + } + if _, err = f.w.Write(f.headerBuf.Bytes()); err != nil { + return err + } + f.headerBuf.Reset() + return nil +} + +func (f *Framer) writeSynReplyFrame(frame *SynReplyFrame) (err os.Error) { + // Marshal the headers. + var writer io.Writer = f.headerBuf + if !f.headerCompressionDisabled { + writer = f.headerCompressor + } + if _, err = writeHeaderValueBlock(writer, frame.Headers); err != nil { + return + } + if !f.headerCompressionDisabled { + f.headerCompressor.Flush() + } + + // Set ControlFrameHeader + frame.CFHeader.version = Version + frame.CFHeader.frameType = TypeSynReply + frame.CFHeader.length = uint32(len(f.headerBuf.Bytes()) + 6) + + // Serialize frame to Writer + if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil { + return + } + if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil { + return + } + if err = binary.Write(f.w, binary.BigEndian, uint16(0)); err != nil { + return + } + if _, err = f.w.Write(f.headerBuf.Bytes()); err != nil { + return + } + f.headerBuf.Reset() + return +} + +func (f *Framer) writeHeadersFrame(frame *HeadersFrame) (err os.Error) { + // Marshal the headers. + var writer io.Writer = f.headerBuf + if !f.headerCompressionDisabled { + writer = f.headerCompressor + } + if _, err = writeHeaderValueBlock(writer, frame.Headers); err != nil { + return + } + if !f.headerCompressionDisabled { + f.headerCompressor.Flush() + } + + // Set ControlFrameHeader + frame.CFHeader.version = Version + frame.CFHeader.frameType = TypeHeaders + frame.CFHeader.length = uint32(len(f.headerBuf.Bytes()) + 6) + + // Serialize frame to Writer + if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil { + return + } + if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil { + return + } + if err = binary.Write(f.w, binary.BigEndian, uint16(0)); err != nil { + return + } + if _, err = f.w.Write(f.headerBuf.Bytes()); err != nil { + return + } + f.headerBuf.Reset() + return +} + +func (f *Framer) writeDataFrame(frame *DataFrame) (err os.Error) { + // Validate DataFrame + if frame.StreamId&0x80000000 != 0 || len(frame.Data) >= 0x0f000000 { + return InvalidDataFrame + } + + // TODO(willchan): Support data compression. + // Serialize frame to Writer + if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil { + return + } + flagsAndLength := (uint32(frame.Flags) << 24) | uint32(len(frame.Data)) + if err = binary.Write(f.w, binary.BigEndian, flagsAndLength); err != nil { + return + } + if _, err = f.w.Write(frame.Data); err != nil { + return + } + + return nil +} diff --git a/src/pkg/http/spdy/framer_test.go b/src/pkg/http/spdy/framer_test.go new file mode 100644 index 0000000000..54420e26bc --- /dev/null +++ b/src/pkg/http/spdy/framer_test.go @@ -0,0 +1,496 @@ +// 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" + "http" + "io" + "reflect" + "testing" +) + +func TestHeaderParsing(t *testing.T) { + headers := http.Header{ + "Url": []string{"http://www.google.com/"}, + "Method": []string{"get"}, + "Version": []string{"http/1.1"}, + } + var headerValueBlockBuf bytes.Buffer + writeHeaderValueBlock(&headerValueBlockBuf, headers) + + newHeaders, err := parseHeaderValueBlock(&headerValueBlockBuf) + if err != nil { + t.Fatal("parseHeaderValueBlock:", err) + } + + if !reflect.DeepEqual(headers, newHeaders) { + t.Fatal("got: ", newHeaders, "\nwant: ", headers) + } +} + +func TestCreateParseSynStreamFrame(t *testing.T) { + buffer := new(bytes.Buffer) + framer := &Framer{ + headerCompressionDisabled: true, + w: buffer, + headerBuf: new(bytes.Buffer), + r: buffer, + } + synStreamFrame := SynStreamFrame{ + CFHeader: ControlFrameHeader{ + version: Version, + frameType: TypeSynStream, + }, + Headers: http.Header{ + "Url": []string{"http://www.google.com/"}, + "Method": []string{"get"}, + "Version": []string{"http/1.1"}, + }, + } + 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) + } + + // Test again with compression + buffer.Reset() + framer, err = NewFramer(buffer, buffer) + 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 TestCreateParseSynReplyFrame(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, + }, + Headers: http.Header{ + "Url": []string{"http://www.google.com/"}, + "Method": []string{"get"}, + "Version": []string{"http/1.1"}, + }, + } + 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) + } + + // Test again with compression + buffer.Reset() + framer, err = NewFramer(buffer, buffer) + 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 TestCreateParseNoop(t *testing.T) { + buffer := new(bytes.Buffer) + framer, err := NewFramer(buffer, buffer) + if err != nil { + t.Fatal("Failed to create new framer:", err) + } + noopFrame := NoopFrame{ + CFHeader: ControlFrameHeader{ + version: Version, + frameType: TypeNoop, + }, + } + if err := framer.WriteFrame(&noopFrame); err != nil { + t.Fatal("WriteFrame:", err) + } + frame, err := framer.ReadFrame() + if err != nil { + t.Fatal("ReadFrame:", err) + } + parsedNoopFrame, ok := frame.(*NoopFrame) + if !ok { + t.Fatal("Parsed incorrect frame type:", frame) + } + if !reflect.DeepEqual(noopFrame, *parsedNoopFrame) { + t.Fatal("got: ", *parsedNoopFrame, "\nwant: ", noopFrame) + } +} + +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) + } + 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 !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, + } + if err := framer.WriteFrame(&goAwayFrame); err != nil { + t.Fatal("WriteFrame:", err) + } + 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 !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, + }, + } + headersFrame.Headers = http.Header{ + "Url": []string{"http://www.google.com/"}, + "Method": []string{"get"}, + "Version": []string{"http/1.1"}, + } + 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) + } + + // Test again with compression + buffer.Reset() + 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 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, + }, + Headers: http.Header{ + "Url": []string{"http://www.google.com/"}, + "Method": []string{"get"}, + "Version": []string{"http/1.1"}, + }, + } + if err := framer.WriteFrame(&headersFrame); err != nil { + t.Fatal("WriteFrame (HEADERS):", err) + } + synStreamFrame := SynStreamFrame{ControlFrameHeader{Version, TypeSynStream, 0, 0}, 0, 0, 0, nil} + synStreamFrame.Headers = http.Header{ + "Url": []string{"http://www.google.com/"}, + "Method": []string{"get"}, + "Version": []string{"http/1.1"}, + } + 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.Fatal("Parsed incorrect frame type:", 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.Fatal("Parsed incorrect frame type:", 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, + }, + Headers: http.Header{ + "Url": []string{"http://www.google.com/"}, + "Method": []string{"get"}, + "Version": []string{"http/1.1"}, + }, + } + synStreamFrame := SynStreamFrame{ + CFHeader: ControlFrameHeader{ + version: Version, + frameType: TypeSynStream, + }, + Headers: http.Header{ + "Url": []string{"http://www.google.com/"}, + "Method": []string{"get"}, + "Version": []string{"http/1.1"}, + }, + } + + // 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) + } +} diff --git a/src/pkg/http/spdy/protocol.go b/src/pkg/http/spdy/protocol.go index ad9aa63335..25b138f389 100644 --- a/src/pkg/http/spdy/protocol.go +++ b/src/pkg/http/spdy/protocol.go @@ -9,17 +9,120 @@ package spdy import ( - "bytes" - "compress/zlib" "encoding/binary" "http" - "io" "os" - "strconv" - "strings" - "sync" ) +// Data Frame Format +// +----------------------------------+ +// |0| Stream-ID (31bits) | +// +----------------------------------+ +// | flags (8) | Length (24 bits) | +// +----------------------------------+ +// | Data | +// +----------------------------------+ +// +// Control Frame Format +// +----------------------------------+ +// |1| Version(15bits) | Type(16bits) | +// +----------------------------------+ +// | flags (8) | Length (24 bits) | +// +----------------------------------+ +// | Data | +// +----------------------------------+ +// +// Control Frame: SYN_STREAM +// +----------------------------------+ +// |1|000000000000001|0000000000000001| +// +----------------------------------+ +// | flags (8) | Length (24 bits) | >= 12 +// +----------------------------------+ +// |X| Stream-ID(31bits) | +// +----------------------------------+ +// |X|Associated-To-Stream-ID (31bits)| +// +----------------------------------+ +// |Pri| unused | Length (16bits)| +// +----------------------------------+ +// +// Control Frame: SYN_REPLY +// +----------------------------------+ +// |1|000000000000001|0000000000000010| +// +----------------------------------+ +// | flags (8) | Length (24 bits) | >= 8 +// +----------------------------------+ +// |X| Stream-ID(31bits) | +// +----------------------------------+ +// | unused (16 bits)| Length (16bits)| +// +----------------------------------+ +// +// Control Frame: RST_STREAM +// +----------------------------------+ +// |1|000000000000001|0000000000000011| +// +----------------------------------+ +// | flags (8) | Length (24 bits) | >= 4 +// +----------------------------------+ +// |X| Stream-ID(31bits) | +// +----------------------------------+ +// | Status code (32 bits) | +// +----------------------------------+ +// +// Control Frame: SETTINGS +// +----------------------------------+ +// |1|000000000000001|0000000000000100| +// +----------------------------------+ +// | flags (8) | Length (24 bits) | +// +----------------------------------+ +// | # of entries (32) | +// +----------------------------------+ +// +// Control Frame: NOOP +// +----------------------------------+ +// |1|000000000000001|0000000000000101| +// +----------------------------------+ +// | flags (8) | Length (24 bits) | = 0 +// +----------------------------------+ +// +// Control Frame: PING +// +----------------------------------+ +// |1|000000000000001|0000000000000110| +// +----------------------------------+ +// | flags (8) | Length (24 bits) | = 4 +// +----------------------------------+ +// | Unique id (32 bits) | +// +----------------------------------+ +// +// Control Frame: GOAWAY +// +----------------------------------+ +// |1|000000000000001|0000000000000111| +// +----------------------------------+ +// | flags (8) | Length (24 bits) | = 4 +// +----------------------------------+ +// |X| Last-accepted-stream-id | +// +----------------------------------+ +// +// Control Frame: HEADERS +// +----------------------------------+ +// |1|000000000000001|0000000000001000| +// +----------------------------------+ +// | flags (8) | Length (24 bits) | >= 8 +// +----------------------------------+ +// |X| Stream-ID (31 bits) | +// +----------------------------------+ +// | unused (16 bits)| Length (16bits)| +// +----------------------------------+ +// +// Control Frame: WINDOW_UPDATE +// +----------------------------------+ +// |1|000000000000001|0000000000001001| +// +----------------------------------+ +// | flags (8) | Length (24 bits) | = 8 +// +----------------------------------+ +// |X| Stream-ID (31 bits) | +// +----------------------------------+ +// | Delta-Window-Size (32 bits) | +// +----------------------------------+ + // Version is the protocol version number that this package implements. const Version = 2 @@ -34,334 +137,348 @@ const ( TypeSettings = 0x0004 TypeNoop = 0x0005 TypePing = 0x0006 - TypeGoaway = 0x0007 + TypeGoAway = 0x0007 TypeHeaders = 0x0008 TypeWindowUpdate = 0x0009 ) -func (t ControlFrameType) String() string { - switch t { - case TypeSynStream: - return "SYN_STREAM" - case TypeSynReply: - return "SYN_REPLY" - case TypeRstStream: - return "RST_STREAM" - case TypeSettings: - return "SETTINGS" - case TypeNoop: - return "NOOP" - case TypePing: - return "PING" - case TypeGoaway: - return "GOAWAY" - case TypeHeaders: - return "HEADERS" - case TypeWindowUpdate: - return "WINDOW_UPDATE" - } - return "Type(" + strconv.Itoa(int(t)) + ")" -} - -type FrameFlags uint8 +// ControlFlags are the flags that can be set on a control frame. +type ControlFlags uint8 -// Stream frame flags const ( - FlagFin FrameFlags = 0x01 - FlagUnidirectional = 0x02 + ControlFlagFin ControlFlags = 0x01 ) -// SETTINGS frame flags +// DataFlags are the flags that can be set on a data frame. +type DataFlags uint8 + const ( - FlagClearPreviouslyPersistedSettings FrameFlags = 0x01 + DataFlagFin DataFlags = 0x01 + DataFlagCompressed = 0x02 ) // MaxDataLength is the maximum number of bytes that can be stored in one frame. const MaxDataLength = 1<<24 - 1 -// A Frame is a framed message as sent between clients and servers. -// There are two types of frames: control frames and data frames. -type Frame struct { - Header [4]byte - Flags FrameFlags - Data []byte -} - -// ControlFrame creates a control frame with the given information. -func ControlFrame(t ControlFrameType, f FrameFlags, data []byte) Frame { - return Frame{ - Header: [4]byte{ - (Version&0xff00)>>8 | 0x80, - (Version & 0x00ff), - byte((t & 0xff00) >> 8), - byte((t & 0x00ff) >> 0), - }, - Flags: f, - Data: data, - } +// Frame is a single SPDY frame in its unpacked in-memory representation. Use +// Framer to read and write it. +type Frame interface { + write(f *Framer) os.Error } -// DataFrame creates a data frame with the given information. -func DataFrame(streamId uint32, f FrameFlags, data []byte) Frame { - return Frame{ - Header: [4]byte{ - byte(streamId & 0x7f000000 >> 24), - byte(streamId & 0x00ff0000 >> 16), - byte(streamId & 0x0000ff00 >> 8), - byte(streamId & 0x000000ff >> 0), - }, - Flags: f, - Data: data, - } +// ControlFrameHeader contains all the fields in a control frame header, +// in its unpacked in-memory representation. +type ControlFrameHeader struct { + // Note, high bit is the "Control" bit. + version uint16 + frameType ControlFrameType + Flags ControlFlags + length uint32 +} + +type controlFrame interface { + Frame + read(h ControlFrameHeader, f *Framer) os.Error +} + +// SynStreamFrame is the unpacked, in-memory representation of a SYN_STREAM +// frame. +type SynStreamFrame struct { + CFHeader ControlFrameHeader + StreamId uint32 + AssociatedToStreamId uint32 + // Note, only 2 highest bits currently used + // Rest of Priority is unused. + Priority uint16 + Headers http.Header +} + +func (frame *SynStreamFrame) write(f *Framer) os.Error { + return f.writeSynStreamFrame(frame) +} + +func (frame *SynStreamFrame) read(h ControlFrameHeader, f *Framer) os.Error { + return f.readSynStreamFrame(h, frame) +} + +// SynReplyFrame is the unpacked, in-memory representation of a SYN_REPLY frame. +type SynReplyFrame struct { + CFHeader ControlFrameHeader + StreamId uint32 + Headers http.Header +} + +func (frame *SynReplyFrame) write(f *Framer) os.Error { + return f.writeSynReplyFrame(frame) +} + +func (frame *SynReplyFrame) read(h ControlFrameHeader, f *Framer) os.Error { + return f.readSynReplyFrame(h, frame) } -// ReadFrame reads an entire frame into memory. -func ReadFrame(r io.Reader) (f Frame, err os.Error) { - _, err = io.ReadFull(r, f.Header[:]) - if err != nil { +// StatusCode represents the status that led to a RST_STREAM +type StatusCode uint32 + +const ( + ProtocolError StatusCode = 1 + InvalidStream = 2 + RefusedStream = 3 + UnsupportedVersion = 4 + Cancel = 5 + InternalError = 6 + FlowControlError = 7 +) + +// RstStreamFrame is the unpacked, in-memory representation of a RST_STREAM +// frame. +type RstStreamFrame struct { + CFHeader ControlFrameHeader + StreamId uint32 + Status StatusCode +} + +func (frame *RstStreamFrame) write(f *Framer) (err os.Error) { + frame.CFHeader.version = Version + frame.CFHeader.frameType = TypeRstStream + frame.CFHeader.length = 8 + + // Serialize frame to Writer + if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil { return } - err = binary.Read(r, binary.BigEndian, &f.Flags) - if err != nil { + if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil { return } - var lengthField [3]byte - _, err = io.ReadFull(r, lengthField[:]) - if err != nil { - if err == os.EOF { - err = io.ErrUnexpectedEOF - } + if err = binary.Write(f.w, binary.BigEndian, frame.Status); err != nil { return } - var length uint32 - length |= uint32(lengthField[0]) << 16 - length |= uint32(lengthField[1]) << 8 - length |= uint32(lengthField[2]) << 0 - if length > 0 { - f.Data = make([]byte, int(length)) - _, err = io.ReadFull(r, f.Data) - if err == os.EOF { - err = io.ErrUnexpectedEOF - } - } else { - f.Data = []byte{} - } return } -// IsControl returns whether the frame holds a control frame. -func (f Frame) IsControl() bool { - return f.Header[0]&0x80 != 0 +func (frame *RstStreamFrame) read(h ControlFrameHeader, f *Framer) os.Error { + frame.CFHeader = h + if err := binary.Read(f.r, binary.BigEndian, &frame.StreamId); err != nil { + return err + } + if err := binary.Read(f.r, binary.BigEndian, &frame.Status); err != nil { + return err + } + return nil } -// Type obtains the type field if the frame is a control frame, otherwise it returns zero. -func (f Frame) Type() ControlFrameType { - if !f.IsControl() { - return 0 - } - return (ControlFrameType(f.Header[2])<<8 | ControlFrameType(f.Header[3])) +// SettingsFlag represents a flag in a SETTINGS frame. +type SettingsFlag uint8 + +const ( + FlagSettingsPersistValue SettingsFlag = 0x1 + FlagSettingsPersisted = 0x2 +) + +// SettingsFlag represents the id of an id/value pair in a SETTINGS frame. +type SettingsId uint32 + +const ( + SettingsUploadBandwidth SettingsId = 1 + SettingsDownloadBandwidth = 2 + SettingsRoundTripTime = 3 + SettingsMaxConcurrentStreams = 4 + SettingsCurrentCwnd = 5 +) + +// SettingsFlagIdValue is the unpacked, in-memory representation of the +// combined flag/id/value for a setting in a SETTINGS frame. +type SettingsFlagIdValue struct { + Flag SettingsFlag + Id SettingsId + Value uint32 } -// StreamId returns the stream ID field if the frame is a data frame, otherwise it returns zero. -func (f Frame) StreamId() (id uint32) { - if f.IsControl() { - return 0 - } - id |= uint32(f.Header[0]) << 24 - id |= uint32(f.Header[1]) << 16 - id |= uint32(f.Header[2]) << 8 - id |= uint32(f.Header[3]) << 0 - return +// SettingsFrame is the unpacked, in-memory representation of a SPDY +// SETTINGS frame. +type SettingsFrame struct { + CFHeader ControlFrameHeader + FlagIdValues []SettingsFlagIdValue } -// WriteTo writes the frame in the SPDY format. -func (f Frame) WriteTo(w io.Writer) (n int64, err os.Error) { - var nn int - // Header - nn, err = w.Write(f.Header[:]) - n += int64(nn) - if err != nil { - return - } - // Flags - nn, err = w.Write([]byte{byte(f.Flags)}) - n += int64(nn) - if err != nil { +func (frame *SettingsFrame) write(f *Framer) (err os.Error) { + frame.CFHeader.version = Version + frame.CFHeader.frameType = TypeSettings + frame.CFHeader.length = uint32(len(frame.FlagIdValues)*8 + 4) + + // Serialize frame to Writer + if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil { return } - // Length - nn, err = w.Write([]byte{ - byte(len(f.Data) & 0x00ff0000 >> 16), - byte(len(f.Data) & 0x0000ff00 >> 8), - byte(len(f.Data) & 0x000000ff), - }) - n += int64(nn) - if err != nil { + if err = binary.Write(f.w, binary.BigEndian, uint32(len(frame.FlagIdValues))); err != nil { return } - // Data - if len(f.Data) > 0 { - nn, err = w.Write(f.Data) - n += int64(nn) + for _, flagIdValue := range frame.FlagIdValues { + flagId := (uint32(flagIdValue.Flag) << 24) | uint32(flagIdValue.Id) + if err = binary.Write(f.w, binary.BigEndian, flagId); err != nil { + return + } + if err = binary.Write(f.w, binary.BigEndian, flagIdValue.Value); err != nil { + return + } } return } -// headerDictionary is the dictionary sent to the zlib compressor/decompressor. -// Even though the specification states there is no null byte at the end, Chrome sends it. -const headerDictionary = "optionsgetheadpostputdeletetrace" + - "acceptaccept-charsetaccept-encodingaccept-languageauthorizationexpectfromhost" + - "if-modified-sinceif-matchif-none-matchif-rangeif-unmodifiedsince" + - "max-forwardsproxy-authorizationrangerefererteuser-agent" + - "100101200201202203204205206300301302303304305306307400401402403404405406407408409410411412413414415416417500501502503504505" + - "accept-rangesageetaglocationproxy-authenticatepublicretry-after" + - "servervarywarningwww-authenticateallowcontent-basecontent-encodingcache-control" + - "connectiondatetrailertransfer-encodingupgradeviawarning" + - "content-languagecontent-lengthcontent-locationcontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookie" + - "MondayTuesdayWednesdayThursdayFridaySaturdaySunday" + - "JanFebMarAprMayJunJulAugSepOctNovDec" + - "chunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplication/xhtmltext/plainpublicmax-age" + - "charset=iso-8859-1utf-8gzipdeflateHTTP/1.1statusversionurl\x00" - -// hrSource is a reader that passes through reads from another reader. -// When the underlying reader reaches EOF, Read will block until another reader is added via change. -type hrSource struct { - r io.Reader - m sync.RWMutex - c *sync.Cond -} - -func (src *hrSource) Read(p []byte) (n int, err os.Error) { - src.m.RLock() - for src.r == nil { - src.c.Wait() +func (frame *SettingsFrame) read(h ControlFrameHeader, f *Framer) os.Error { + frame.CFHeader = h + var numSettings uint32 + if err := binary.Read(f.r, binary.BigEndian, &numSettings); err != nil { + return err } - n, err = src.r.Read(p) - src.m.RUnlock() - if err == os.EOF { - src.change(nil) - err = nil + frame.FlagIdValues = make([]SettingsFlagIdValue, numSettings) + for i := uint32(0); i < numSettings; i++ { + if err := binary.Read(f.r, binary.BigEndian, &frame.FlagIdValues[i].Id); err != nil { + return err + } + frame.FlagIdValues[i].Flag = SettingsFlag((frame.FlagIdValues[i].Id & 0xff000000) >> 24) + frame.FlagIdValues[i].Id &= 0xffffff + if err := binary.Read(f.r, binary.BigEndian, &frame.FlagIdValues[i].Value); err != nil { + return err + } } - return + return nil } -func (src *hrSource) change(r io.Reader) { - src.m.Lock() - defer src.m.Unlock() - src.r = r - src.c.Broadcast() +// NoopFrame is the unpacked, in-memory representation of a NOOP frame. +type NoopFrame struct { + CFHeader ControlFrameHeader } -// A HeaderReader reads zlib-compressed headers. -type HeaderReader struct { - source hrSource - decompressor io.ReadCloser -} +func (frame *NoopFrame) write(f *Framer) os.Error { + frame.CFHeader.version = Version + frame.CFHeader.frameType = TypeNoop -// NewHeaderReader creates a HeaderReader with the initial dictionary. -func NewHeaderReader() (hr *HeaderReader) { - hr = new(HeaderReader) - hr.source.c = sync.NewCond(hr.source.m.RLocker()) - return + // Serialize frame to Writer + return writeControlFrameHeader(f.w, frame.CFHeader) } -// ReadHeader reads a set of headers from a reader. -func (hr *HeaderReader) ReadHeader(r io.Reader) (h http.Header, err os.Error) { - hr.source.change(r) - h, err = hr.read() - return +func (frame *NoopFrame) read(h ControlFrameHeader, f *Framer) os.Error { + frame.CFHeader = h + return nil } -// Decode reads a set of headers from a block of bytes. -func (hr *HeaderReader) Decode(data []byte) (h http.Header, err os.Error) { - hr.source.change(bytes.NewBuffer(data)) - h, err = hr.read() - return +// PingFrame is the unpacked, in-memory representation of a PING frame. +type PingFrame struct { + CFHeader ControlFrameHeader + Id uint32 } -func (hr *HeaderReader) read() (h http.Header, err os.Error) { - var count uint16 - if hr.decompressor == nil { - hr.decompressor, err = zlib.NewReaderDict(&hr.source, []byte(headerDictionary)) - if err != nil { - return - } - } - err = binary.Read(hr.decompressor, binary.BigEndian, &count) - if err != nil { +func (frame *PingFrame) write(f *Framer) (err os.Error) { + frame.CFHeader.version = Version + frame.CFHeader.frameType = TypePing + frame.CFHeader.length = 4 + + // Serialize frame to Writer + if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil { return } - h = make(http.Header, int(count)) - for i := 0; i < int(count); i++ { - var name, value string - name, err = readHeaderString(hr.decompressor) - if err != nil { - return - } - value, err = readHeaderString(hr.decompressor) - if err != nil { - return - } - valueList := strings.Split(string(value), "\x00", -1) - for _, v := range valueList { - h.Add(name, v) - } + if err = binary.Write(f.w, binary.BigEndian, frame.Id); err != nil { + return } return } -func readHeaderString(r io.Reader) (s string, err os.Error) { - var length uint16 - err = binary.Read(r, binary.BigEndian, &length) - if err != nil { +func (frame *PingFrame) read(h ControlFrameHeader, f *Framer) os.Error { + frame.CFHeader = h + if err := binary.Read(f.r, binary.BigEndian, &frame.Id); err != nil { + return err + } + return nil +} + +// GoAwayFrame is the unpacked, in-memory representation of a GOAWAY frame. +type GoAwayFrame struct { + CFHeader ControlFrameHeader + LastGoodStreamId uint32 +} + +func (frame *GoAwayFrame) write(f *Framer) (err os.Error) { + frame.CFHeader.version = Version + frame.CFHeader.frameType = TypeGoAway + frame.CFHeader.length = 4 + + // Serialize frame to Writer + if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil { return } - data := make([]byte, int(length)) - _, err = io.ReadFull(r, data) - if err != nil { + if err = binary.Write(f.w, binary.BigEndian, frame.LastGoodStreamId); err != nil { return } - return string(data), nil + return nil } -// HeaderWriter will write zlib-compressed headers on different streams. -type HeaderWriter struct { - compressor *zlib.Writer - buffer *bytes.Buffer +func (frame *GoAwayFrame) read(h ControlFrameHeader, f *Framer) os.Error { + frame.CFHeader = h + if err := binary.Read(f.r, binary.BigEndian, &frame.LastGoodStreamId); err != nil { + return err + } + return nil } -// NewHeaderWriter creates a HeaderWriter ready to compress headers. -func NewHeaderWriter(level int) (hw *HeaderWriter) { - hw = &HeaderWriter{buffer: new(bytes.Buffer)} - hw.compressor, _ = zlib.NewWriterDict(hw.buffer, level, []byte(headerDictionary)) - return +// HeadersFrame is the unpacked, in-memory representation of a HEADERS frame. +type HeadersFrame struct { + CFHeader ControlFrameHeader + StreamId uint32 + Headers http.Header } -// WriteHeader writes a header block directly to an output. -func (hw *HeaderWriter) WriteHeader(w io.Writer, h http.Header) (err os.Error) { - hw.write(h) - _, err = io.Copy(w, hw.buffer) - hw.buffer.Reset() - return +func (frame *HeadersFrame) write(f *Framer) os.Error { + return f.writeHeadersFrame(frame) } -// Encode returns a compressed header block. -func (hw *HeaderWriter) Encode(h http.Header) (data []byte) { - hw.write(h) - data = make([]byte, hw.buffer.Len()) - hw.buffer.Read(data) - return +func (frame *HeadersFrame) read(h ControlFrameHeader, f *Framer) os.Error { + return f.readHeadersFrame(h, frame) } -func (hw *HeaderWriter) write(h http.Header) { - binary.Write(hw.compressor, binary.BigEndian, uint16(len(h))) - for k, vals := range h { - k = strings.ToLower(k) - binary.Write(hw.compressor, binary.BigEndian, uint16(len(k))) - binary.Write(hw.compressor, binary.BigEndian, []byte(k)) - v := strings.Join(vals, "\x00") - binary.Write(hw.compressor, binary.BigEndian, uint16(len(v))) - binary.Write(hw.compressor, binary.BigEndian, []byte(v)) +func newControlFrame(frameType ControlFrameType) (controlFrame, os.Error) { + ctor, ok := cframeCtor[frameType] + if !ok { + return nil, InvalidControlFrame } - hw.compressor.Flush() + return ctor(), nil +} + +var cframeCtor = map[ControlFrameType]func() controlFrame{ + TypeSynStream: func() controlFrame { return new(SynStreamFrame) }, + TypeSynReply: func() controlFrame { return new(SynReplyFrame) }, + TypeRstStream: func() controlFrame { return new(RstStreamFrame) }, + TypeSettings: func() controlFrame { return new(SettingsFrame) }, + TypeNoop: func() controlFrame { return new(NoopFrame) }, + TypePing: func() controlFrame { return new(PingFrame) }, + TypeGoAway: func() controlFrame { return new(GoAwayFrame) }, + TypeHeaders: func() controlFrame { return new(HeadersFrame) }, + // TODO(willchan): Add TypeWindowUpdate +} + +// DataFrame is the unpacked, in-memory representation of a DATA frame. +type DataFrame struct { + // Note, high bit is the "Control" bit. Should be 0 for data frames. + StreamId uint32 + Flags DataFlags + Data []byte } + +func (frame *DataFrame) write(f *Framer) os.Error { + return f.writeDataFrame(frame) +} + +// HeaderDictionary is the dictionary sent to the zlib compressor/decompressor. +// Even though the specification states there is no null byte at the end, Chrome sends it. +const HeaderDictionary = "optionsgetheadpostputdeletetrace" + + "acceptaccept-charsetaccept-encodingaccept-languageauthorizationexpectfromhost" + + "if-modified-sinceif-matchif-none-matchif-rangeif-unmodifiedsince" + + "max-forwardsproxy-authorizationrangerefererteuser-agent" + + "100101200201202203204205206300301302303304305306307400401402403404405406407408409410411412413414415416417500501502503504505" + + "accept-rangesageetaglocationproxy-authenticatepublicretry-after" + + "servervarywarningwww-authenticateallowcontent-basecontent-encodingcache-control" + + "connectiondatetrailertransfer-encodingupgradeviawarning" + + "content-languagecontent-lengthcontent-locationcontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookie" + + "MondayTuesdayWednesdayThursdayFridaySaturdaySunday" + + "JanFebMarAprMayJunJulAugSepOctNovDec" + + "chunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplication/xhtmltext/plainpublicmax-age" + + "charset=iso-8859-1utf-8gzipdeflateHTTP/1.1statusversionurl\x00" diff --git a/src/pkg/http/spdy/protocol_test.go b/src/pkg/http/spdy/protocol_test.go deleted file mode 100644 index 998ff998bc..0000000000 --- a/src/pkg/http/spdy/protocol_test.go +++ /dev/null @@ -1,259 +0,0 @@ -// 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" - "http" - "os" - "testing" -) - -type frameIoTest struct { - desc string - data []byte - frame Frame - readError os.Error - readOnly bool -} - -var frameIoTests = []frameIoTest{ - { - "noop frame", - []byte{ - 0x80, 0x02, 0x00, 0x05, - 0x00, 0x00, 0x00, 0x00, - }, - ControlFrame( - TypeNoop, - 0x00, - []byte{}, - ), - nil, - false, - }, - { - "ping frame", - []byte{ - 0x80, 0x02, 0x00, 0x06, - 0x00, 0x00, 0x00, 0x04, - 0x00, 0x00, 0x00, 0x01, - }, - ControlFrame( - TypePing, - 0x00, - []byte{0x00, 0x00, 0x00, 0x01}, - ), - nil, - false, - }, - { - "syn_stream frame", - []byte{ - 0x80, 0x02, 0x00, 0x01, - 0x01, 0x00, 0x00, 0x53, - 0x00, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x78, 0xbb, - 0xdf, 0xa2, 0x51, 0xb2, - 0x62, 0x60, 0x66, 0x60, - 0xcb, 0x4d, 0x2d, 0xc9, - 0xc8, 0x4f, 0x61, 0x60, - 0x4e, 0x4f, 0x2d, 0x61, - 0x60, 0x2e, 0x2d, 0xca, - 0x61, 0x10, 0xcb, 0x28, - 0x29, 0x29, 0xb0, 0xd2, - 0xd7, 0x2f, 0x2f, 0x2f, - 0xd7, 0x4b, 0xcf, 0xcf, - 0x4f, 0xcf, 0x49, 0xd5, - 0x4b, 0xce, 0xcf, 0xd5, - 0x67, 0x60, 0x2f, 0x4b, - 0x2d, 0x2a, 0xce, 0xcc, - 0xcf, 0x63, 0xe0, 0x00, - 0x29, 0xd0, 0x37, 0xd4, - 0x33, 0x04, 0x00, 0x00, - 0x00, 0xff, 0xff, - }, - ControlFrame( - TypeSynStream, - 0x01, - []byte{ - 0x00, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x78, 0xbb, - 0xdf, 0xa2, 0x51, 0xb2, - 0x62, 0x60, 0x66, 0x60, - 0xcb, 0x4d, 0x2d, 0xc9, - 0xc8, 0x4f, 0x61, 0x60, - 0x4e, 0x4f, 0x2d, 0x61, - 0x60, 0x2e, 0x2d, 0xca, - 0x61, 0x10, 0xcb, 0x28, - 0x29, 0x29, 0xb0, 0xd2, - 0xd7, 0x2f, 0x2f, 0x2f, - 0xd7, 0x4b, 0xcf, 0xcf, - 0x4f, 0xcf, 0x49, 0xd5, - 0x4b, 0xce, 0xcf, 0xd5, - 0x67, 0x60, 0x2f, 0x4b, - 0x2d, 0x2a, 0xce, 0xcc, - 0xcf, 0x63, 0xe0, 0x00, - 0x29, 0xd0, 0x37, 0xd4, - 0x33, 0x04, 0x00, 0x00, - 0x00, 0xff, 0xff, - }, - ), - nil, - false, - }, - { - "data frame", - []byte{ - 0x00, 0x00, 0x00, 0x05, - 0x01, 0x00, 0x00, 0x04, - 0x01, 0x02, 0x03, 0x04, - }, - DataFrame( - 5, - 0x01, - []byte{0x01, 0x02, 0x03, 0x04}, - ), - nil, - false, - }, - { - "too much data", - []byte{ - 0x00, 0x00, 0x00, 0x05, - 0x01, 0x00, 0x00, 0x04, - 0x01, 0x02, 0x03, 0x04, - 0x05, 0x06, 0x07, 0x08, - }, - DataFrame( - 5, - 0x01, - []byte{0x01, 0x02, 0x03, 0x04}, - ), - nil, - true, - }, - { - "not enough data", - []byte{ - 0x00, 0x00, 0x00, 0x05, - }, - Frame{}, - os.EOF, - true, - }, -} - -func TestReadFrame(t *testing.T) { - for _, tt := range frameIoTests { - f, err := ReadFrame(bytes.NewBuffer(tt.data)) - if err != tt.readError { - t.Errorf("%s: ReadFrame: %s", tt.desc, err) - continue - } - if err == nil { - if !bytes.Equal(f.Header[:], tt.frame.Header[:]) { - t.Errorf("%s: header %q != %q", tt.desc, string(f.Header[:]), string(tt.frame.Header[:])) - } - if f.Flags != tt.frame.Flags { - t.Errorf("%s: flags %#02x != %#02x", tt.desc, f.Flags, tt.frame.Flags) - } - if !bytes.Equal(f.Data, tt.frame.Data) { - t.Errorf("%s: data %q != %q", tt.desc, string(f.Data), string(tt.frame.Data)) - } - } - } -} - -func TestWriteTo(t *testing.T) { - for _, tt := range frameIoTests { - if tt.readOnly { - continue - } - b := new(bytes.Buffer) - _, err := tt.frame.WriteTo(b) - if err != nil { - t.Errorf("%s: WriteTo: %s", tt.desc, err) - } - if !bytes.Equal(b.Bytes(), tt.data) { - t.Errorf("%s: data %q != %q", tt.desc, string(b.Bytes()), string(tt.data)) - } - } -} - -var headerDataTest = []byte{ - 0x78, 0xbb, 0xdf, 0xa2, - 0x51, 0xb2, 0x62, 0x60, - 0x66, 0x60, 0xcb, 0x4d, - 0x2d, 0xc9, 0xc8, 0x4f, - 0x61, 0x60, 0x4e, 0x4f, - 0x2d, 0x61, 0x60, 0x2e, - 0x2d, 0xca, 0x61, 0x10, - 0xcb, 0x28, 0x29, 0x29, - 0xb0, 0xd2, 0xd7, 0x2f, - 0x2f, 0x2f, 0xd7, 0x4b, - 0xcf, 0xcf, 0x4f, 0xcf, - 0x49, 0xd5, 0x4b, 0xce, - 0xcf, 0xd5, 0x67, 0x60, - 0x2f, 0x4b, 0x2d, 0x2a, - 0xce, 0xcc, 0xcf, 0x63, - 0xe0, 0x00, 0x29, 0xd0, - 0x37, 0xd4, 0x33, 0x04, - 0x00, 0x00, 0x00, 0xff, - 0xff, -} - -func TestReadHeader(t *testing.T) { - r := NewHeaderReader() - h, err := r.Decode(headerDataTest) - if err != nil { - t.Fatalf("Error: %v", err) - return - } - if len(h) != 3 { - t.Errorf("Header count = %d (expected 3)", len(h)) - } - if h.Get("Url") != "http://www.google.com/" { - t.Errorf("Url: %q != %q", h.Get("Url"), "http://www.google.com/") - } - if h.Get("Method") != "get" { - t.Errorf("Method: %q != %q", h.Get("Method"), "get") - } - if h.Get("Version") != "http/1.1" { - t.Errorf("Version: %q != %q", h.Get("Version"), "http/1.1") - } -} - -func TestWriteHeader(t *testing.T) { - for level := zlib.NoCompression; level <= zlib.BestCompression; level++ { - r := NewHeaderReader() - w := NewHeaderWriter(level) - for i := 0; i < 100; i++ { - b := new(bytes.Buffer) - gold := http.Header{ - "Url": []string{"http://www.google.com/"}, - "Method": []string{"get"}, - "Version": []string{"http/1.1"}, - } - w.WriteHeader(b, gold) - h, err := r.Decode(b.Bytes()) - if err != nil { - t.Errorf("(level=%d i=%d) Error: %v", level, i, err) - return - } - if len(h) != len(gold) { - t.Errorf("(level=%d i=%d) Header count = %d (expected %d)", level, i, len(h), len(gold)) - } - for k, _ := range h { - if h.Get(k) != gold.Get(k) { - t.Errorf("(level=%d i=%d) %s: %q != %q", level, i, k, h.Get(k), gold.Get(k)) - } - } - } - } -}