diff options
author | mbelshe@chromium.org <mbelshe@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-12-03 22:04:03 +0000 |
---|---|---|
committer | mbelshe@chromium.org <mbelshe@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-12-03 22:04:03 +0000 |
commit | d083585002893abaa6172c361aa04306a0e4d396 (patch) | |
tree | 75ff77ddf51d68d450ee0e7d56192409e44bcf97 /net | |
parent | 95c97199c4406036b216d74e4ba3a5c57c0081a2 (diff) | |
download | chromium_src-d083585002893abaa6172c361aa04306a0e4d396.zip chromium_src-d083585002893abaa6172c361aa04306a0e4d396.tar.gz chromium_src-d083585002893abaa6172c361aa04306a0e4d396.tar.bz2 |
Update server push to allow use of HEADERS frame.
- Sync'd server changes for SPDY protocol and framer.
- Adds HEADERS support & smaller header frame support.
- Changes field name from "path" to "url" for pushed streams.
- Changes existing semantics in SpdyStream and SpdyHttpStream
with how the OnResponseReceived callback works and with how
headers are parsed to reflect multi-frame arrival of headers.
Other changes:
- Reworked the StaticSocketDataProvider interface slightly so that we
can share code between tests using DelayedSocketData or
DeterministicSocketData
- Tidy up net_log for pushed streams with associated-stream id logging
and format fixes for SPDY_STREAM.
BUG=none
TEST=spdy_framer_test,spdy_network_transaction_unittest(s)
Review URL: http://codereview.chromium.org/5248001
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@68221 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r-- | net/base/net_error_list.h | 4 | ||||
-rw-r--r-- | net/base/net_log_event_type_list.h | 10 | ||||
-rw-r--r-- | net/socket/socket_test_util.h | 10 | ||||
-rw-r--r-- | net/spdy/spdy_framer.cc | 202 | ||||
-rw-r--r-- | net/spdy/spdy_framer.h | 61 | ||||
-rw-r--r-- | net/spdy/spdy_framer_test.cc | 208 | ||||
-rw-r--r-- | net/spdy/spdy_http_stream.cc | 33 | ||||
-rw-r--r-- | net/spdy/spdy_http_stream.h | 64 | ||||
-rw-r--r-- | net/spdy/spdy_http_utils.cc | 8 | ||||
-rw-r--r-- | net/spdy/spdy_network_transaction_unittest.cc | 736 | ||||
-rw-r--r-- | net/spdy/spdy_protocol.h | 144 | ||||
-rw-r--r-- | net/spdy/spdy_proxy_client_socket.cc | 13 | ||||
-rw-r--r-- | net/spdy/spdy_proxy_client_socket.h | 23 | ||||
-rw-r--r-- | net/spdy/spdy_session.cc | 95 | ||||
-rw-r--r-- | net/spdy/spdy_session.h | 6 | ||||
-rw-r--r-- | net/spdy/spdy_stream.cc | 65 | ||||
-rw-r--r-- | net/spdy/spdy_stream.h | 12 | ||||
-rw-r--r-- | net/spdy/spdy_test_util.cc | 56 | ||||
-rw-r--r-- | net/spdy/spdy_test_util.h | 12 |
19 files changed, 1425 insertions, 337 deletions
diff --git a/net/base/net_error_list.h b/net/base/net_error_list.h index 636808c..bc7065d 100644 --- a/net/base/net_error_list.h +++ b/net/base/net_error_list.h @@ -426,6 +426,10 @@ NET_ERROR(RESPONSE_BODY_TOO_BIG_TO_DRAIN, -345) // The HTTP response was too big to drain. NET_ERROR(RESPONSE_HEADERS_MULTIPLE_CONTENT_LENGTH, -346) +// SPDY Headers have been received, but not all of them - status or version +// headers are missing, so we're expecting additional frames to complete them. +NET_ERROR(INCOMPLETE_SPDY_HEADERS, -347) + // The cache does not have the requested entry. NET_ERROR(CACHE_MISS, -400) diff --git a/net/base/net_log_event_type_list.h b/net/base/net_log_event_type_list.h index 54310d3..92c65d1 100644 --- a/net/base/net_log_event_type_list.h +++ b/net/base/net_log_event_type_list.h @@ -596,9 +596,19 @@ EVENT_TYPE(SPDY_SESSION_SYN_STREAM) // "flags": <The control frame flags> // "headers": <The list of header:value pairs> // "id": <The stream id> +// "associated_stream": <The stream id> // } EVENT_TYPE(SPDY_SESSION_PUSHED_SYN_STREAM) +// This event is sent for a SPDY HEADERS frame. +// The following parameters are attached: +// { +// "flags": <The control frame flags> +// "headers": <The list of header:value pairs> +// "id": <The stream id> +// } +EVENT_TYPE(SPDY_SESSION_HEADERS) + // This event is sent for a SPDY SYN_REPLY. // The following parameters are attached: // { diff --git a/net/socket/socket_test_util.h b/net/socket/socket_test_util.h index 147a4ba..0a01df3 100644 --- a/net/socket/socket_test_util.h +++ b/net/socket/socket_test_util.h @@ -172,6 +172,7 @@ class StaticSocketDataProvider : public SocketDataProvider { virtual MockRead GetNextRead(); virtual MockWriteResult OnWrite(const std::string& data); virtual void Reset(); + virtual void CompleteRead() {} // These functions get access to the next available read and write data. const MockRead& PeekRead() const; @@ -284,7 +285,7 @@ class DelayedSocketData : public StaticSocketDataProvider, virtual MockRead GetNextRead(); virtual MockWriteResult OnWrite(const std::string& data); virtual void Reset(); - void CompleteRead(); + virtual void CompleteRead(); void ForceNextRead(); private: @@ -327,6 +328,8 @@ class OrderedSocketData : public StaticSocketDataProvider, virtual MockRead GetNextRead(); virtual MockWriteResult OnWrite(const std::string& data); virtual void Reset(); + virtual void CompleteRead(); + void SetCompletionCallback(CompletionCallback* callback) { callback_ = callback; } @@ -334,8 +337,6 @@ class OrderedSocketData : public StaticSocketDataProvider, // Posts a quit message to the current message loop, if one is running. void EndLoop(); - void CompleteRead(); - private: friend class base::RefCounted<OrderedSocketData>; virtual ~OrderedSocketData(); @@ -425,6 +426,8 @@ class DeterministicSocketData : public StaticSocketDataProvider, virtual void Reset(); + virtual void CompleteRead() {} + // Consume all the data up to the give stop point (via SetStop()). void Run(); @@ -442,7 +445,6 @@ class DeterministicSocketData : public StaticSocketDataProvider, virtual void StopAfter(int seq) { SetStop(sequence_number_ + seq); } - void CompleteRead(); bool stopped() const { return stopped_; } void SetStopped(bool val) { stopped_ = val; } MockRead& current_read() { return current_read_; } diff --git a/net/spdy/spdy_framer.cc b/net/spdy/spdy_framer.cc index ea58559..ed21610 100644 --- a/net/spdy/spdy_framer.cc +++ b/net/spdy/spdy_framer.cc @@ -2,6 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// TODO(rtenhove) clean up frame buffer size calculations so that we aren't +// constantly adding and subtracting header sizes; this is ugly and error- +// prone. + #include "net/spdy/spdy_framer.h" #include "base/metrics/stats_counters.h" @@ -17,23 +21,26 @@ namespace spdy { -// The initial size of the control frame buffer; this is used internally -// as we parse through control frames. -static const size_t kControlFrameBufferInitialSize = 32 * 1024; -// The maximum size of the control frame buffer that we support. -// TODO(mbelshe): We should make this stream-based so there are no limits. -static const size_t kControlFrameBufferMaxSize = 64 * 1024; - // By default is compression on or off. bool SpdyFramer::compression_default_ = true; int SpdyFramer::spdy_version_ = kSpdyProtocolVersion; +// The initial size of the control frame buffer; this is used internally +// as we parse through control frames. (It is exposed here for unit test +// purposes.) +size_t SpdyFramer::kControlFrameBufferInitialSize = 8 * 1024; + +// The maximum size of the control frame buffer that we support. +// TODO(mbelshe): We should make this stream-based so there are no limits. +size_t SpdyFramer::kControlFrameBufferMaxSize = 16 * 1024; + #ifdef DEBUG_SPDY_STATE_CHANGES #define CHANGE_STATE(newstate) \ { \ do { \ - VLOG(1) << "Changing state from: " << StateToString(state_) \ - << " to " << StateToString(newstate); \ + LOG(INFO) << "Changing state from: " \ + << StateToString(state_) \ + << " to " << StateToString(newstate) << "\n"; \ state_ = newstate; \ } while (false); \ } @@ -72,7 +79,7 @@ void SpdyFramer::Reset() { current_frame_len_ = 0; if (current_frame_capacity_ != kControlFrameBufferInitialSize) { delete [] current_frame_buffer_; - current_frame_buffer_ = NULL; + current_frame_buffer_ = 0; current_frame_capacity_ = 0; ExpandControlFrameBuffer(kControlFrameBufferInitialSize); } @@ -289,6 +296,11 @@ void SpdyFramer::ProcessControlFrameHeader() { SpdyRstStreamControlFrame::size() - SpdyFrame::size()) set_error(SPDY_INVALID_CONTROL_FRAME); break; + case SETTINGS: + if (current_control_frame.length() < + SpdySettingsControlFrame::size() - SpdyControlFrame::size()) + set_error(SPDY_INVALID_CONTROL_FRAME); + break; case NOOP: // NOOP. Swallow it. CHANGE_STATE(SPDY_AUTO_RESET); @@ -298,14 +310,14 @@ void SpdyFramer::ProcessControlFrameHeader() { SpdyGoAwayControlFrame::size() - SpdyFrame::size()) set_error(SPDY_INVALID_CONTROL_FRAME); break; - case SETTINGS: + case HEADERS: if (current_control_frame.length() < - SpdySettingsControlFrame::size() - SpdyControlFrame::size()) + SpdyHeadersControlFrame::size() - SpdyControlFrame::size()) set_error(SPDY_INVALID_CONTROL_FRAME); break; case WINDOW_UPDATE: if (current_control_frame.length() != - SpdyWindowUpdateControlFrame::size() - SpdyFrame::size()) + SpdyWindowUpdateControlFrame::size() - SpdyControlFrame::size()) set_error(SPDY_INVALID_CONTROL_FRAME); break; default: @@ -435,7 +447,7 @@ bool SpdyFramer::ParseHeaderBlock(const SpdyFrame* frame, SpdyHeaderBlock* block) { SpdyControlFrame control_frame(frame->data(), false); uint32 type = control_frame.type(); - if (type != SYN_STREAM && type != SYN_REPLY) + if (type != SYN_STREAM && type != SYN_REPLY && type != HEADERS) return false; // Find the header data within the control frame. @@ -461,14 +473,21 @@ bool SpdyFramer::ParseHeaderBlock(const SpdyFrame* frame, header_length = syn_frame.header_block_len(); } break; + case HEADERS: + { + SpdyHeadersControlFrame header_frame(decompressed_frame->data(), false); + header_data = header_frame.header_block(); + header_length = header_frame.header_block_len(); + } + break; } SpdyFrameBuilder builder(header_data, header_length); void* iter = NULL; uint16 num_headers; if (builder.ReadUInt16(&iter, &num_headers)) { - int index = 0; - for ( ; index < num_headers; ++index) { + int index; + for (index = 0; index < num_headers; ++index) { std::string name; std::string value; if (!builder.ReadString(&iter, &name)) @@ -551,55 +570,60 @@ SpdySynStreamControlFrame* SpdyFramer::CreateSynStream( return reinterpret_cast<SpdySynStreamControlFrame*>(syn_frame.release()); } -/* static */ -SpdyRstStreamControlFrame* SpdyFramer::CreateRstStream(SpdyStreamId stream_id, - SpdyStatusCodes status) { +SpdySynReplyControlFrame* SpdyFramer::CreateSynReply(SpdyStreamId stream_id, + SpdyControlFlags flags, bool compressed, SpdyHeaderBlock* headers) { DCHECK_GT(stream_id, 0u); DCHECK_EQ(0u, stream_id & ~kStreamIdMask); - DCHECK_NE(status, INVALID); - DCHECK_LT(status, NUM_STATUS_CODES); SpdyFrameBuilder frame; + frame.WriteUInt16(kControlFlagMask | spdy_version_); - frame.WriteUInt16(RST_STREAM); - frame.WriteUInt32(8); + frame.WriteUInt16(SYN_REPLY); + frame.WriteUInt32(0); // Placeholder for the length and flags. frame.WriteUInt32(stream_id); - frame.WriteUInt32(status); - return reinterpret_cast<SpdyRstStreamControlFrame*>(frame.take()); -} + frame.WriteUInt16(0); // Unused -/* static */ -SpdyGoAwayControlFrame* SpdyFramer::CreateGoAway( - SpdyStreamId last_accepted_stream_id) { - DCHECK_EQ(0u, last_accepted_stream_id & ~kStreamIdMask); + frame.WriteUInt16(headers->size()); // Number of headers. + SpdyHeaderBlock::iterator it; + for (it = headers->begin(); it != headers->end(); ++it) { + bool wrote_header; + wrote_header = frame.WriteString(it->first); + wrote_header &= frame.WriteString(it->second); + DCHECK(wrote_header); + } - SpdyFrameBuilder frame; - frame.WriteUInt16(kControlFlagMask | spdy_version_); - frame.WriteUInt16(GOAWAY); - size_t go_away_size = SpdyGoAwayControlFrame::size() - SpdyFrame::size(); - frame.WriteUInt32(go_away_size); - frame.WriteUInt32(last_accepted_stream_id); - return reinterpret_cast<SpdyGoAwayControlFrame*>(frame.take()); + // Write the length and flags. + size_t length = frame.length() - SpdyFrame::size(); + DCHECK_EQ(0u, length & ~static_cast<size_t>(kLengthMask)); + FlagsAndLength flags_length; + flags_length.length_ = htonl(static_cast<uint32>(length)); + DCHECK_EQ(0, flags & ~kControlFlagsMask); + flags_length.flags_[0] = flags; + frame.WriteBytesToOffset(4, &flags_length, sizeof(flags_length)); + + scoped_ptr<SpdyFrame> reply_frame(frame.take()); + if (compressed) { + return reinterpret_cast<SpdySynReplyControlFrame*>( + CompressFrame(*reply_frame.get())); + } + return reinterpret_cast<SpdySynReplyControlFrame*>(reply_frame.release()); } /* static */ -SpdyWindowUpdateControlFrame* SpdyFramer::CreateWindowUpdate( - SpdyStreamId stream_id, - uint32 delta_window_size) { +SpdyRstStreamControlFrame* SpdyFramer::CreateRstStream(SpdyStreamId stream_id, + SpdyStatusCodes status) { DCHECK_GT(stream_id, 0u); DCHECK_EQ(0u, stream_id & ~kStreamIdMask); - DCHECK_GT(delta_window_size, 0u); - DCHECK_LT(delta_window_size, 0x80000000u); // 2^31 + DCHECK_NE(status, INVALID); + DCHECK_LT(status, NUM_STATUS_CODES); SpdyFrameBuilder frame; frame.WriteUInt16(kControlFlagMask | spdy_version_); - frame.WriteUInt16(WINDOW_UPDATE); - size_t window_update_size = SpdyWindowUpdateControlFrame::size() - - SpdyFrame::size(); - frame.WriteUInt32(window_update_size); + frame.WriteUInt16(RST_STREAM); + frame.WriteUInt32(8); frame.WriteUInt32(stream_id); - frame.WriteUInt32(delta_window_size); - return reinterpret_cast<SpdyWindowUpdateControlFrame*>(frame.take()); + frame.WriteUInt32(status); + return reinterpret_cast<SpdyRstStreamControlFrame*>(frame.take()); } /* static */ @@ -621,15 +645,38 @@ SpdySettingsControlFrame* SpdyFramer::CreateSettings( return reinterpret_cast<SpdySettingsControlFrame*>(frame.take()); } -SpdySynReplyControlFrame* SpdyFramer::CreateSynReply(SpdyStreamId stream_id, +/* static */ +SpdyControlFrame* SpdyFramer::CreateNopFrame() { + SpdyFrameBuilder frame; + frame.WriteUInt16(kControlFlagMask | spdy_version_); + frame.WriteUInt16(NOOP); + frame.WriteUInt32(0); + return reinterpret_cast<SpdyControlFrame*>(frame.take()); +} + +/* static */ +SpdyGoAwayControlFrame* SpdyFramer::CreateGoAway( + SpdyStreamId last_accepted_stream_id) { + DCHECK_EQ(0u, last_accepted_stream_id & ~kStreamIdMask); + + SpdyFrameBuilder frame; + frame.WriteUInt16(kControlFlagMask | spdy_version_); + frame.WriteUInt16(GOAWAY); + size_t go_away_size = SpdyGoAwayControlFrame::size() - SpdyFrame::size(); + frame.WriteUInt32(go_away_size); + frame.WriteUInt32(last_accepted_stream_id); + return reinterpret_cast<SpdyGoAwayControlFrame*>(frame.take()); +} + +SpdyHeadersControlFrame* SpdyFramer::CreateHeaders(SpdyStreamId stream_id, SpdyControlFlags flags, bool compressed, SpdyHeaderBlock* headers) { + // Basically the same as CreateSynReply(). DCHECK_GT(stream_id, 0u); DCHECK_EQ(0u, stream_id & ~kStreamIdMask); SpdyFrameBuilder frame; - - frame.WriteUInt16(kControlFlagMask | spdy_version_); - frame.WriteUInt16(SYN_REPLY); + frame.WriteUInt16(kControlFlagMask | kSpdyProtocolVersion); + frame.WriteUInt16(HEADERS); frame.WriteUInt32(0); // Placeholder for the length and flags. frame.WriteUInt32(stream_id); frame.WriteUInt16(0); // Unused @@ -652,12 +699,32 @@ SpdySynReplyControlFrame* SpdyFramer::CreateSynReply(SpdyStreamId stream_id, flags_length.flags_[0] = flags; frame.WriteBytesToOffset(4, &flags_length, sizeof(flags_length)); - scoped_ptr<SpdyFrame> reply_frame(frame.take()); + scoped_ptr<SpdyFrame> headers_frame(frame.take()); if (compressed) { - return reinterpret_cast<SpdySynReplyControlFrame*>( - CompressFrame(*reply_frame.get())); + return reinterpret_cast<SpdyHeadersControlFrame*>( + CompressFrame(*headers_frame.get())); } - return reinterpret_cast<SpdySynReplyControlFrame*>(reply_frame.release()); + return reinterpret_cast<SpdyHeadersControlFrame*>(headers_frame.release()); +} + +/* static */ +SpdyWindowUpdateControlFrame* SpdyFramer::CreateWindowUpdate( + SpdyStreamId stream_id, + uint32 delta_window_size) { + DCHECK_GT(stream_id, 0u); + DCHECK_EQ(0u, stream_id & ~kStreamIdMask); + DCHECK_GT(delta_window_size, 0u); + DCHECK_LE(delta_window_size, spdy::kSpdyStreamMaximumWindowSize); + + SpdyFrameBuilder frame; + frame.WriteUInt16(kControlFlagMask | spdy_version_); + frame.WriteUInt16(WINDOW_UPDATE); + size_t window_update_size = SpdyWindowUpdateControlFrame::size() - + SpdyFrame::size(); + frame.WriteUInt32(window_update_size); + frame.WriteUInt32(stream_id); + frame.WriteUInt32(delta_window_size); + return reinterpret_cast<SpdyWindowUpdateControlFrame*>(frame.take()); } SpdyDataFrame* SpdyFramer::CreateDataFrame(SpdyStreamId stream_id, @@ -692,15 +759,6 @@ SpdyDataFrame* SpdyFramer::CreateDataFrame(SpdyStreamId stream_id, return rv; } -/* static */ -SpdyControlFrame* SpdyFramer::CreateNopFrame() { - SpdyFrameBuilder frame; - frame.WriteUInt16(kControlFlagMask | spdy_version_); - frame.WriteUInt16(NOOP); - frame.WriteUInt32(0); - return reinterpret_cast<SpdyControlFrame*>(frame.take()); -} - // The following compression setting are based on Brian Olson's analysis. See // https://groups.google.com/group/spdy-dev/browse_thread/thread/dfaf498542fac792 // for more details. @@ -844,6 +902,16 @@ bool SpdyFramer::GetFrameBoundaries(const SpdyFrame& frame, *payload = frame.data() + *header_length; } break; + case HEADERS: + { + const SpdyHeadersControlFrame& headers_frame = + reinterpret_cast<const SpdyHeadersControlFrame&>(frame); + frame_size = SpdyHeadersControlFrame::size(); + *payload_length = headers_frame.header_block_len(); + *header_length = frame_size; + *payload = frame.data() + *header_length; + } + break; default: // TODO(mbelshe): set an error? return false; // We can't compress this frame! @@ -985,8 +1053,10 @@ SpdyFrame* SpdyFramer::DecompressFrameWithZStream(const SpdyFrame& frame, // Create an output frame. Assume it does not need to be longer than // the input data. - int decompressed_max_size = kControlFrameBufferInitialSize; + size_t decompressed_max_size = kControlFrameBufferInitialSize; int new_frame_size = header_length + decompressed_max_size; + if (frame.length() > decompressed_max_size) + return NULL; scoped_ptr<SpdyFrame> new_frame(new SpdyFrame(new_frame_size)); memcpy(new_frame->data(), frame.data(), frame.length() + SpdyFrame::size()); diff --git a/net/spdy/spdy_framer.h b/net/spdy/spdy_framer.h index 9b290cd..85805f3 100644 --- a/net/spdy/spdy_framer.h +++ b/net/spdy/spdy_framer.h @@ -157,9 +157,27 @@ class SpdyFramer { bool compressed, SpdyHeaderBlock* headers); + // Create a SpdySynReplyControlFrame. + // |stream_id| is the stream for this frame. + // |flags| is the flags to use with the data. + // To mark this frame as the last frame, enable CONTROL_FLAG_FIN. + // |compressed| specifies whether the frame should be compressed. + // |headers| is the header block to include in the frame. + SpdySynReplyControlFrame* CreateSynReply(SpdyStreamId stream_id, + SpdyControlFlags flags, + bool compressed, + SpdyHeaderBlock* headers); + static SpdyRstStreamControlFrame* CreateRstStream(SpdyStreamId stream_id, SpdyStatusCodes status); + // Creates an instance of SpdySettingsControlFrame. The SETTINGS frame is + // used to communicate name/value pairs relevant to the communication channel. + // TODO(mbelshe): add the name/value pairs!! + static SpdySettingsControlFrame* CreateSettings(const SpdySettings& values); + + static SpdyControlFrame* CreateNopFrame(); + // Creates an instance of SpdyGoAwayControlFrame. The GOAWAY frame is used // prior to the shutting down of the TCP connection, and includes the // stream_id of the last stream the sender of the frame is willing to process @@ -167,32 +185,25 @@ class SpdyFramer { static SpdyGoAwayControlFrame* CreateGoAway( SpdyStreamId last_accepted_stream_id); + // Creates an instance of SpdyHeadersControlFrame. The HEADERS frame is used + // for sending additional headers outside of a SYN_STREAM/SYN_REPLY. The + // arguments are the same as for CreateSynReply. + SpdyHeadersControlFrame* CreateHeaders(SpdyStreamId stream_id, + SpdyControlFlags flags, + bool compressed, + SpdyHeaderBlock* headers); + // Creates an instance of SpdyWindowUpdateControlFrame. The WINDOW_UPDATE // frame is used to implement per stream flow control in SPDY. static SpdyWindowUpdateControlFrame* CreateWindowUpdate( - SpdyStreamId stream_id, uint32 delta_window_size); - - // Creates an instance of SpdySettingsControlFrame. The SETTINGS frame is - // used to communicate name/value pairs relevant to the communication channel. - // TODO(mbelshe): add the name/value pairs!! - static SpdySettingsControlFrame* CreateSettings(const SpdySettings& values); + SpdyStreamId stream_id, + uint32 delta_window_size); // Given a SpdySettingsControlFrame, extract the settings. // Returns true on successful parse, false otherwise. static bool ParseSettings(const SpdySettingsControlFrame* frame, SpdySettings* settings); - // Create a SpdySynReplyControlFrame. - // |stream_id| is the stream for this frame. - // |flags| is the flags to use with the data. - // To mark this frame as the last frame, enable CONTROL_FLAG_FIN. - // |compressed| specifies whether the frame should be compressed. - // |headers| is the header block to include in the frame. - SpdySynReplyControlFrame* CreateSynReply(SpdyStreamId stream_id, - SpdyControlFlags flags, - bool compressed, - SpdyHeaderBlock* headers); - // Create a data frame. // |stream_id| is the stream for this frame // |data| is the data to be included in the frame. @@ -203,8 +214,6 @@ class SpdyFramer { SpdyDataFrame* CreateDataFrame(SpdyStreamId stream_id, const char* data, uint32 len, SpdyDataFlags flags); - static SpdyControlFrame* CreateNopFrame(); - // NOTES about frame compression. // We want spdy to compress headers across the entire session. As long as // the session is over TCP, frames are sent serially. The client & server @@ -249,7 +258,11 @@ class SpdyFramer { protected: FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, DataCompression); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, ExpandBuffer_HeapSmash); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, HugeHeaderBlock); FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, UnclosedStreamDataCompressors); + FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, + UncompressLargerThanFrameBufferInitialSize); friend class net::HttpNetworkLayer; // This is temporary for the server. friend class net::HttpNetworkTransactionTest; friend class net::HttpProxyClientSocketPoolTest; @@ -266,6 +279,16 @@ class SpdyFramer { void set_enable_compression(bool value); static void set_enable_compression_default(bool value); + + // The initial size of the control frame buffer; this is used internally + // as we parse through control frames. (It is exposed here for unit test + // purposes.) + static size_t kControlFrameBufferInitialSize; + + // The maximum size of the control frame buffer that we support. + // TODO(mbelshe): We should make this stream-based so there are no limits. + static size_t kControlFrameBufferMaxSize; + private: typedef std::map<SpdyStreamId, z_stream*> CompressorMap; diff --git a/net/spdy/spdy_framer_test.cc b/net/spdy/spdy_framer_test.cc index d238062..b4345c1 100644 --- a/net/spdy/spdy_framer_test.cc +++ b/net/spdy/spdy_framer_test.cc @@ -86,6 +86,7 @@ class TestSpdyVisitor : public SpdyFramerVisitorInterface { : error_count_(0), syn_frame_count_(0), syn_reply_frame_count_(0), + headers_frame_count_(0), data_bytes_(0), fin_frame_count_(0), fin_flag_count_(0), @@ -129,6 +130,11 @@ class TestSpdyVisitor : public SpdyFramerVisitorInterface { case RST_STREAM: fin_frame_count_++; break; + case HEADERS: + parsed_headers = framer_.ParseHeaderBlock(frame, &headers); + DCHECK(parsed_headers); + headers_frame_count_++; + break; default: DCHECK(false); // Error! } @@ -163,6 +169,7 @@ class TestSpdyVisitor : public SpdyFramerVisitorInterface { int error_count_; int syn_frame_count_; int syn_reply_frame_count_; + int headers_frame_count_; int data_bytes_; int fin_frame_count_; // The count of RST_STREAM type frames received. int fin_flag_count_; // The count of frames with the FIN flag set. @@ -264,7 +271,6 @@ TEST_F(SpdyFramerTest, WrongNumberOfHeaders) { frame1.WriteUInt16(SYN_STREAM); frame1.WriteUInt32(0); // Placeholder for the length. frame1.WriteUInt32(3); // stream_id - frame1.WriteUInt32(0); // associated stream id frame1.WriteUInt16(0); // Priority. frame1.WriteUInt16(1); // Wrong number of headers (underflow) @@ -280,7 +286,6 @@ TEST_F(SpdyFramerTest, WrongNumberOfHeaders) { frame2.WriteUInt16(SYN_STREAM); frame2.WriteUInt32(0); // Placeholder for the length. frame2.WriteUInt32(3); // stream_id - frame2.WriteUInt32(0); // associated stream id frame2.WriteUInt16(0); // Priority. frame2.WriteUInt16(100); // Wrong number of headers (overflow) @@ -446,6 +451,15 @@ TEST_F(SpdyFramerTest, Basic) { 0x00, 0x02, 'h', 'h', 0x00, 0x02, 'v', 'v', + 0x80, 0x02, 0x00, 0x08, // HEADERS on Stream #1 + 0x00, 0x00, 0x00, 0x18, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x02, 'h', '2', + 0x00, 0x02, 'v', '2', + 0x00, 0x02, 'h', '3', + 0x00, 0x02, 'v', '3', + 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1 0x00, 0x00, 0x00, 0x0c, 0xde, 0xad, 0xbe, 0xef, @@ -487,6 +501,7 @@ TEST_F(SpdyFramerTest, Basic) { EXPECT_EQ(0, visitor.error_count_); EXPECT_EQ(2, visitor.syn_frame_count_); EXPECT_EQ(0, visitor.syn_reply_frame_count_); + EXPECT_EQ(1, visitor.headers_frame_count_); EXPECT_EQ(24, visitor.data_bytes_); EXPECT_EQ(2, visitor.fin_frame_count_); EXPECT_EQ(0, visitor.fin_flag_count_); @@ -528,6 +543,7 @@ TEST_F(SpdyFramerTest, FinOnDataFrame) { EXPECT_EQ(0, visitor.error_count_); EXPECT_EQ(1, visitor.syn_frame_count_); EXPECT_EQ(1, visitor.syn_reply_frame_count_); + EXPECT_EQ(0, visitor.headers_frame_count_); EXPECT_EQ(16, visitor.data_bytes_); EXPECT_EQ(0, visitor.fin_frame_count_); EXPECT_EQ(0, visitor.fin_flag_count_); @@ -559,6 +575,7 @@ TEST_F(SpdyFramerTest, FinOnSynReplyFrame) { EXPECT_EQ(0, visitor.error_count_); EXPECT_EQ(1, visitor.syn_frame_count_); EXPECT_EQ(1, visitor.syn_reply_frame_count_); + EXPECT_EQ(0, visitor.headers_frame_count_); EXPECT_EQ(0, visitor.data_bytes_); EXPECT_EQ(0, visitor.fin_frame_count_); EXPECT_EQ(1, visitor.fin_flag_count_); @@ -683,7 +700,9 @@ TEST_F(SpdyFramerTest, UnclosedStreamDataCompressors) { const char bytes[] = "this is a test test test test test!"; scoped_ptr<SpdyFrame> send_frame( - send_framer.CreateDataFrame(1, bytes, arraysize(bytes), + send_framer.CreateDataFrame(1, + bytes, + arraysize(bytes), DATA_FLAG_FIN)); EXPECT_TRUE(send_frame.get() != NULL); @@ -698,6 +717,7 @@ TEST_F(SpdyFramerTest, UnclosedStreamDataCompressors) { EXPECT_EQ(0, visitor.error_count_); EXPECT_EQ(1, visitor.syn_frame_count_); EXPECT_EQ(0, visitor.syn_reply_frame_count_); + EXPECT_EQ(0, visitor.headers_frame_count_); EXPECT_EQ(arraysize(bytes), static_cast<unsigned>(visitor.data_bytes_)); EXPECT_EQ(0, visitor.fin_frame_count_); EXPECT_EQ(0, visitor.fin_flag_count_); @@ -1140,6 +1160,112 @@ TEST_F(SpdyFramerTest, CreateGoAway) { } } +TEST_F(SpdyFramerTest, CreateHeadersUncompressed) { + SpdyFramer framer; + FramerSetEnableCompressionHelper(&framer, false); + + { + const char kDescription[] = "HEADERS frame, no FIN"; + + SpdyHeaderBlock headers; + headers["bar"] = "foo"; + headers["foo"] = "bar"; + + const unsigned char kFrameData[] = { + 0x80, 0x02, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x1C, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x03, 'b', 'a', + 'r', 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x03, 'b', 'a', 'r' + }; + scoped_ptr<SpdyFrame> frame(framer.CreateHeaders( + 1, CONTROL_FLAG_NONE, false, &headers)); + CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); + } + + { + const char kDescription[] = + "HEADERS frame with a 0-length header name, FIN, max stream ID"; + + SpdyHeaderBlock headers; + headers[""] = "foo"; + headers["foo"] = "bar"; + + const unsigned char kFrameData[] = { + 0x80, 0x02, 0x00, 0x08, + 0x01, 0x00, 0x00, 0x19, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x03, 'f', 'o', 'o', + 0x00, 0x03, 'b', 'a', + 'r' + }; + scoped_ptr<SpdyFrame> frame(framer.CreateHeaders( + 0x7fffffff, CONTROL_FLAG_FIN, false, &headers)); + CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); + } + + { + const char kDescription[] = + "HEADERS frame with a 0-length header val, FIN, max stream ID"; + + SpdyHeaderBlock headers; + headers["bar"] = "foo"; + headers["foo"] = ""; + + const unsigned char kFrameData[] = { + 0x80, 0x02, 0x00, 0x08, + 0x01, 0x00, 0x00, 0x19, + 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x03, 'b', 'a', + 'r', 0x00, 0x03, 'f', + 'o', 'o', 0x00, 0x03, + 'f', 'o', 'o', 0x00, + 0x00 + }; + scoped_ptr<SpdyFrame> frame(framer.CreateHeaders( + 0x7fffffff, CONTROL_FLAG_FIN, false, &headers)); + CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); + } +} + +TEST_F(SpdyFramerTest, CreateHeadersCompressed) { + SpdyFramer framer; + FramerSetEnableCompressionHelper(&framer, true); + + { + const char kDescription[] = "HEADERS frame, no FIN"; + + SpdyHeaderBlock headers; + headers["bar"] = "foo"; + headers["foo"] = "bar"; + + const unsigned char kFrameData[] = { + 0x80, 0x02, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x21, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x38, 0xea, + 0xdf, 0xa2, 0x51, 0xb2, + 0x62, 0x60, 0x62, 0x60, + 0x4e, 0x4a, 0x2c, 0x62, + 0x60, 0x4e, 0xcb, 0xcf, + 0x87, 0x12, 0x40, 0x2e, + 0x00, 0x00, 0x00, 0xff, + 0xff + }; + scoped_ptr<SpdyFrame> frame(framer.CreateHeaders( + 1, CONTROL_FLAG_NONE, true, &headers)); + CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); + } +} + TEST_F(SpdyFramerTest, CreateWindowUpdate) { SpdyFramer framer; @@ -1180,4 +1306,80 @@ TEST_F(SpdyFramerTest, CreateWindowUpdate) { } } +// This test case reproduces conditions that caused ExpandControlFrameBuffer to +// fail to expand the buffer control frame buffer when it should have, allowing +// the framer to overrun the buffer, and smash other heap contents. This test +// relies on the debug version of the heap manager, which checks for buffer +// overrun errors during delete processing. Regression test for b/2974814. +TEST_F(SpdyFramerTest, ExpandBuffer_HeapSmash) { + // Sweep through the area of problematic values, to make sure we always cover + // the danger zone, even if it moves around at bit due to SPDY changes. + for (uint16 val2_len = SpdyFramer::kControlFrameBufferInitialSize - 50; + val2_len < SpdyFramer::kControlFrameBufferInitialSize; + val2_len++) { + std::string val2 = std::string(val2_len, 'a'); + SpdyHeaderBlock headers; + headers["bar"] = "foo"; + headers["foo"] = "baz"; + headers["grue"] = val2.c_str(); + SpdyFramer framer; + scoped_ptr<SpdySynStreamControlFrame> template_frame( + framer.CreateSynStream(1, // stream_id + 0, // associated_stream_id + 1, // priority + CONTROL_FLAG_NONE, + false, // compress + &headers)); + EXPECT_TRUE(template_frame.get() != NULL); + TestSpdyVisitor visitor; + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(template_frame.get()->data()), + template_frame.get()->length() + SpdyControlFrame::size()); + EXPECT_EQ(1, visitor.syn_frame_count_); + } +} + +std::string RandomString(int length) { + std::string rv; + for (int index = 0; index < length; index++) + rv += static_cast<char>('a' + (rand() % 26)); + return rv; +} + +// Stress that we can handle a really large header block compression and +// decompression. +TEST_F(SpdyFramerTest, HugeHeaderBlock) { + // Loop targetting various sizes which will potentially jam up the + // frame compressor/decompressor. + SpdyFramer compress_framer; + SpdyFramer decompress_framer; + for (size_t target_size = 1024; + target_size < SpdyFramer::kControlFrameBufferInitialSize; + target_size += 1024) { + SpdyHeaderBlock headers; + for (size_t index = 0; index < target_size; ++index) { + std::string name = RandomString(4); + std::string value = RandomString(8); + headers[name] = value; + } + + // Encode the header block into a SynStream frame. + scoped_ptr<SpdySynStreamControlFrame> frame( + compress_framer.CreateSynStream(1, + 0, + 1, + CONTROL_FLAG_NONE, + true, + &headers)); + // The point of this test is to exercise the limits. So, it is ok if the + // frame was too large to encode, or if the decompress fails. We just want + // to make sure we don't crash. + if (frame.get() != NULL) { + // Now that same header block should decompress just fine. + SpdyHeaderBlock new_headers; + decompress_framer.ParseHeaderBlock(frame.get(), &new_headers); + } + } +} + } // namespace diff --git a/net/spdy/spdy_http_stream.cc b/net/spdy/spdy_http_stream.cc index 286d557..fedadd7 100644 --- a/net/spdy/spdy_http_stream.cc +++ b/net/spdy/spdy_http_stream.cc @@ -27,6 +27,7 @@ SpdyHttpStream::SpdyHttpStream(SpdySession* spdy_session, bool direct) spdy_session_(spdy_session), response_info_(NULL), download_finished_(false), + response_headers_received_(false), user_callback_(NULL), user_buffer_len_(0), buffered_read_callback_pending_(false), @@ -242,29 +243,41 @@ int SpdyHttpStream::OnResponseReceived(const spdy::SpdyHeaderBlock& response, response_info_ = push_response_info_.get(); } + // If the response is already received, these headers are too late. + if (response_headers_received_) { + LOG(WARNING) << "SpdyHttpStream headers received after response started."; + return OK; + } + // TODO(mbelshe): This is the time of all headers received, not just time // to first byte. - DCHECK(response_info_->response_time.is_null()); response_info_->response_time = base::Time::Now(); if (!SpdyHeadersToHttpResponse(response, response_info_)) { - status = ERR_INVALID_RESPONSE; - } else { - stream_->GetSSLInfo(&response_info_->ssl_info, - &response_info_->was_npn_negotiated); - response_info_->request_time = stream_->GetRequestTime(); - response_info_->vary_data.Init(*request_info_, *response_info_->headers); - // TODO(ahendrickson): This is recorded after the entire SYN_STREAM control - // frame has been received and processed. Move to framer? - response_info_->response_time = response_time; + // We might not have complete headers yet. + return ERR_INCOMPLETE_SPDY_HEADERS; } + response_headers_received_ = true; + stream_->GetSSLInfo(&response_info_->ssl_info, + &response_info_->was_npn_negotiated); + response_info_->request_time = stream_->GetRequestTime(); + response_info_->vary_data.Init(*request_info_, *response_info_->headers); + // TODO(ahendrickson): This is recorded after the entire SYN_STREAM control + // frame has been received and processed. Move to framer? + response_info_->response_time = response_time; + if (user_callback_) DoCallback(status); return status; } void SpdyHttpStream::OnDataReceived(const char* data, int length) { + // SpdyStream won't call us with data if the header block didn't contain a + // valid set of headers. So we don't expect to not have headers received + // here. + DCHECK(response_headers_received_); + // Note that data may be received for a SpdyStream prior to the user calling // ReadResponseBody(), therefore user_buffer_ may be NULL. This may often // happen for server initiated streams. diff --git a/net/spdy/spdy_http_stream.h b/net/spdy/spdy_http_stream.h index a878ff9..cd351cd 100644 --- a/net/spdy/spdy_http_stream.h +++ b/net/spdy/spdy_http_stream.h @@ -37,99 +37,50 @@ class SpdyHttpStream : public SpdyStream::Delegate, public HttpStream { SpdyStream* stream() { return stream_.get(); } - // =================================================== - // HttpStream methods: + // Cancels any callbacks from being invoked and deletes the stream. + void Cancel(); - // Initialize stream. Must be called before calling SendRequest(). + // HttpStream methods: virtual int InitializeStream(const HttpRequestInfo* request_info, const BoundNetLog& net_log, CompletionCallback* callback); - - // Sends the request. - // |callback| is used when this completes asynchronously. - // SpdyHttpStream takes ownership of |upload_data|. |upload_data| may be NULL. - // The actual SYN_STREAM packet will be sent if the stream is non-pushed. virtual int SendRequest(const HttpRequestHeaders& headers, UploadDataStream* request_body, HttpResponseInfo* response, CompletionCallback* callback); - - // Returns the number of bytes uploaded. virtual uint64 GetUploadProgress() const; - - // Reads the response headers. Returns a net error code. virtual int ReadResponseHeaders(CompletionCallback* callback); - virtual const HttpResponseInfo* GetResponseInfo() const; - - // Reads the response body. Returns a net error code or the number of bytes - // read. - virtual int ReadResponseBody( - IOBuffer* buf, int buf_len, CompletionCallback* callback); - - // Closes the stream. + virtual int ReadResponseBody(IOBuffer* buf, + int buf_len, + CompletionCallback* callback); virtual void Close(bool not_reusable); - virtual HttpStream* RenewStreamForAuth() { return NULL; } - - // Indicates if the response body has been completely read. virtual bool IsResponseBodyComplete() const { if (!stream_) return false; return stream_->closed(); } - - // With SPDY the end of response is always detectable. virtual bool CanFindEndOfResponse() const { return true; } - - // A SPDY stream never has more data after the FIN. virtual bool IsMoreDataBuffered() const { return false; } - virtual bool IsConnectionReused() const { return spdy_session_->IsReused(); } - virtual void SetConnectionReused() { // SPDY doesn't need an indicator here. } - virtual void GetSSLInfo(SSLInfo* ssl_info); virtual void GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info); - // =================================================== - // SpdyStream::Delegate. - - // Cancels any callbacks from being invoked and deletes the stream. - void Cancel(); - + // SpdyStream::Delegate methods: virtual bool OnSendHeadersComplete(int status); virtual int OnSendBody(); virtual bool OnSendBodyComplete(int status); - - // Called by the SpdySession when a response (e.g. a SYN_REPLY) has been - // received for this stream. - // SpdyHttpSession calls back |callback| set by SendRequest or - // ReadResponseHeaders. virtual int OnResponseReceived(const spdy::SpdyHeaderBlock& response, base::Time response_time, int status); - - // Called by the SpdySession when response data has been received for this - // stream. This callback may be called multiple times as data arrives - // from the network, and will never be called prior to OnResponseReceived. - // SpdyHttpSession schedule to call back |callback| set by ReadResponseBody. virtual void OnDataReceived(const char* buffer, int bytes); - - // For HTTP streams, no data is sent from the client while in the OPEN state, - // so OnDataSent is never called. virtual void OnDataSent(int length); - - // Called by the SpdySession when the request is finished. This callback - // will always be called at the end of the request and signals to the - // stream that the stream has no more network events. No further callbacks - // to the stream will be made after this call. - // SpdyHttpSession call back |callback| set by SendRequest, - // ReadResponseHeaders or ReadResponseBody. virtual void OnClose(int status); private: @@ -161,6 +112,7 @@ class SpdyHttpStream : public SpdyStream::Delegate, public HttpStream { scoped_ptr<HttpResponseInfo> push_response_info_; bool download_finished_; + bool response_headers_received_; // Indicates waiting for more HEADERS. // We buffer the response body as it arrives asynchronously from the stream. // TODO(mbelshe): is this infinite buffering? diff --git a/net/spdy/spdy_http_utils.cc b/net/spdy/spdy_http_utils.cc index 09ac79c..e367c42 100644 --- a/net/spdy/spdy_http_utils.cc +++ b/net/spdy/spdy_http_utils.cc @@ -27,18 +27,14 @@ bool SpdyHeadersToHttpResponse(const spdy::SpdyHeaderBlock& headers, // The "status" and "version" headers are required. spdy::SpdyHeaderBlock::const_iterator it; it = headers.find("status"); - if (it == headers.end()) { - LOG(ERROR) << "SpdyHeaderBlock without status header."; + if (it == headers.end()) return false; - } status = it->second; // Grab the version. If not provided by the server, it = headers.find("version"); - if (it == headers.end()) { - LOG(ERROR) << "SpdyHeaderBlock without version header."; + if (it == headers.end()) return false; - } version = it->second; response->response_time = base::Time::Now(); diff --git a/net/spdy/spdy_network_transaction_unittest.cc b/net/spdy/spdy_network_transaction_unittest.cc index 7400756..730d53f 100644 --- a/net/spdy/spdy_network_transaction_unittest.cc +++ b/net/spdy/spdy_network_transaction_unittest.cc @@ -177,7 +177,6 @@ class SpdyNetworkTransactionTest output_.status_line = response->headers->GetStatusLine(); output_.response_info = *response; // Make a copy so we can verify. output_.rv = ReadTransaction(trans_.get(), &output_.response_data); - EXPECT_EQ(OK, output_.rv); return; } @@ -352,7 +351,7 @@ class SpdyNetworkTransactionTest // to skip over data destined for other transactions while we consume // the data for |trans|. int ReadResult(HttpNetworkTransaction* trans, - OrderedSocketData* data, + StaticSocketDataProvider* data, std::string* result) { const int kSize = 3000; @@ -397,19 +396,14 @@ class SpdyNetworkTransactionTest EXPECT_EQ(0u, spdy_session->num_unclaimed_pushed_streams()); } - void RunServerPushTest(MockWrite writes[], int writes_length, - MockRead reads[], int reads_length, + void RunServerPushTest(OrderedSocketData* data, HttpResponseInfo* response, HttpResponseInfo* response2, std::string& expected) { - scoped_refptr<OrderedSocketData> data( - new OrderedSocketData(reads, reads_length, - writes, writes_length)); NormalSpdyTransactionHelper helper(CreateGetRequest(), BoundNetLog(), GetParam()); - helper.RunPreTestSetup(); - helper.AddData(data.get()); + helper.AddData(data); HttpNetworkTransaction* trans = helper.trans(); @@ -1198,7 +1192,7 @@ TEST_P(SpdyNetworkTransactionTest, Put) { "content-length", "0" }; scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyPacket(kSynStartHeader, NULL, 0, - kPutHeaders, arraysize(kPutHeaders)/2)); + kPutHeaders, arraysize(kPutHeaders) / 2)); MockWrite writes[] = { CreateMockWrite(*req) }; @@ -1222,7 +1216,7 @@ TEST_P(SpdyNetworkTransactionTest, Put) { "content-length", "1234" }; scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyPacket(kSynReplyHeader, - NULL, 0, kStandardGetHeaders, arraysize(kStandardGetHeaders)/2)); + NULL, 0, kStandardGetHeaders, arraysize(kStandardGetHeaders) / 2)); MockRead reads[] = { CreateMockRead(*resp), CreateMockRead(*body), @@ -1269,7 +1263,7 @@ TEST_P(SpdyNetworkTransactionTest, Head) { "content-length", "0" }; scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyPacket(kSynStartHeader, NULL, 0, - kHeadHeaders, arraysize(kHeadHeaders)/2)); + kHeadHeaders, arraysize(kHeadHeaders) / 2)); MockWrite writes[] = { CreateMockWrite(*req) }; @@ -1293,7 +1287,7 @@ TEST_P(SpdyNetworkTransactionTest, Head) { "content-length", "1234" }; scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyPacket(kSynReplyHeader, - NULL, 0, kStandardGetHeaders, arraysize(kStandardGetHeaders)/2)); + NULL, 0, kStandardGetHeaders, arraysize(kStandardGetHeaders) / 2)); MockRead reads[] = { CreateMockRead(*resp), CreateMockRead(*body), @@ -1650,7 +1644,7 @@ TEST_P(SpdyNetworkTransactionTest, WindowUpdateReceived) { SpdyHttpStream* stream = static_cast<SpdyHttpStream*>(trans->stream_.get()); ASSERT_TRUE(stream != NULL); ASSERT_TRUE(stream->stream() != NULL); - EXPECT_EQ(spdy::kInitialWindowSize + + EXPECT_EQ(static_cast<int>(spdy::kSpdyStreamInitialWindowSize) + kDeltaWindowSize * kDeltaCount - kMaxSpdyFrameChunkSize * kFrameCount, stream->stream()->send_window_size()); @@ -1709,8 +1703,9 @@ TEST_P(SpdyNetworkTransactionTest, WindowUpdateSent) { ASSERT_TRUE(stream != NULL); ASSERT_TRUE(stream->stream() != NULL); - EXPECT_EQ(spdy::kInitialWindowSize - kUploadDataSize, - stream->stream()->recv_window_size()); + EXPECT_EQ( + static_cast<int>(spdy::kSpdyStreamInitialWindowSize) - kUploadDataSize, + stream->stream()->recv_window_size()); const HttpResponseInfo* response = trans->GetResponseInfo(); ASSERT_TRUE(response != NULL); @@ -1841,17 +1836,19 @@ TEST_P(SpdyNetworkTransactionTest, FlowControlStallResume) { // frames plus SYN_STREAM plus the last data frame; also we need another // data frame that we will send once the WINDOW_UPDATE is received, // therefore +3. - size_t nwrites = spdy::kInitialWindowSize / kMaxSpdyFrameChunkSize + 3; + size_t nwrites = + spdy::kSpdyStreamInitialWindowSize / kMaxSpdyFrameChunkSize + 3; // Calculate last frame's size; 0 size data frame is legal. - size_t last_frame_size = spdy::kInitialWindowSize % kMaxSpdyFrameChunkSize; + size_t last_frame_size = + spdy::kSpdyStreamInitialWindowSize % kMaxSpdyFrameChunkSize; // Construct content for a data frame of maximum size. scoped_ptr<std::string> content( new std::string(kMaxSpdyFrameChunkSize, 'a')); scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyPost( - spdy::kInitialWindowSize + kUploadDataSize, NULL, 0)); + spdy::kSpdyStreamInitialWindowSize + kUploadDataSize, NULL, 0)); // Full frames. scoped_ptr<spdy::SpdyFrame> body1( @@ -1898,7 +1895,7 @@ TEST_P(SpdyNetworkTransactionTest, FlowControlStallResume) { request.url = GURL("http://www.google.com/"); request.upload_data = new UploadData(); scoped_ptr<std::string> upload_data( - new std::string(spdy::kInitialWindowSize, 'a')); + new std::string(spdy::kSpdyStreamInitialWindowSize, 'a')); upload_data->append(kUploadData, kUploadDataSize); request.upload_data->AppendBytes(upload_data->c_str(), upload_data->size()); NormalSpdyTransactionHelper helper(request, @@ -2199,11 +2196,11 @@ TEST_P(SpdyNetworkTransactionTest, RedirectGetRequest) { // Setup writes/reads to www.google.com scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyPacket( - kSynStartHeader, kExtraHeaders, arraysize(kExtraHeaders)/2, - kStandardGetHeaders, arraysize(kStandardGetHeaders)/2)); + kSynStartHeader, kExtraHeaders, arraysize(kExtraHeaders) / 2, + kStandardGetHeaders, arraysize(kStandardGetHeaders) / 2)); scoped_ptr<spdy::SpdyFrame> req2(ConstructSpdyPacket( - kSynStartHeader, kExtraHeaders, arraysize(kExtraHeaders)/2, - kStandardGetHeaders2, arraysize(kStandardGetHeaders2)/2)); + kSynStartHeader, kExtraHeaders, arraysize(kExtraHeaders) / 2, + kStandardGetHeaders2, arraysize(kStandardGetHeaders2) / 2)); scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReplyRedirect(1)); MockWrite writes[] = { CreateMockWrite(*req, 1), @@ -2308,16 +2305,27 @@ TEST_P(SpdyNetworkTransactionTest, RedirectServerPush) { }; // Setup writes/reads to www.google.com - scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyPacket( - kSynStartHeader, kExtraHeaders, arraysize(kExtraHeaders)/2, - kStandardGetHeaders, arraysize(kStandardGetHeaders)/2)); - scoped_ptr<spdy::SpdyFrame> req2(ConstructSpdyPacket( - kSynStartHeader, kExtraHeaders, arraysize(kExtraHeaders)/2, - kStandardGetHeaders2, arraysize(kStandardGetHeaders2)/2)); + scoped_ptr<spdy::SpdyFrame> req( + ConstructSpdyPacket(kSynStartHeader, + kExtraHeaders, + arraysize(kExtraHeaders) / 2, + kStandardGetHeaders, + arraysize(kStandardGetHeaders) / 2)); + scoped_ptr<spdy::SpdyFrame> req2( + ConstructSpdyPacket(kSynStartHeader, + kExtraHeaders, + arraysize(kExtraHeaders) / 2, + kStandardGetHeaders2, + arraysize(kStandardGetHeaders2) / 2)); scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); - scoped_ptr<spdy::SpdyFrame> rep(ConstructSpdyPush(NULL, 0, 2, 1, "/foo.dat", - "301 Moved Permanently", "http://www.foo.com/index.php", - "http://www.foo.com/index.php")); + scoped_ptr<spdy::SpdyFrame> rep( + ConstructSpdyPush(NULL, + 0, + 2, + 1, + "http://www.google.com/foo.dat", + "301 Moved Permanently", + "http://www.foo.com/index.php")); scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); MockWrite writes[] = { CreateMockWrite(*req, 1), @@ -2411,7 +2419,11 @@ TEST_P(SpdyNetworkTransactionTest, ServerPushSingleDataFrame) { scoped_ptr<spdy::SpdyFrame> stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); scoped_ptr<spdy::SpdyFrame> - stream2_syn(ConstructSpdyPush(NULL, 0, 2, 1, "/foo.dat")); + stream2_syn(ConstructSpdyPush(NULL, + 0, + 2, + 1, + "http://www.google.com/foo.dat")); MockRead reads[] = { CreateMockRead(*stream1_reply, 2), CreateMockRead(*stream2_syn, 3), @@ -2424,8 +2436,15 @@ TEST_P(SpdyNetworkTransactionTest, ServerPushSingleDataFrame) { HttpResponseInfo response; HttpResponseInfo response2; std::string expected_push_result("pushed"); - RunServerPushTest(writes, arraysize(writes), reads, arraysize(reads), - &response, &response2, expected_push_result); + scoped_refptr<OrderedSocketData> data(new OrderedSocketData( + reads, + arraysize(reads), + writes, + arraysize(writes))); + RunServerPushTest(data.get(), + &response, + &response2, + expected_push_result); // Verify the SYN_REPLY. EXPECT_TRUE(response.headers != NULL); @@ -2451,7 +2470,11 @@ TEST_P(SpdyNetworkTransactionTest, ServerPushSingleDataFrame2) { scoped_ptr<spdy::SpdyFrame> stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); scoped_ptr<spdy::SpdyFrame> - stream2_syn(ConstructSpdyPush(NULL, 0, 2, 1, "/foo.dat")); + stream2_syn(ConstructSpdyPush(NULL, + 0, + 2, + 1, + "http://www.google.com/foo.dat")); scoped_ptr<spdy::SpdyFrame> stream1_body(ConstructSpdyBodyFrame(1, true)); MockRead reads[] = { @@ -2466,8 +2489,15 @@ TEST_P(SpdyNetworkTransactionTest, ServerPushSingleDataFrame2) { HttpResponseInfo response; HttpResponseInfo response2; std::string expected_push_result("pushed"); - RunServerPushTest(writes, arraysize(writes), reads, arraysize(reads), - &response, &response2, expected_push_result); + scoped_refptr<OrderedSocketData> data(new OrderedSocketData( + reads, + arraysize(reads), + writes, + arraysize(writes))); + RunServerPushTest(data.get(), + &response, + &response2, + expected_push_result); // Verify the SYN_REPLY. EXPECT_TRUE(response.headers != NULL); @@ -2490,7 +2520,11 @@ TEST_P(SpdyNetworkTransactionTest, ServerPushServerAborted) { scoped_ptr<spdy::SpdyFrame> stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); scoped_ptr<spdy::SpdyFrame> - stream2_syn(ConstructSpdyPush(NULL, 0, 2, 1, "/foo.dat")); + stream2_syn(ConstructSpdyPush(NULL, + 0, + 2, + 1, + "http://www.google.com/foo.dat")); scoped_ptr<spdy::SpdyFrame> stream2_rst(ConstructSpdyRstStream(2, spdy::PROTOCOL_ERROR)); MockRead reads[] = { @@ -2558,9 +2592,17 @@ TEST_P(SpdyNetworkTransactionTest, ServerPushDuplicate) { scoped_ptr<spdy::SpdyFrame> stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); scoped_ptr<spdy::SpdyFrame> - stream2_syn(ConstructSpdyPush(NULL, 0, 2, 1, "/foo.dat")); + stream2_syn(ConstructSpdyPush(NULL, + 0, + 2, + 1, + "http://www.google.com/foo.dat")); scoped_ptr<spdy::SpdyFrame> - stream3_syn(ConstructSpdyPush(NULL, 0, 4, 1, "/foo.dat")); + stream3_syn(ConstructSpdyPush(NULL, + 0, + 4, + 1, + "http://www.google.com/foo.dat")); MockRead reads[] = { CreateMockRead(*stream1_reply, 2), CreateMockRead(*stream2_syn, 3), @@ -2574,8 +2616,15 @@ TEST_P(SpdyNetworkTransactionTest, ServerPushDuplicate) { HttpResponseInfo response; HttpResponseInfo response2; std::string expected_push_result("pushed"); - RunServerPushTest(writes, arraysize(writes), reads, arraysize(reads), - &response, &response2, expected_push_result); + scoped_refptr<OrderedSocketData> data(new OrderedSocketData( + reads, + arraysize(reads), + writes, + arraysize(writes))); + RunServerPushTest(data.get(), + &response, + &response2, + expected_push_result); // Verify the SYN_REPLY. EXPECT_TRUE(response.headers != NULL); @@ -2607,7 +2656,11 @@ TEST_P(SpdyNetworkTransactionTest, ServerPushMultipleDataFrame) { scoped_ptr<spdy::SpdyFrame> stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); scoped_ptr<spdy::SpdyFrame> - stream2_syn(ConstructSpdyPush(NULL, 0, 2, 1, "/foo.dat")); + stream2_syn(ConstructSpdyPush(NULL, + 0, + 2, + 1, + "http://www.google.com/foo.dat")); MockRead reads[] = { CreateMockRead(*stream1_reply, 2), CreateMockRead(*stream2_syn, 3), @@ -2626,8 +2679,15 @@ TEST_P(SpdyNetworkTransactionTest, ServerPushMultipleDataFrame) { HttpResponseInfo response; HttpResponseInfo response2; std::string expected_push_result("pushed my darling hello my baby"); - RunServerPushTest(writes, arraysize(writes), reads, arraysize(reads), - &response, &response2, expected_push_result); + scoped_refptr<OrderedSocketData> data(new OrderedSocketData( + reads, + arraysize(reads), + writes, + arraysize(writes))); + RunServerPushTest(data.get(), + &response, + &response2, + expected_push_result); // Verify the SYN_REPLY. EXPECT_TRUE(response.headers != NULL); @@ -2659,7 +2719,11 @@ TEST_P(SpdyNetworkTransactionTest, ServerPushMultipleDataFrameInterrupted) { scoped_ptr<spdy::SpdyFrame> stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); scoped_ptr<spdy::SpdyFrame> - stream2_syn(ConstructSpdyPush(NULL, 0, 2, 1, "/foo.dat")); + stream2_syn(ConstructSpdyPush(NULL, + 0, + 2, + 1, + "http://www.google.com/foo.dat")); MockRead reads[] = { CreateMockRead(*stream1_reply, 2), CreateMockRead(*stream2_syn, 3), @@ -2679,8 +2743,15 @@ TEST_P(SpdyNetworkTransactionTest, ServerPushMultipleDataFrameInterrupted) { HttpResponseInfo response; HttpResponseInfo response2; std::string expected_push_result("pushed my darling hello my baby"); - RunServerPushTest(writes, arraysize(writes), reads, arraysize(reads), - &response, &response2, expected_push_result); + scoped_refptr<OrderedSocketData> data(new OrderedSocketData( + reads, + arraysize(reads), + writes, + arraysize(writes))); + RunServerPushTest(data.get(), + &response, + &response2, + expected_push_result); // Verify the SYN_REPLY. EXPECT_TRUE(response.headers != NULL); @@ -2706,7 +2777,11 @@ TEST_P(SpdyNetworkTransactionTest, ServerPushInvalidAssociatedStreamID0) { scoped_ptr<spdy::SpdyFrame> stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); scoped_ptr<spdy::SpdyFrame> - stream2_syn(ConstructSpdyPush(NULL, 0, 2, 0, "/foo.dat")); + stream2_syn(ConstructSpdyPush(NULL, + 0, + 2, + 0, + "http://www.google.com/foo.dat")); MockRead reads[] = { CreateMockRead(*stream1_reply, 2), CreateMockRead(*stream2_syn, 3), @@ -2763,7 +2838,11 @@ TEST_P(SpdyNetworkTransactionTest, ServerPushInvalidAssociatedStreamID9) { scoped_ptr<spdy::SpdyFrame> stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); scoped_ptr<spdy::SpdyFrame> - stream2_syn(ConstructSpdyPush(NULL, 0, 2, 9, "/foo.dat")); + stream2_syn(ConstructSpdyPush(NULL, + 0, + 2, + 9, + "http://www.google.com/foo.dat")); MockRead reads[] = { CreateMockRead(*stream1_reply, 2), CreateMockRead(*stream2_syn, 3), @@ -3181,7 +3260,7 @@ TEST_P(SpdyNetworkTransactionTest, InvalidSynReply) { BoundNetLog(), GetParam()); helper.RunToCompletion(data.get()); TransactionHelperResult out = helper.output(); - EXPECT_EQ(ERR_INVALID_RESPONSE, out.rv); + EXPECT_EQ(ERR_INCOMPLETE_SPDY_HEADERS, out.rv); } } @@ -4602,7 +4681,8 @@ TEST_P(SpdyNetworkTransactionTest, SpdyBasicAuth) { }; scoped_ptr<spdy::SpdyFrame> req_get_authorization( ConstructSpdyGet( - kExtraAuthorizationHeaders, arraysize(kExtraAuthorizationHeaders)/2, + kExtraAuthorizationHeaders, + arraysize(kExtraAuthorizationHeaders) / 2, false, 3, LOWEST)); MockWrite spdy_writes[] = { CreateMockWrite(*req_get, 1), @@ -4619,7 +4699,8 @@ TEST_P(SpdyNetworkTransactionTest, SpdyBasicAuth) { scoped_ptr<spdy::SpdyFrame> resp_authentication( ConstructSpdySynReplyError( "401 Authentication Required", - kExtraAuthenticationHeaders, arraysize(kExtraAuthenticationHeaders)/2, + kExtraAuthenticationHeaders, + arraysize(kExtraAuthenticationHeaders) / 2, 1)); scoped_ptr<spdy::SpdyFrame> body_authentication( ConstructSpdyBodyFrame(1, true)); @@ -4677,4 +4758,549 @@ TEST_P(SpdyNetworkTransactionTest, SpdyBasicAuth) { EXPECT_TRUE(response_restart->auth_challenge.get() == NULL); } +TEST_P(SpdyNetworkTransactionTest, ServerPushWithHeaders) { + static const unsigned char kPushBodyFrame[] = { + 0x00, 0x00, 0x00, 0x02, // header, ID + 0x01, 0x00, 0x00, 0x06, // FIN, length + 'p', 'u', 's', 'h', 'e', 'd' // "pushed" + }; + scoped_ptr<spdy::SpdyFrame> + stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> + stream1_body(ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + }; + + static const char* const kInitialHeaders[] = { + "url", + "http://www.google.com/foo.dat", + }; + static const char* const kLateHeaders[] = { + "hello", + "bye", + "status", + "200", + "version", + "HTTP/1.1" + }; + scoped_ptr<spdy::SpdyFrame> + stream2_syn(ConstructSpdyControlFrame(kInitialHeaders, + arraysize(kInitialHeaders) / 2, + false, + 2, + LOWEST, + spdy::SYN_STREAM, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 1)); + scoped_ptr<spdy::SpdyFrame> + stream2_headers(ConstructSpdyControlFrame(kLateHeaders, + arraysize(kLateHeaders) / 2, + false, + 2, + LOWEST, + spdy::HEADERS, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 0)); + + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + CreateMockRead(*stream2_headers, 4), + CreateMockRead(*stream1_body, 5, false), + MockRead(true, reinterpret_cast<const char*>(kPushBodyFrame), + arraysize(kPushBodyFrame), 6), + MockRead(true, ERR_IO_PENDING, 7), // Force a pause + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + std::string expected_push_result("pushed"); + scoped_refptr<OrderedSocketData> data(new OrderedSocketData( + reads, + arraysize(reads), + writes, + arraysize(writes))); + RunServerPushTest(data.get(), + &response, + &response2, + expected_push_result); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + // Verify the pushed stream. + EXPECT_TRUE(response2.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionTest, ServerPushClaimBeforeHeaders) { + // We push a stream and attempt to claim it before the headers come down. + static const unsigned char kPushBodyFrame[] = { + 0x00, 0x00, 0x00, 0x02, // header, ID + 0x01, 0x00, 0x00, 0x06, // FIN, length + 'p', 'u', 's', 'h', 'e', 'd' // "pushed" + }; + scoped_ptr<spdy::SpdyFrame> + stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> + stream1_body(ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 0, false), + }; + + static const char* const kInitialHeaders[] = { + "url", + "http://www.google.com/foo.dat", + }; + static const char* const kLateHeaders[] = { + "hello", + "bye", + "status", + "200", + "version", + "HTTP/1.1" + }; + scoped_ptr<spdy::SpdyFrame> + stream2_syn(ConstructSpdyControlFrame(kInitialHeaders, + arraysize(kInitialHeaders) / 2, + false, + 2, + LOWEST, + spdy::SYN_STREAM, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 1)); + scoped_ptr<spdy::SpdyFrame> + stream2_headers(ConstructSpdyControlFrame(kLateHeaders, + arraysize(kLateHeaders) / 2, + false, + 2, + LOWEST, + spdy::HEADERS, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 0)); + + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 1), + CreateMockRead(*stream2_syn, 2), + CreateMockRead(*stream1_body, 3), + CreateMockRead(*stream2_headers, 4), + MockRead(true, reinterpret_cast<const char*>(kPushBodyFrame), + arraysize(kPushBodyFrame), 5), + MockRead(true, 0, 5), // EOF + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + std::string expected_push_result("pushed"); + scoped_refptr<DeterministicSocketData> data(new DeterministicSocketData( + reads, + arraysize(reads), + writes, + arraysize(writes))); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.SetDeterministic(); + helper.AddDeterministicData(static_cast<DeterministicSocketData*>(data)); + helper.RunPreTestSetup(); + + HttpNetworkTransaction* trans = helper.trans(); + + // Run until we've received the primary SYN_STREAM, the pushed SYN_STREAM, + // and the body of the primary stream, but before we've received the HEADERS + // for the pushed stream. + data->SetStop(3); + + // Start the transaction. + TestCompletionCallback callback; + int rv = trans->Start(&CreateGetRequest(), &callback, BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + data->Run(); + rv = callback.WaitForResult(); + EXPECT_EQ(0, rv); + + // Request the pushed path. At this point, we've received the push, but the + // headers are not yet complete. + scoped_ptr<HttpNetworkTransaction> trans2( + new HttpNetworkTransaction(helper.session())); + rv = trans2->Start(&CreateGetPushRequest(), &callback, BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + data->RunFor(3); + MessageLoop::current()->RunAllPending(); + + // Read the server push body. + std::string result2; + ReadResult(trans2.get(), data.get(), &result2); + // Read the response body. + std::string result; + ReadResult(trans, data, &result); + + // Verify that we consumed all test data. + EXPECT_TRUE(data->at_read_eof()); + EXPECT_TRUE(data->at_write_eof()); + + // Verify that the received push data is same as the expected push data. + EXPECT_EQ(result2.compare(expected_push_result), 0) + << "Received data: " + << result2 + << "||||| Expected data: " + << expected_push_result; + + // Verify the SYN_REPLY. + // Copy the response info, because trans goes away. + response = *trans->GetResponseInfo(); + response2 = *trans2->GetResponseInfo(); + + VerifyStreamsClosed(helper); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + // Verify the pushed stream. + EXPECT_TRUE(response2.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionTest, ServerPushWithTwoHeaderFrames) { + // We push a stream and attempt to claim it before the headers come down. + static const unsigned char kPushBodyFrame[] = { + 0x00, 0x00, 0x00, 0x02, // header, ID + 0x01, 0x00, 0x00, 0x06, // FIN, length + 'p', 'u', 's', 'h', 'e', 'd' // "pushed" + }; + scoped_ptr<spdy::SpdyFrame> + stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> + stream1_body(ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 0, false), + }; + + static const char* const kInitialHeaders[] = { + "url", + "http://www.google.com/foo.dat", + }; + static const char* const kMiddleHeaders[] = { + "hello", + "bye", + }; + static const char* const kLateHeaders[] = { + "status", + "200", + "version", + "HTTP/1.1" + }; + scoped_ptr<spdy::SpdyFrame> + stream2_syn(ConstructSpdyControlFrame(kInitialHeaders, + arraysize(kInitialHeaders) / 2, + false, + 2, + LOWEST, + spdy::SYN_STREAM, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 1)); + scoped_ptr<spdy::SpdyFrame> + stream2_headers1(ConstructSpdyControlFrame(kMiddleHeaders, + arraysize(kMiddleHeaders) / 2, + false, + 2, + LOWEST, + spdy::HEADERS, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 0)); + scoped_ptr<spdy::SpdyFrame> + stream2_headers2(ConstructSpdyControlFrame(kLateHeaders, + arraysize(kLateHeaders) / 2, + false, + 2, + LOWEST, + spdy::HEADERS, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 0)); + + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 1), + CreateMockRead(*stream2_syn, 2), + CreateMockRead(*stream1_body, 3), + CreateMockRead(*stream2_headers1, 4), + CreateMockRead(*stream2_headers2, 5), + MockRead(true, reinterpret_cast<const char*>(kPushBodyFrame), + arraysize(kPushBodyFrame), 6), + MockRead(true, 0, 6), // EOF + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + std::string expected_push_result("pushed"); + scoped_refptr<DeterministicSocketData> data(new DeterministicSocketData( + reads, + arraysize(reads), + writes, + arraysize(writes))); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.SetDeterministic(); + helper.AddDeterministicData(static_cast<DeterministicSocketData*>(data)); + helper.RunPreTestSetup(); + + HttpNetworkTransaction* trans = helper.trans(); + + // Run until we've received the primary SYN_STREAM, the pushed SYN_STREAM, + // the first HEADERS frame, and the body of the primary stream, but before + // we've received the final HEADERS for the pushed stream. + data->SetStop(4); + + // Start the transaction. + TestCompletionCallback callback; + int rv = trans->Start(&CreateGetRequest(), &callback, BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + data->Run(); + rv = callback.WaitForResult(); + EXPECT_EQ(0, rv); + + // Request the pushed path. At this point, we've received the push, but the + // headers are not yet complete. + scoped_ptr<HttpNetworkTransaction> trans2( + new HttpNetworkTransaction(helper.session())); + rv = trans2->Start(&CreateGetPushRequest(), &callback, BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + data->RunFor(3); + MessageLoop::current()->RunAllPending(); + + // Read the server push body. + std::string result2; + ReadResult(trans2.get(), data, &result2); + // Read the response body. + std::string result; + ReadResult(trans, data, &result); + + // Verify that we consumed all test data. + EXPECT_TRUE(data->at_read_eof()); + EXPECT_TRUE(data->at_write_eof()); + + // Verify that the received push data is same as the expected push data. + EXPECT_EQ(result2.compare(expected_push_result), 0) + << "Received data: " + << result2 + << "||||| Expected data: " + << expected_push_result; + + // Verify the SYN_REPLY. + // Copy the response info, because trans goes away. + response = *trans->GetResponseInfo(); + response2 = *trans2->GetResponseInfo(); + + VerifyStreamsClosed(helper); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + // Verify the pushed stream. + EXPECT_TRUE(response2.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); + + // Verify we got all the headers + EXPECT_TRUE(response2.headers->HasHeaderValue( + "url", + "http://www.google.com/foo.dat")); + EXPECT_TRUE(response2.headers->HasHeaderValue("hello", "bye")); + EXPECT_TRUE(response2.headers->HasHeaderValue("status", "200")); + EXPECT_TRUE(response2.headers->HasHeaderValue("version", "HTTP/1.1")); +} + +TEST_P(SpdyNetworkTransactionTest, SynReplyWithHeaders) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + static const char* const kInitialHeaders[] = { + "status", + "200 OK", + "version", + "HTTP/1.1" + }; + static const char* const kLateHeaders[] = { + "hello", + "bye", + }; + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyControlFrame(kInitialHeaders, + arraysize(kInitialHeaders) / 2, + false, + 1, + LOWEST, + spdy::SYN_REPLY, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 0)); + scoped_ptr<spdy::SpdyFrame> + stream1_headers(ConstructSpdyControlFrame(kLateHeaders, + arraysize(kLateHeaders) / 2, + false, + 1, + LOWEST, + spdy::HEADERS, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 0)); + scoped_ptr<spdy::SpdyFrame> stream1_body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*stream1_reply), + CreateMockRead(*stream1_headers), + CreateMockRead(*stream1_body), + MockRead(true, 0, 0) // EOF + }; + + scoped_refptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); +} + +TEST_P(SpdyNetworkTransactionTest, SynReplyWithLateHeaders) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + static const char* const kInitialHeaders[] = { + "status", + "200 OK", + "version", + "HTTP/1.1" + }; + static const char* const kLateHeaders[] = { + "hello", + "bye", + }; + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyControlFrame(kInitialHeaders, + arraysize(kInitialHeaders) / 2, + false, + 1, + LOWEST, + spdy::SYN_REPLY, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 0)); + scoped_ptr<spdy::SpdyFrame> + stream1_headers(ConstructSpdyControlFrame(kLateHeaders, + arraysize(kLateHeaders) / 2, + false, + 1, + LOWEST, + spdy::HEADERS, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 0)); + scoped_ptr<spdy::SpdyFrame> stream1_body(ConstructSpdyBodyFrame(1, false)); + scoped_ptr<spdy::SpdyFrame> stream1_body2(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*stream1_reply), + CreateMockRead(*stream1_body), + CreateMockRead(*stream1_headers), + CreateMockRead(*stream1_body2), + MockRead(true, 0, 0) // EOF + }; + + scoped_refptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); +} + +TEST_P(SpdyNetworkTransactionTest, SynReplyWithDuplicateLateHeaders) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + static const char* const kInitialHeaders[] = { + "status", + "200 OK", + "version", + "HTTP/1.1" + }; + static const char* const kLateHeaders[] = { + "status", + "500 Server Error", + }; + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyControlFrame(kInitialHeaders, + arraysize(kInitialHeaders) / 2, + false, + 1, + LOWEST, + spdy::SYN_REPLY, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 0)); + scoped_ptr<spdy::SpdyFrame> + stream1_headers(ConstructSpdyControlFrame(kLateHeaders, + arraysize(kLateHeaders) / 2, + false, + 1, + LOWEST, + spdy::HEADERS, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 0)); + scoped_ptr<spdy::SpdyFrame> stream1_body(ConstructSpdyBodyFrame(1, false)); + scoped_ptr<spdy::SpdyFrame> stream1_body2(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*stream1_reply), + CreateMockRead(*stream1_body), + CreateMockRead(*stream1_headers), + CreateMockRead(*stream1_body2), + MockRead(true, 0, 0) // EOF + }; + + scoped_refptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv); +} + } // namespace net diff --git a/net/spdy/spdy_protocol.h b/net/spdy/spdy_protocol.h index ce074c4d..9834a11 100644 --- a/net/spdy/spdy_protocol.h +++ b/net/spdy/spdy_protocol.h @@ -14,6 +14,8 @@ #include <arpa/inet.h> #endif +#include <limits> + #include "base/basictypes.h" #include "base/logging.h" #include "net/spdy/spdy_bitmasks.h" @@ -40,7 +42,7 @@ // +----------------------------------+ // |1|000000000000001|0000000000000001| // +----------------------------------+ -// | flags (8) | Length (24 bits) | >= 8 +// | flags (8) | Length (24 bits) | >= 12 // +----------------------------------+ // |X| Stream-ID(31bits) | // +----------------------------------+ @@ -105,6 +107,17 @@ // |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| @@ -115,15 +128,26 @@ // +----------------------------------+ // | Delta-Window-Size (32 bits) | // +----------------------------------+ - - namespace spdy { -// The SPDY version of this implementation. +// This implementation of Spdy is version 2; It's like version 1, with some +// minor tweaks. const int kSpdyProtocolVersion = 2; -// Default initial window size. -const int kInitialWindowSize = 64 * 1024; +// Initial window size for a Spdy stream +const size_t kSpdyStreamInitialWindowSize = 64 * 1024; // 64 KBytes + +// Maximum window size for a Spdy stream +const size_t kSpdyStreamMaximumWindowSize = std::numeric_limits<int32>::max(); + +// HTTP-over-SPDY header constants +const char kMethod[] = "method"; +const char kStatus[] = "status"; +const char kUrl[] = "url"; +const char kVersion[] = "version"; +// When we server push, we will add [path: fully/qualified/url] to the server +// push headers so that the client will know what url the data corresponds to. +const char kPath[] = "path"; // Note: all protocol data structures are on-the-wire format. That means that // data is stored in network-normalized order. Readers must use the @@ -176,7 +200,9 @@ enum SpdySettingsIds { SETTINGS_MAX_CONCURRENT_STREAMS = 0x4, SETTINGS_CURRENT_CWND = 0x5, // Downstream byte retransmission rate in percentage. - SETTINGS_DOWNLOAD_RETRANS_RATE = 0x6 + SETTINGS_DOWNLOAD_RETRANS_RATE = 0x6, + // Initial window size in bytes + SETTINGS_INITIAL_WINDOW_SIZE = 0x7 }; // Status codes, as used in control frames (primarily RST_STREAM). @@ -196,7 +222,7 @@ enum SpdyStatusCodes { // A SPDY stream id is a 31 bit entity. typedef uint32 SpdyStreamId; -// A SPDY priority is a number between 0 and 4. +// A SPDY priority is a number between 0 and 3 (inclusive). typedef uint8 SpdyPriority; // SPDY Priorities. (there are only 2 bits) @@ -251,20 +277,38 @@ struct SpdyRstStreamControlFrameBlock : SpdyFrameBlock { uint32 status_; }; +// A SETTINGS Control Frame structure. +struct SpdySettingsControlFrameBlock : SpdyFrameBlock { + uint32 num_entries_; + // Variable data here. +}; + // A GOAWAY Control Frame structure. struct SpdyGoAwayControlFrameBlock : SpdyFrameBlock { SpdyStreamId last_accepted_stream_id_; }; +// A HEADERS Control Frame structure. +struct SpdyHeadersControlFrameBlock : SpdyFrameBlock { + SpdyStreamId stream_id_; + uint16 unused_; +}; + +// A WINDOW_UPDATE Control Frame structure +struct SpdyWindowUpdateControlFrameBlock : SpdyFrameBlock { + SpdyStreamId stream_id_; + uint32 delta_window_size_; +}; + // A structure for the 8 bit flags and 24 bit ID fields. union SettingsFlagsAndId { uint8 flags_[4]; // 8 bits uint32 id_; // 24 bits - SettingsFlagsAndId(uint32 val) : id_(val) {}; + SettingsFlagsAndId(uint32 val) : id_(val) {} uint8 flags() const { return flags_[0]; } void set_flags(uint8 flags) { flags_[0] = flags; } - uint32 id() const { return (ntohl(id_) & kSettingsIdMask); }; + uint32 id() const { return (ntohl(id_) & kSettingsIdMask); } void set_id(uint32 id) { DCHECK_EQ(0u, (id & ~kSettingsIdMask)); id = htonl(id & kSettingsIdMask); @@ -272,18 +316,6 @@ union SettingsFlagsAndId { } }; -// A SETTINGS Control Frame structure. -struct SpdySettingsControlFrameBlock : SpdyFrameBlock { - uint32 num_entries_; - // Variable data here. -}; - -// A WINDOW_UPDATE Control Frame structure -struct SpdyWindowUpdateControlFrameBlock : SpdyFrameBlock { - SpdyStreamId stream_id_; - uint32 delta_window_size_; -}; - #pragma pack(pop) // ------------------------------------------------------------------------- @@ -561,6 +593,42 @@ class SpdyRstStreamControlFrame : public SpdyControlFrame { DISALLOW_COPY_AND_ASSIGN(SpdyRstStreamControlFrame); }; +class SpdySettingsControlFrame : public SpdyControlFrame { + public: + SpdySettingsControlFrame() : SpdyControlFrame(size()) {} + SpdySettingsControlFrame(char* data, bool owns_buffer) + : SpdyControlFrame(data, owns_buffer) {} + + uint32 num_entries() const { + return ntohl(block()->num_entries_); + } + + void set_num_entries(int val) { + mutable_block()->num_entries_ = htonl(val); + } + + int header_block_len() const { + return length() - (size() - SpdyFrame::size()); + } + + const char* header_block() const { + return reinterpret_cast<const char*>(block()) + size(); + } + + // Returns the size of the SpdySettingsControlFrameBlock structure. + // Note: this is not the size of the SpdySettingsControlFrameBlock class. + static size_t size() { return sizeof(SpdySettingsControlFrameBlock); } + + private: + const struct SpdySettingsControlFrameBlock* block() const { + return static_cast<SpdySettingsControlFrameBlock*>(frame_); + } + struct SpdySettingsControlFrameBlock* mutable_block() { + return static_cast<SpdySettingsControlFrameBlock*>(frame_); + } + DISALLOW_COPY_AND_ASSIGN(SpdySettingsControlFrame); +}; + class SpdyGoAwayControlFrame : public SpdyControlFrame { public: SpdyGoAwayControlFrame() : SpdyControlFrame(size()) {} @@ -587,20 +655,22 @@ class SpdyGoAwayControlFrame : public SpdyControlFrame { DISALLOW_COPY_AND_ASSIGN(SpdyGoAwayControlFrame); }; -class SpdySettingsControlFrame : public SpdyControlFrame { +// A HEADERS frame. +class SpdyHeadersControlFrame : public SpdyControlFrame { public: - SpdySettingsControlFrame() : SpdyControlFrame(size()) {} - SpdySettingsControlFrame(char* data, bool owns_buffer) + SpdyHeadersControlFrame() : SpdyControlFrame(size()) {} + SpdyHeadersControlFrame(char* data, bool owns_buffer) : SpdyControlFrame(data, owns_buffer) {} - uint32 num_entries() const { - return ntohl(block()->num_entries_); + SpdyStreamId stream_id() const { + return ntohl(block()->stream_id_) & kStreamIdMask; } - void set_num_entries(int val) { - mutable_block()->num_entries_ = htonl(val); + void set_stream_id(SpdyStreamId id) { + mutable_block()->stream_id_ = htonl(id & kStreamIdMask); } + // The number of bytes in the header block beyond the frame header length. int header_block_len() const { return length() - (size() - SpdyFrame::size()); } @@ -609,18 +679,18 @@ class SpdySettingsControlFrame : public SpdyControlFrame { return reinterpret_cast<const char*>(block()) + size(); } - // Returns the size of the SpdySettingsControlFrameBlock structure. - // Note: this is not the size of the SpdySettingsControlFrameBlock class. - static size_t size() { return sizeof(SpdySettingsControlFrameBlock); } + // Returns the size of the SpdyHeadersControlFrameBlock structure. + // Note: this is not the size of the SpdyHeadersControlFrame class. + static size_t size() { return sizeof(SpdyHeadersControlFrameBlock); } private: - const struct SpdySettingsControlFrameBlock* block() const { - return static_cast<SpdySettingsControlFrameBlock*>(frame_); + const struct SpdyHeadersControlFrameBlock* block() const { + return static_cast<SpdyHeadersControlFrameBlock*>(frame_); } - struct SpdySettingsControlFrameBlock* mutable_block() { - return static_cast<SpdySettingsControlFrameBlock*>(frame_); + struct SpdyHeadersControlFrameBlock* mutable_block() { + return static_cast<SpdyHeadersControlFrameBlock*>(frame_); } - DISALLOW_COPY_AND_ASSIGN(SpdySettingsControlFrame); + DISALLOW_COPY_AND_ASSIGN(SpdyHeadersControlFrame); }; // A WINDOW_UPDATE frame. diff --git a/net/spdy/spdy_proxy_client_socket.cc b/net/spdy/spdy_proxy_client_socket.cc index 8066007..1cddb5e 100644 --- a/net/spdy/spdy_proxy_client_socket.cc +++ b/net/spdy/spdy_proxy_client_socket.cc @@ -390,13 +390,18 @@ int SpdyProxyClientSocket::OnResponseReceived( const spdy::SpdyHeaderBlock& response, base::Time response_time, int status) { - // Save the response - SpdyHeadersToHttpResponse(response, &response_); + // If we've already received the reply, existing headers are too late. + // TODO(mbelshe): figure out a way to make HEADERS frames useful after the + // initial response. + if (next_state_ != STATE_READ_REPLY_COMPLETE) + return OK; - DCHECK_EQ(next_state_, STATE_READ_REPLY_COMPLETE); + // Save the response + int rv = SpdyHeadersToHttpResponse(response, &response_); + if (rv == ERR_INCOMPLETE_SPDY_HEADERS) + return rv; // More headers are coming. OnIOComplete(status); - return OK; } diff --git a/net/spdy/spdy_proxy_client_socket.h b/net/spdy/spdy_proxy_client_socket.h index 4a0747e..5f2dd6f 100644 --- a/net/spdy/spdy_proxy_client_socket.h +++ b/net/spdy/spdy_proxy_client_socket.h @@ -62,7 +62,6 @@ class SpdyProxyClientSocket : public ClientSocket, public SpdyStream::Delegate { } // ClientSocket methods: - virtual int Connect(CompletionCallback* callback); virtual void Disconnect(); virtual bool IsConnected() const; @@ -74,43 +73,21 @@ class SpdyProxyClientSocket : public ClientSocket, public SpdyStream::Delegate { virtual bool UsingTCPFastOpen() const; // Socket methods: - virtual int Read(IOBuffer* buf, int buf_len, CompletionCallback* callback); virtual int Write(IOBuffer* buf, int buf_len, CompletionCallback* callback); - virtual bool SetReceiveBufferSize(int32 size); virtual bool SetSendBufferSize(int32 size); - virtual int GetPeerAddress(AddressList* address) const; // SpdyStream::Delegate methods: - - // Called when SYN frame has been sent. - // Returns true if no more data to be sent after SYN frame. virtual bool OnSendHeadersComplete(int status); - - // Called when stream is ready to send data. - // Returns network error code. OK when it successfully sent data. virtual int OnSendBody(); - - // Called when data has been sent. |status| indicates network error - // or number of bytes has been sent. - // Returns true if no more data to be sent. virtual bool OnSendBodyComplete(int status); - - // Called when SYN_STREAM or SYN_REPLY received. |status| indicates network - // error. Returns network error code. virtual int OnResponseReceived(const spdy::SpdyHeaderBlock& response, base::Time response_time, int status); - - // Called when data is received. virtual void OnDataReceived(const char* data, int length); - - // Called when data is sent. virtual void OnDataSent(int length); - - // Called when SpdyStream is closed. virtual void OnClose(int status); private: diff --git a/net/spdy/spdy_session.cc b/net/spdy/spdy_session.cc index 2c79c45..a134b98 100644 --- a/net/spdy/spdy_session.cc +++ b/net/spdy/spdy_session.cc @@ -31,8 +31,12 @@ namespace net { NetLogSpdySynParameter::NetLogSpdySynParameter( const linked_ptr<spdy::SpdyHeaderBlock>& headers, spdy::SpdyControlFlags flags, - spdy::SpdyStreamId id) - : headers_(headers), flags_(flags), id_(id) { + spdy::SpdyStreamId id, + spdy::SpdyStreamId associated_stream) + : headers_(headers), + flags_(flags), + id_(id), + associated_stream_(associated_stream) { } NetLogSpdySynParameter::~NetLogSpdySynParameter() { @@ -49,6 +53,8 @@ Value* NetLogSpdySynParameter::ToValue() const { dict->SetInteger("flags", flags_); dict->Set("headers", headers_list); dict->SetInteger("id", id_); + if (associated_stream_) + dict->SetInteger("associated_stream", associated_stream_); return dict; } @@ -242,8 +248,8 @@ SpdySession::SpdySession(const HostPortProxyPair& host_port_proxy_pair, frames_received_(0), sent_settings_(false), received_settings_(false), - initial_send_window_size_(spdy::kInitialWindowSize), - initial_recv_window_size_(spdy::kInitialWindowSize), + initial_send_window_size_(spdy::kSpdyStreamInitialWindowSize), + initial_recv_window_size_(spdy::kSpdyStreamInitialWindowSize), net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_SPDY_SESSION)) { DCHECK(HttpStreamFactory::spdy_enabled()); net_log_.BeginEvent( @@ -320,9 +326,7 @@ int SpdySession::GetPushStream( return ERR_SPDY_PROTOCOL_ERROR; } - const std::string& path = url.PathForRequest(); - - *stream = GetActivePushStream(path); + *stream = GetActivePushStream(url.spec()); if (stream->get()) { DCHECK(streams_pushed_and_claimed_count_ < streams_pushed_count_); streams_pushed_and_claimed_count_++; @@ -470,7 +474,7 @@ int SpdySession::WriteSynStream( net_log().AddEvent( NetLog::TYPE_SPDY_SESSION_SYN_STREAM, make_scoped_refptr( - new NetLogSpdySynParameter(headers, flags, stream_id))); + new NetLogSpdySynParameter(headers, flags, stream_id, 0))); } return ERR_IO_PENDING; @@ -995,7 +999,6 @@ void SpdySession::OnStreamFrameData(spdy::SpdyStreamId stream_id, bool SpdySession::Respond(const spdy::SpdyHeaderBlock& headers, const scoped_refptr<SpdyStream> stream) { int rv = OK; - rv = stream->OnResponseReceived(headers); if (rv < 0) { DCHECK_NE(rv, ERR_IO_PENDING); @@ -1016,7 +1019,7 @@ void SpdySession::OnSyn(const spdy::SpdySynStreamControlFrame& frame, NetLog::TYPE_SPDY_SESSION_PUSHED_SYN_STREAM, make_scoped_refptr(new NetLogSpdySynParameter( headers, static_cast<spdy::SpdyControlFlags>(frame.flags()), - stream_id))); + stream_id, associated_stream_id))); } // Server-initiated streams should have even sequence numbers. @@ -1042,13 +1045,20 @@ void SpdySession::OnSyn(const spdy::SpdySynStreamControlFrame& frame, // TODO(mbelshe): DCHECK that this is a GET method? - const std::string& path = ContainsKey(*headers, "path") ? - headers->find("path")->second : ""; + const std::string& url = ContainsKey(*headers, "url") ? + headers->find("url")->second : ""; // Verify that the response had a URL for us. - if (path.empty()) { + if (url.empty()) { + ResetStream(stream_id, spdy::PROTOCOL_ERROR); + LOG(WARNING) << "Pushed stream did not contain a url."; + return; + } + + GURL gurl(url); + if (!gurl.is_valid()) { ResetStream(stream_id, spdy::PROTOCOL_ERROR); - LOG(WARNING) << "Pushed stream did not contain a path."; + LOG(WARNING) << "Pushed stream url was invalid: " << url; return; } @@ -1062,9 +1072,9 @@ void SpdySession::OnSyn(const spdy::SpdySynStreamControlFrame& frame, // TODO(erikchen): Actually do something with the associated id. // There should not be an existing pushed stream with the same path. - PushedStreamMap::iterator it = unclaimed_pushed_streams_.find(path); + PushedStreamMap::iterator it = unclaimed_pushed_streams_.find(url); if (it != unclaimed_pushed_streams_.end()) { - LOG(ERROR) << "Received duplicate pushed stream with path: " << path; + LOG(ERROR) << "Received duplicate pushed stream with url: " << url; ResetStream(stream_id, spdy::PROTOCOL_ERROR); return; } @@ -1072,9 +1082,9 @@ void SpdySession::OnSyn(const spdy::SpdySynStreamControlFrame& frame, scoped_refptr<SpdyStream> stream( new SpdyStream(this, stream_id, true, net_log_)); - stream->set_path(path); + stream->set_path(gurl.PathForRequest()); - unclaimed_pushed_streams_[path] = stream; + unclaimed_pushed_streams_[url] = stream; ActivateStream(stream); stream->set_response_received(); @@ -1114,25 +1124,62 @@ void SpdySession::OnSynReply(const spdy::SpdySynReplyControlFrame& frame, NetLog::TYPE_SPDY_SESSION_SYN_REPLY, make_scoped_refptr(new NetLogSpdySynParameter( headers, static_cast<spdy::SpdyControlFlags>(frame.flags()), - stream_id))); + stream_id, 0))); } Respond(*headers, stream); } +void SpdySession::OnHeaders(const spdy::SpdyHeadersControlFrame& frame, + const linked_ptr<spdy::SpdyHeaderBlock>& headers) { + spdy::SpdyStreamId stream_id = frame.stream_id(); + + bool valid_stream = IsStreamActive(stream_id); + if (!valid_stream) { + // NOTE: it may just be that the stream was cancelled. + LOG(WARNING) << "Received HEADERS for invalid stream " << stream_id; + return; + } + + scoped_refptr<SpdyStream> stream = active_streams_[stream_id]; + CHECK_EQ(stream->stream_id(), stream_id); + CHECK(!stream->cancelled()); + + if (net_log().IsLoggingAllEvents()) { + net_log().AddEvent( + NetLog::TYPE_SPDY_SESSION_HEADERS, + make_scoped_refptr(new NetLogSpdySynParameter( + headers, static_cast<spdy::SpdyControlFlags>(frame.flags()), + stream_id, 0))); + } + + int rv = stream->OnHeaders(*headers); + if (rv < 0) { + DCHECK_NE(rv, ERR_IO_PENDING); + const spdy::SpdyStreamId stream_id = stream->stream_id(); + DeleteStream(stream_id, rv); + } +} + void SpdySession::OnControl(const spdy::SpdyControlFrame* frame) { const linked_ptr<spdy::SpdyHeaderBlock> headers(new spdy::SpdyHeaderBlock); uint32 type = frame->type(); - if (type == spdy::SYN_STREAM || type == spdy::SYN_REPLY) { + if (type == spdy::SYN_STREAM || + type == spdy::SYN_REPLY || + type == spdy::HEADERS) { if (!spdy_framer_.ParseHeaderBlock(frame, headers.get())) { LOG(WARNING) << "Could not parse Spdy Control Frame Header."; int stream_id = 0; - if (type == spdy::SYN_STREAM) + if (type == spdy::SYN_STREAM) { stream_id = (reinterpret_cast<const spdy::SpdySynStreamControlFrame*> (frame))->stream_id(); - if (type == spdy::SYN_REPLY) + } else if (type == spdy::SYN_REPLY) { stream_id = (reinterpret_cast<const spdy::SpdySynReplyControlFrame*> (frame))->stream_id(); + } else if (type == spdy::HEADERS) { + stream_id = (reinterpret_cast<const spdy::SpdyHeadersControlFrame*> + (frame))->stream_id(); + } if(IsStreamActive(stream_id)) ResetStream(stream_id, spdy::PROTOCOL_ERROR); return; @@ -1156,6 +1203,10 @@ void SpdySession::OnControl(const spdy::SpdyControlFrame* frame) { OnSyn(*reinterpret_cast<const spdy::SpdySynStreamControlFrame*>(frame), headers); break; + case spdy::HEADERS: + OnHeaders(*reinterpret_cast<const spdy::SpdyHeadersControlFrame*>(frame), + headers); + break; case spdy::SYN_REPLY: OnSynReply( *reinterpret_cast<const spdy::SpdySynReplyControlFrame*>(frame), diff --git a/net/spdy/spdy_session.h b/net/spdy/spdy_session.h index 7007d28..210e9af 100644 --- a/net/spdy/spdy_session.h +++ b/net/spdy/spdy_session.h @@ -263,6 +263,8 @@ class SpdySession : public base::RefCounted<SpdySession>, const linked_ptr<spdy::SpdyHeaderBlock>& headers); void OnSynReply(const spdy::SpdySynReplyControlFrame& frame, const linked_ptr<spdy::SpdyHeaderBlock>& headers); + void OnHeaders(const spdy::SpdyHeadersControlFrame& frame, + const linked_ptr<spdy::SpdyHeaderBlock>& headers); void OnRst(const spdy::SpdyRstStreamControlFrame& frame); void OnGoAway(const spdy::SpdyGoAwayControlFrame& frame); void OnSettings(const spdy::SpdySettingsControlFrame& frame); @@ -432,7 +434,8 @@ class NetLogSpdySynParameter : public NetLog::EventParameters { public: NetLogSpdySynParameter(const linked_ptr<spdy::SpdyHeaderBlock>& headers, spdy::SpdyControlFlags flags, - spdy::SpdyStreamId id); + spdy::SpdyStreamId id, + spdy::SpdyStreamId associated_stream); virtual Value* ToValue() const; @@ -446,6 +449,7 @@ class NetLogSpdySynParameter : public NetLog::EventParameters { const linked_ptr<spdy::SpdyHeaderBlock> headers_; const spdy::SpdyControlFlags flags_; const spdy::SpdyStreamId id_; + const spdy::SpdyStreamId associated_stream_; DISALLOW_COPY_AND_ASSIGN(NetLogSpdySynParameter); }; diff --git a/net/spdy/spdy_stream.cc b/net/spdy/spdy_stream.cc index f26dd6d..44d1243 100644 --- a/net/spdy/spdy_stream.cc +++ b/net/spdy/spdy_stream.cc @@ -44,8 +44,8 @@ SpdyStream::SpdyStream(SpdySession* session, stream_id_(stream_id), priority_(0), stalled_by_flow_control_(false), - send_window_size_(spdy::kInitialWindowSize), - recv_window_size_(spdy::kInitialWindowSize), + send_window_size_(spdy::kSpdyStreamInitialWindowSize), + recv_window_size_(spdy::kSpdyStreamInitialWindowSize), pushed_(pushed), metrics_(Singleton<BandwidthMetrics>::get()), response_received_(false), @@ -60,14 +60,10 @@ SpdyStream::SpdyStream(SpdySession* session, net_log_(net_log), send_bytes_(0), recv_bytes_(0) { - net_log_.BeginEvent( - NetLog::TYPE_SPDY_STREAM, - make_scoped_refptr(new NetLogIntegerParameter("stream_id", stream_id_))); } SpdyStream::~SpdyStream() { UpdateHistograms(); - net_log_.EndEvent(NetLog::TYPE_SPDY_STREAM, NULL); } void SpdyStream::SetDelegate(Delegate* delegate) { @@ -88,9 +84,17 @@ void SpdyStream::PushedStreamReplayData() { if (cancelled_ || !delegate_) return; - delegate_->OnResponseReceived(*response_, response_time_, OK); - continue_buffering_data_ = false; + + int rv = delegate_->OnResponseReceived(*response_, response_time_, OK); + if (rv == ERR_INCOMPLETE_SPDY_HEADERS) { + // We don't have complete headers. Assume we're waiting for another + // HEADERS frame. Since we don't have headers, we had better not have + // any pending data frames. + DCHECK_EQ(0U, pending_buffers_.size()); + return; + } + std::vector<scoped_refptr<IOBufferWithSize> > buffers; buffers.swap(pending_buffers_); for (size_t i = 0; i < buffers.size(); ++i) { @@ -253,9 +257,44 @@ int SpdyStream::OnResponseReceived(const spdy::SpdyHeaderBlock& response) { return rv; } +int SpdyStream::OnHeaders(const spdy::SpdyHeaderBlock& headers) { + DCHECK(!response_->empty()); + + // Append all the headers into the response header block. + for (spdy::SpdyHeaderBlock::const_iterator it = headers.begin(); + it != headers.end(); ++it) { + // Disallow duplicate headers. This is just to be conservative. + if ((*response_).find(it->first) != (*response_).end()) { + LOG(WARNING) << "HEADERS duplicate header"; + response_status_ = ERR_SPDY_PROTOCOL_ERROR; + return ERR_SPDY_PROTOCOL_ERROR; + } + + (*response_)[it->first] = it->second; + } + + int rv = OK; + if (delegate_) { + rv = delegate_->OnResponseReceived(*response_, response_time_, rv); + // ERR_INCOMPLETE_SPDY_HEADERS means that we are waiting for more + // headers before the response header block is complete. + if (rv == ERR_INCOMPLETE_SPDY_HEADERS) + rv = OK; + } + return rv; +} + void SpdyStream::OnDataReceived(const char* data, int length) { DCHECK_GE(length, 0); + // If we don't have a response, then the SYN_REPLY did not come through. + // We cannot pass data up to the caller unless the reply headers have been + // received. + if (!response_received()) { + session_->CloseStream(stream_id_, ERR_SYN_REPLY_NOT_RECEIVED); + return; + } + if (!delegate_ || continue_buffering_data_) { // It should be valid for this to happen in the server push case. // We'll return received data when delegate gets attached to the stream. @@ -272,15 +311,7 @@ void SpdyStream::OnDataReceived(const char* data, int length) { return; } - CHECK(!closed()); - - // If we don't have a response, then the SYN_REPLY did not come through. - // We cannot pass data up to the caller unless the reply headers have been - // received. - if (!response_received()) { - session_->CloseStream(stream_id_, ERR_SYN_REPLY_NOT_RECEIVED); - return; - } + CHECK(!closed()); // A zero-length read means that the stream is being closed. if (!length) { diff --git a/net/spdy/spdy_stream.h b/net/spdy/spdy_stream.h index 4e30a60..d3dd242 100644 --- a/net/spdy/spdy_stream.h +++ b/net/spdy/spdy_stream.h @@ -53,8 +53,12 @@ class SpdyStream : public base::RefCounted<SpdyStream> { // Returns true if no more data to be sent. virtual bool OnSendBodyComplete(int status) = 0; - // Called when SYN_STREAM or SYN_REPLY received. |status| indicates network - // error. Returns network error code. + // Called when the SYN_STREAM, SYN_REPLY, or HEADERS frames are received. + // Normal streams will receive a SYN_REPLY and optional HEADERS frames. + // Pushed streams will receive a SYN_STREAM and optional HEADERS frames. + // Because a stream may have a SYN_* frame and multiple HEADERS frames, + // this callback may be called multiple times. + // |status| indicates network error. Returns network error code. virtual int OnResponseReceived(const spdy::SpdyHeaderBlock& response, base::Time response_time, int status) = 0; @@ -157,6 +161,10 @@ class SpdyStream : public base::RefCounted<SpdyStream> { // has been received for this stream. Returns a status code. int OnResponseReceived(const spdy::SpdyHeaderBlock& response); + // Called by the SpdySession when late-bound headers are received for a + // stream. Returns a status code. + int OnHeaders(const spdy::SpdyHeaderBlock& headers); + // Called by the SpdySession when response data has been received for this // stream. This callback may be called multiple times as data arrives // from the network, and will never be called prior to OnResponseReceived. diff --git a/net/spdy/spdy_test_util.cc b/net/spdy/spdy_test_util.cc index 896cd9d..5d6c9b0 100644 --- a/net/spdy/spdy_test_util.cc +++ b/net/spdy/spdy_test_util.cc @@ -171,6 +171,10 @@ spdy::SpdyFrame* ConstructSpdyPacket(const SpdyHeaderInfo& header_info, case spdy::RST_STREAM: frame = framer.CreateRstStream(header_info.id, header_info.status); break; + case spdy::HEADERS: + frame = framer.CreateHeaders(header_info.id, header_info.control_flags, + header_info.compressed, &headers); + break; default: frame = framer.CreateDataFrame(header_info.id, header_info.data, header_info.data_length, @@ -460,16 +464,14 @@ spdy::SpdyFrame* ConstructSpdyPush(const char* const extra_headers[], int extra_header_count, int stream_id, int associated_stream_id, - const char* path) { + const char* url) { const char* const kStandardGetHeaders[] = { "hello", "bye", - "path", - path, "status", "200 OK", "url", - path, + url, "version", "HTTP/1.1" }; @@ -489,15 +491,12 @@ spdy::SpdyFrame* ConstructSpdyPush(const char* const extra_headers[], int extra_header_count, int stream_id, int associated_stream_id, - const char* path, + const char* url, const char* status, - const char* location, - const char* url) { + const char* location) { const char* const kStandardGetHeaders[] = { "hello", "bye", - "path", - path, "status", status, "location", @@ -519,6 +518,45 @@ spdy::SpdyFrame* ConstructSpdyPush(const char* const extra_headers[], associated_stream_id); } +spdy::SpdyFrame* ConstructSpdyPush(int stream_id, + int associated_stream_id, + const char* url) { + const char* const kStandardGetHeaders[] = { + "url", + url + }; + return ConstructSpdyControlFrame(0, + 0, + false, + stream_id, + LOWEST, + spdy::SYN_STREAM, + spdy::CONTROL_FLAG_NONE, + kStandardGetHeaders, + arraysize(kStandardGetHeaders), + associated_stream_id); +} + +spdy::SpdyFrame* ConstructSpdyPushHeaders(int stream_id, + const char* const extra_headers[], + int extra_header_count) { + const char* const kStandardGetHeaders[] = { + "status", + "200 OK", + "version", + "HTTP/1.1" + }; + return ConstructSpdyControlFrame(extra_headers, + extra_header_count, + false, + stream_id, + LOWEST, + spdy::HEADERS, + spdy::CONTROL_FLAG_NONE, + kStandardGetHeaders, + arraysize(kStandardGetHeaders)); +} + // Constructs a standard SPDY SYN_REPLY packet with the specified status code. // Returns a SpdyFrame. spdy::SpdyFrame* ConstructSpdySynReplyError( diff --git a/net/spdy/spdy_test_util.h b/net/spdy/spdy_test_util.h index 0a81bb7..0a5d2e0 100644 --- a/net/spdy/spdy_test_util.h +++ b/net/spdy/spdy_test_util.h @@ -229,16 +229,22 @@ spdy::SpdyFrame* ConstructSpdyPush(const char* const extra_headers[], int extra_header_count, int stream_id, int associated_stream_id, - const char* path); + const char* url); spdy::SpdyFrame* ConstructSpdyPush(const char* const extra_headers[], int extra_header_count, int stream_id, int associated_stream_id, - const char* path, + const char* url, const char* status, - const char* location, + const char* location); +spdy::SpdyFrame* ConstructSpdyPush(int stream_id, + int associated_stream_id, const char* url); +spdy::SpdyFrame* ConstructSpdyPushHeaders(int stream_id, + const char* const extra_headers[], + int extra_header_count); + // Constructs a standard SPDY SYN_REPLY packet to match the SPDY GET. // |extra_headers| are the extra header-value pairs, which typically // will vary the most between calls. |