// 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 implements SPDY protocol which is described in // draft-mbelshe-httpbis-spdy-00. // // http://tools.ietf.org/html/draft-mbelshe-httpbis-spdy-00 package spdy import ( "bytes" "compress/zlib" "io" "net/http" ) // 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) 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) 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" ZeroStreamId ErrorCode = "stream id zero is disallowed" ) // 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) Error() 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, error) { compressBuf := new(bytes.Buffer) compressor, err := zlib.NewWriterLevelDict(compressBuf, zlib.BestCompression, []byte(HeaderDictionary)) if err != nil { return nil, err } framer := &Framer{ w: w, headerBuf: compressBuf, headerCompressor: compressor, r: r, } return framer, nil }