summaryrefslogtreecommitdiff
path: root/libgo/go/http/spdy
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/http/spdy')
-rw-r--r--libgo/go/http/spdy/protocol.go367
-rw-r--r--libgo/go/http/spdy/protocol_test.go259
-rw-r--r--libgo/go/http/spdy/read.go313
-rw-r--r--libgo/go/http/spdy/spdy_test.go497
-rw-r--r--libgo/go/http/spdy/types.go370
-rw-r--r--libgo/go/http/spdy/write.go286
6 files changed, 1466 insertions, 626 deletions
diff --git a/libgo/go/http/spdy/protocol.go b/libgo/go/http/spdy/protocol.go
deleted file mode 100644
index d584ea232ea..00000000000
--- a/libgo/go/http/spdy/protocol.go
+++ /dev/null
@@ -1,367 +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 is an incomplete implementation of the SPDY protocol.
-//
-// The implementation follows draft 2 of the spec:
-// https://sites.google.com/a/chromium.org/dev/spdy/spdy-protocol/spdy-protocol-draft2
-package spdy
-
-import (
- "bytes"
- "compress/zlib"
- "encoding/binary"
- "http"
- "io"
- "os"
- "strconv"
- "strings"
- "sync"
-)
-
-// Version is the protocol version number that this package implements.
-const Version = 2
-
-// ControlFrameType stores the type field in a control frame header.
-type ControlFrameType uint16
-
-// Control frame type constants
-const (
- TypeSynStream ControlFrameType = 0x0001
- TypeSynReply = 0x0002
- TypeRstStream = 0x0003
- TypeSettings = 0x0004
- TypeNoop = 0x0005
- TypePing = 0x0006
- 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
-
-// Stream frame flags
-const (
- FlagFin FrameFlags = 0x01
- FlagUnidirectional = 0x02
-)
-
-// SETTINGS frame flags
-const (
- FlagClearPreviouslyPersistedSettings FrameFlags = 0x01
-)
-
-// 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,
- }
-}
-
-// 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,
- }
-}
-
-// 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 {
- return
- }
- err = binary.Read(r, binary.BigEndian, &f.Flags)
- if err != nil {
- return
- }
- var lengthField [3]byte
- _, err = io.ReadFull(r, lengthField[:])
- if err != nil {
- if err == os.EOF {
- err = io.ErrUnexpectedEOF
- }
- 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
-}
-
-// 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]))
-}
-
-// 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
-}
-
-// 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 {
- 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 {
- return
- }
- // Data
- if len(f.Data) > 0 {
- nn, err = w.Write(f.Data)
- n += int64(nn)
- }
- 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()
- }
- n, err = src.r.Read(p)
- src.m.RUnlock()
- if err == os.EOF {
- src.change(nil)
- err = nil
- }
- return
-}
-
-func (src *hrSource) change(r io.Reader) {
- src.m.Lock()
- defer src.m.Unlock()
- src.r = r
- src.c.Broadcast()
-}
-
-// A HeaderReader reads zlib-compressed headers.
-type HeaderReader struct {
- source hrSource
- decompressor io.ReadCloser
-}
-
-// 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
-}
-
-// 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
-}
-
-// 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
-}
-
-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 {
- 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)
- }
- }
- return
-}
-
-func readHeaderString(r io.Reader) (s string, err os.Error) {
- var length uint16
- err = binary.Read(r, binary.BigEndian, &length)
- if err != nil {
- return
- }
- data := make([]byte, int(length))
- _, err = io.ReadFull(r, data)
- if err != nil {
- return
- }
- return string(data), nil
-}
-
-// HeaderWriter will write zlib-compressed headers on different streams.
-type HeaderWriter struct {
- compressor *zlib.Writer
- buffer *bytes.Buffer
-}
-
-// 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
-}
-
-// 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
-}
-
-// 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 (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))
- }
- hw.compressor.Flush()
-}
diff --git a/libgo/go/http/spdy/protocol_test.go b/libgo/go/http/spdy/protocol_test.go
deleted file mode 100644
index 998ff998bc7..00000000000
--- a/libgo/go/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))
- }
- }
- }
- }
-}
diff --git a/libgo/go/http/spdy/read.go b/libgo/go/http/spdy/read.go
new file mode 100644
index 00000000000..c6b6ab3af84
--- /dev/null
+++ b/libgo/go/http/spdy/read.go
@@ -0,0 +1,313 @@
+// 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 (
+ "compress/zlib"
+ "encoding/binary"
+ "http"
+ "io"
+ "os"
+ "strings"
+)
+
+func (frame *SynStreamFrame) read(h ControlFrameHeader, f *Framer) os.Error {
+ return f.readSynStreamFrame(h, frame)
+}
+
+func (frame *SynReplyFrame) read(h ControlFrameHeader, f *Framer) os.Error {
+ return f.readSynReplyFrame(h, frame)
+}
+
+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
+}
+
+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
+ }
+ 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 nil
+}
+
+func (frame *NoopFrame) read(h ControlFrameHeader, f *Framer) os.Error {
+ frame.CFHeader = h
+ return 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
+}
+
+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
+}
+
+func (frame *HeadersFrame) read(h ControlFrameHeader, f *Framer) os.Error {
+ return f.readHeadersFrame(h, frame)
+}
+
+func newControlFrame(frameType ControlFrameType) (controlFrame, os.Error) {
+ ctor, ok := cframeCtor[frameType]
+ if !ok {
+ return nil, &Error{Err: InvalidControlFrame}
+ }
+ 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
+}
+
+func (f *Framer) uncorkHeaderDecompressor(payloadSize int64) os.Error {
+ if f.headerDecompressor != nil {
+ f.headerReader.N = payloadSize
+ return nil
+ }
+ f.headerReader = io.LimitedReader{R: f.r, N: payloadSize}
+ decompressor, err := zlib.NewReaderDict(&f.headerReader, []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, streamId uint32) (http.Header, os.Error) {
+ var numHeaders uint16
+ if err := binary.Read(r, binary.BigEndian, &numHeaders); err != nil {
+ return nil, err
+ }
+ var e os.Error
+ 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) {
+ e = &Error{UnlowercasedHeaderName, streamId}
+ name = strings.ToLower(name)
+ }
+ if h[name] != nil {
+ e = &Error{DuplicateHeaders, streamId}
+ }
+ 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")
+ for _, v := range valueList {
+ h.Add(name, v)
+ }
+ }
+ if e != nil {
+ return h, e
+ }
+ 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.uncorkHeaderDecompressor(int64(h.length - 10))
+ reader = f.headerDecompressor
+ }
+
+ frame.Headers, err = parseHeaderValueBlock(reader, frame.StreamId)
+ if !f.headerCompressionDisabled && ((err == os.EOF && f.headerReader.N == 0) || f.headerReader.N != 0) {
+ err = &Error{WrongCompressedPayloadSize, 0}
+ }
+ if err != nil {
+ return err
+ }
+ // Remove this condition when we bump Version to 3.
+ if Version >= 3 {
+ for h, _ := range frame.Headers {
+ if invalidReqHeaders[h] {
+ return &Error{InvalidHeaderPresent, frame.StreamId}
+ }
+ }
+ }
+ 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.uncorkHeaderDecompressor(int64(h.length - 6))
+ reader = f.headerDecompressor
+ }
+ frame.Headers, err = parseHeaderValueBlock(reader, frame.StreamId)
+ if !f.headerCompressionDisabled && ((err == os.EOF && f.headerReader.N == 0) || f.headerReader.N != 0) {
+ err = &Error{WrongCompressedPayloadSize, 0}
+ }
+ if err != nil {
+ return err
+ }
+ // Remove this condition when we bump Version to 3.
+ if Version >= 3 {
+ for h, _ := range frame.Headers {
+ if invalidRespHeaders[h] {
+ return &Error{InvalidHeaderPresent, frame.StreamId}
+ }
+ }
+ }
+ 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.uncorkHeaderDecompressor(int64(h.length - 6))
+ reader = f.headerDecompressor
+ }
+ frame.Headers, err = parseHeaderValueBlock(reader, frame.StreamId)
+ if !f.headerCompressionDisabled && ((err == os.EOF && f.headerReader.N == 0) || f.headerReader.N != 0) {
+ err = &Error{WrongCompressedPayloadSize, 0}
+ }
+ if err != nil {
+ return err
+ }
+
+ // Remove this condition when we bump Version to 3.
+ if Version >= 3 {
+ var invalidHeaders map[string]bool
+ if frame.StreamId%2 == 0 {
+ invalidHeaders = invalidReqHeaders
+ } else {
+ invalidHeaders = invalidRespHeaders
+ }
+ for h, _ := range frame.Headers {
+ if invalidHeaders[h] {
+ return &Error{InvalidHeaderPresent, frame.StreamId}
+ }
+ }
+ }
+ 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)
+ if _, err := io.ReadFull(f.r, frame.Data); err != nil {
+ return nil, err
+ }
+ return &frame, nil
+}
diff --git a/libgo/go/http/spdy/spdy_test.go b/libgo/go/http/spdy/spdy_test.go
new file mode 100644
index 00000000000..cb91e028613
--- /dev/null
+++ b/libgo/go/http/spdy/spdy_test.go
@@ -0,0 +1,497 @@
+// 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)
+
+ const bogusStreamId = 1
+ newHeaders, err := parseHeaderValueBlock(&headerValueBlockBuf, bogusStreamId)
+ 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.Fatalf("expected HeadersFrame; got %T %v", frame, frame)
+ }
+ if !reflect.DeepEqual(headersFrame, *parsedHeadersFrame) {
+ t.Fatal("got: ", *parsedHeadersFrame, "\nwant: ", headersFrame)
+ }
+ frame, err = framer.ReadFrame()
+ if err != nil {
+ t.Fatal("ReadFrame (SYN_STREAM):", err, buffer.Bytes())
+ }
+ parsedSynStreamFrame, ok := frame.(*SynStreamFrame)
+ if !ok {
+ t.Fatalf("expected SynStreamFrame; got %T %v", frame, frame)
+ }
+ if !reflect.DeepEqual(synStreamFrame, *parsedSynStreamFrame) {
+ t.Fatal("got: ", *parsedSynStreamFrame, "\nwant: ", synStreamFrame)
+ }
+}
+
+func TestMultipleSPDYFrames(t *testing.T) {
+ // Initialize the framers.
+ pr1, pw1 := io.Pipe()
+ pr2, pw2 := io.Pipe()
+ writer, err := NewFramer(pw1, pr2)
+ if err != nil {
+ t.Fatal("Failed to create writer:", err)
+ }
+ reader, err := NewFramer(pw2, pr1)
+ if err != nil {
+ t.Fatal("Failed to create reader:", err)
+ }
+
+ // Set up the frames we're actually transferring.
+ headersFrame := HeadersFrame{
+ CFHeader: ControlFrameHeader{
+ version: Version,
+ frameType: TypeHeaders,
+ },
+ 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/libgo/go/http/spdy/types.go b/libgo/go/http/spdy/types.go
new file mode 100644
index 00000000000..41cafb1741f
--- /dev/null
+++ b/libgo/go/http/spdy/types.go
@@ -0,0 +1,370 @@
+// 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"
+ "io"
+ "os"
+)
+
+// 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
+
+// ControlFrameType stores the type field in a control frame header.
+type ControlFrameType uint16
+
+// Control frame type constants
+const (
+ TypeSynStream ControlFrameType = 0x0001
+ TypeSynReply = 0x0002
+ TypeRstStream = 0x0003
+ TypeSettings = 0x0004
+ TypeNoop = 0x0005
+ TypePing = 0x0006
+ TypeGoAway = 0x0007
+ TypeHeaders = 0x0008
+ TypeWindowUpdate = 0x0009
+)
+
+// ControlFlags are the flags that can be set on a control frame.
+type ControlFlags uint8
+
+const (
+ ControlFlagFin ControlFlags = 0x01
+)
+
+// DataFlags are the flags that can be set on a data frame.
+type DataFlags uint8
+
+const (
+ DataFlagFin DataFlags = 0x01
+ DataFlagCompressed = 0x02
+)
+
+// MaxDataLength is the maximum number of bytes that can be stored in one frame.
+const MaxDataLength = 1<<24 - 1
+
+// 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
+}
+
+// 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
+}
+
+// SynReplyFrame is the unpacked, in-memory representation of a SYN_REPLY frame.
+type SynReplyFrame struct {
+ CFHeader ControlFrameHeader
+ StreamId uint32
+ Headers http.Header
+}
+
+// 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
+}
+
+// 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
+}
+
+// SettingsFrame is the unpacked, in-memory representation of a SPDY
+// SETTINGS frame.
+type SettingsFrame struct {
+ CFHeader ControlFrameHeader
+ FlagIdValues []SettingsFlagIdValue
+}
+
+// NoopFrame is the unpacked, in-memory representation of a NOOP frame.
+type NoopFrame struct {
+ CFHeader ControlFrameHeader
+}
+
+// PingFrame is the unpacked, in-memory representation of a PING frame.
+type PingFrame struct {
+ CFHeader ControlFrameHeader
+ Id uint32
+}
+
+// GoAwayFrame is the unpacked, in-memory representation of a GOAWAY frame.
+type GoAwayFrame struct {
+ CFHeader ControlFrameHeader
+ LastGoodStreamId uint32
+}
+
+// HeadersFrame is the unpacked, in-memory representation of a HEADERS frame.
+type HeadersFrame struct {
+ CFHeader ControlFrameHeader
+ StreamId uint32
+ Headers http.Header
+}
+
+// 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
+}
+
+// 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"
+
+// A SPDY specific error.
+type ErrorCode string
+
+const (
+ UnlowercasedHeaderName ErrorCode = "header was not lowercased"
+ DuplicateHeaders ErrorCode = "multiple headers with same name"
+ WrongCompressedPayloadSize ErrorCode = "compressed payload size was incorrect"
+ UnknownFrameType ErrorCode = "unknown frame type"
+ InvalidControlFrame ErrorCode = "invalid control frame"
+ InvalidDataFrame ErrorCode = "invalid data frame"
+ InvalidHeaderPresent ErrorCode = "frame contained invalid header"
+)
+
+// Error contains both the type of error and additional values. StreamId is 0
+// if Error is not associated with a stream.
+type Error struct {
+ Err ErrorCode
+ StreamId uint32
+}
+
+func (e *Error) String() string {
+ return string(e.Err)
+}
+
+var invalidReqHeaders = map[string]bool{
+ "Connection": true,
+ "Keep-Alive": true,
+ "Proxy-Connection": true,
+ "Transfer-Encoding": true,
+}
+
+var invalidRespHeaders = map[string]bool{
+ "Connection": true,
+ "Keep-Alive": true,
+ "Transfer-Encoding": true,
+}
+
+// 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
+ headerReader io.LimitedReader
+ 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
+}
diff --git a/libgo/go/http/spdy/write.go b/libgo/go/http/spdy/write.go
new file mode 100644
index 00000000000..7d40bbe9fe2
--- /dev/null
+++ b/libgo/go/http/spdy/write.go
@@ -0,0 +1,286 @@
+// 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 (
+ "encoding/binary"
+ "http"
+ "io"
+ "os"
+ "strings"
+)
+
+func (frame *SynStreamFrame) write(f *Framer) os.Error {
+ return f.writeSynStreamFrame(frame)
+}
+
+func (frame *SynReplyFrame) write(f *Framer) os.Error {
+ return f.writeSynReplyFrame(frame)
+}
+
+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
+ }
+ if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil {
+ return
+ }
+ if err = binary.Write(f.w, binary.BigEndian, frame.Status); err != nil {
+ return
+ }
+ return
+}
+
+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
+ }
+ if err = binary.Write(f.w, binary.BigEndian, uint32(len(frame.FlagIdValues))); err != nil {
+ return
+ }
+ 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
+}
+
+func (frame *NoopFrame) write(f *Framer) os.Error {
+ frame.CFHeader.version = Version
+ frame.CFHeader.frameType = TypeNoop
+
+ // Serialize frame to Writer
+ return writeControlFrameHeader(f.w, frame.CFHeader)
+}
+
+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
+ }
+ if err = binary.Write(f.w, binary.BigEndian, frame.Id); err != nil {
+ return
+ }
+ return
+}
+
+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
+ }
+ if err = binary.Write(f.w, binary.BigEndian, frame.LastGoodStreamId); err != nil {
+ return
+ }
+ return nil
+}
+
+func (frame *HeadersFrame) write(f *Framer) os.Error {
+ return f.writeHeadersFrame(frame)
+}
+
+func (frame *DataFrame) write(f *Framer) os.Error {
+ return f.writeDataFrame(frame)
+}
+
+// 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 &Error{InvalidDataFrame, frame.StreamId}
+ }
+
+ // 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
+}