]> Cypherpunks repositories - gostls13.git/commitdiff
http/spdy: redo interfaces, flesh out implementation & frame types
authorWilliam Chan <willchan@chromium.org>
Thu, 26 May 2011 16:54:54 +0000 (09:54 -0700)
committerBrad Fitzpatrick <bradfitz@golang.org>
Thu, 26 May 2011 16:54:54 +0000 (09:54 -0700)
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

src/pkg/http/spdy/Makefile
src/pkg/http/spdy/framer.go [new file with mode: 0644]
src/pkg/http/spdy/framer_test.go [new file with mode: 0644]
src/pkg/http/spdy/protocol.go
src/pkg/http/spdy/protocol_test.go [deleted file]

index ff70d0437520f4ff9b15cf90e6dabeaeb6afde69..e5842c2e47d30a9e0638555b374404ec988011d0 100644 (file)
@@ -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 (file)
index 0000000..0ad2989
--- /dev/null
@@ -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 (file)
index 0000000..54420e2
--- /dev/null
@@ -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)
+       }
+}
index ad9aa6333559c2ddb6a084c82942f45864f9a3ce..25b138f38935f6577d04f47aea4ef93ac6b4a51e 100644 (file)
 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 (file)
index 998ff99..0000000
+++ /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))
-                               }
-                       }
-               }
-       }
-}