diff options
Diffstat (limited to 'libgo/go/http/spdy')
-rw-r--r-- | libgo/go/http/spdy/protocol.go | 367 | ||||
-rw-r--r-- | libgo/go/http/spdy/protocol_test.go | 259 | ||||
-rw-r--r-- | libgo/go/http/spdy/read.go | 313 | ||||
-rw-r--r-- | libgo/go/http/spdy/spdy_test.go | 497 | ||||
-rw-r--r-- | libgo/go/http/spdy/types.go | 370 | ||||
-rw-r--r-- | libgo/go/http/spdy/write.go | 286 |
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 +} |