summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormbelshe@chromium.org <mbelshe@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-12-15 20:05:25 +0000
committermbelshe@chromium.org <mbelshe@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-12-15 20:05:25 +0000
commitb3df487b910d11d59add9f678cceb75487d0be29 (patch)
tree65b22889125bdac211aaa47246c5c096b3d1133f
parent196e08b72ef068daa076a0329eb5f5093e64b0dc (diff)
downloadchromium_src-b3df487b910d11d59add9f678cceb75487d0be29.zip
chromium_src-b3df487b910d11d59add9f678cceb75487d0be29.tar.gz
chromium_src-b3df487b910d11d59add9f678cceb75487d0be29.tar.bz2
Merge r68605, 68221:
Add origin checking for server pushed resources. BUG=64108 TEST=PushedStream, ServerPushCrossOriginCorrectness Review URL: http://codereview.chromium.org/5516012 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 Review URL: http://codereview.chromium.org/5734005 git-svn-id: svn://svn.chromium.org/chrome/branches/552/src@69304 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--net/base/net_error_list.h4
-rw-r--r--net/base/net_log_event_type_list.h10
-rw-r--r--net/socket/socket_test_util.h10
-rw-r--r--net/spdy/spdy_framer.cc195
-rw-r--r--net/spdy/spdy_framer.h61
-rw-r--r--net/spdy/spdy_framer_test.cc208
-rw-r--r--net/spdy/spdy_http_stream.cc33
-rw-r--r--net/spdy/spdy_http_stream.h64
-rw-r--r--net/spdy/spdy_http_utils.cc8
-rw-r--r--net/spdy/spdy_network_transaction_unittest.cc850
-rw-r--r--net/spdy/spdy_protocol.h144
-rw-r--r--net/spdy/spdy_proxy_client_socket.cc13
-rw-r--r--net/spdy/spdy_proxy_client_socket.h23
-rw-r--r--net/spdy/spdy_session.cc170
-rw-r--r--net/spdy/spdy_session.h26
-rw-r--r--net/spdy/spdy_stream.cc101
-rw-r--r--net/spdy/spdy_stream.h20
-rw-r--r--net/spdy/spdy_stream_unittest.cc54
-rw-r--r--net/spdy/spdy_test_util.cc65
-rw-r--r--net/spdy/spdy_test_util.h12
20 files changed, 1693 insertions, 378 deletions
diff --git a/net/base/net_error_list.h b/net/base/net_error_list.h
index 475dc2f..9b5ed46 100644
--- a/net/base/net_error_list.h
+++ b/net/base/net_error_list.h
@@ -404,6 +404,10 @@ NET_ERROR(UNDOCUMENTED_SECURITY_LIBRARY_STATUS, -344)
// The HTTP response was too big to drain.
NET_ERROR(RESPONSE_BODY_TOO_BIG_TO_DRAIN, -345)
+// 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 9c3264f..2135a47 100644
--- a/net/base/net_log_event_type_list.h
+++ b/net/base/net_log_event_type_list.h
@@ -566,9 +566,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 7293c05..8622783 100644
--- a/net/socket/socket_test_util.h
+++ b/net/socket/socket_test_util.h
@@ -174,6 +174,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;
@@ -285,7 +286,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:
@@ -328,6 +329,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;
}
@@ -335,8 +338,6 @@ class OrderedSocketData : public StaticSocketDataProvider,
// Posts a quit message to the current message loop, if one is running.
void EndLoop();
- void CompleteRead();
-
private:
int sequence_number_;
int loop_stop_stage_;
@@ -423,6 +424,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();
@@ -440,7 +443,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 50682a8..92442df 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/scoped_ptr.h"
@@ -18,17 +22,19 @@
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) \
{ \
@@ -291,6 +297,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);
@@ -300,14 +311,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:
@@ -437,7 +448,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.
@@ -463,14 +474,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))
@@ -553,55 +571,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 */
@@ -623,15 +646,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
@@ -654,12 +700,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,
@@ -694,15 +760,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.
@@ -846,6 +903,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!
@@ -987,8 +1054,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 00ddac0..6696851 100644
--- a/net/spdy/spdy_framer.h
+++ b/net/spdy/spdy_framer.h
@@ -156,9 +156,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
@@ -166,32 +184,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.
@@ -202,8 +213,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
@@ -248,7 +257,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::SpdyHttpStreamTest;
@@ -264,6 +277,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 aa86604..21802c3 100644
--- a/net/spdy/spdy_http_stream.cc
+++ b/net/spdy/spdy_http_stream.cc
@@ -26,6 +26,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),
@@ -240,29 +241,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 a8e1a2c..c4c3dc6 100644
--- a/net/spdy/spdy_http_stream.h
+++ b/net/spdy/spdy_http_stream.h
@@ -37,99 +37,52 @@ 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 std::string& 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);
-
- // Indicates if the response body has been completely read.
+ virtual HttpStream* RenewStreamForAuth() { return NULL; }
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 ClientSocketHandle* DetachConnection();
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 +114,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 b2ef551..d694859 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 e9076d3..7544fff 100644
--- a/net/spdy/spdy_network_transaction_unittest.cc
+++ b/net/spdy/spdy_network_transaction_unittest.cc
@@ -165,7 +165,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;
}
@@ -328,7 +327,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;
@@ -373,19 +372,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,
+ HttpResponseInfo* push_response,
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();
@@ -425,7 +419,7 @@ class SpdyNetworkTransactionTest
// Verify the SYN_REPLY.
// Copy the response info, because trans goes away.
*response = *trans->GetResponseInfo();
- *response2 = *trans2->GetResponseInfo();
+ *push_response = *trans2->GetResponseInfo();
VerifyStreamsClosed(helper);
}
@@ -1174,7 +1168,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)
};
@@ -1198,7 +1192,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),
@@ -1245,7 +1239,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)
};
@@ -1269,7 +1263,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),
@@ -1626,7 +1620,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());
@@ -1685,8 +1679,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);
@@ -1817,17 +1812,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(
@@ -1874,7 +1871,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,
@@ -2175,11 +2172,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),
@@ -2284,16 +2281,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),
@@ -2387,7 +2395,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),
@@ -2400,8 +2412,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);
@@ -2427,7 +2446,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[] = {
@@ -2442,8 +2465,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);
@@ -2466,7 +2496,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[] = {
@@ -2534,9 +2568,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),
@@ -2550,8 +2592,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);
@@ -2583,7 +2632,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),
@@ -2602,8 +2655,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);
@@ -2635,7 +2695,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),
@@ -2655,8 +2719,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);
@@ -2682,7 +2753,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),
@@ -2739,7 +2814,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),
@@ -2855,7 +2934,6 @@ TEST_P(SpdyNetworkTransactionTest, SynReplyHeaders) {
"cookie: val2\n"
"hello: bye\n"
"status: 200\n"
- "url: /index.php\n"
"version: HTTP/1.1\n"
},
// This is the minimalist set of headers.
@@ -2863,7 +2941,6 @@ TEST_P(SpdyNetworkTransactionTest, SynReplyHeaders) {
{ NULL },
"hello: bye\n"
"status: 200\n"
- "url: /index.php\n"
"version: HTTP/1.1\n"
},
// Headers with a comma separated list.
@@ -2874,7 +2951,6 @@ TEST_P(SpdyNetworkTransactionTest, SynReplyHeaders) {
"cookie: val1,val2\n"
"hello: bye\n"
"status: 200\n"
- "url: /index.php\n"
"version: HTTP/1.1\n"
}
};
@@ -3157,7 +3233,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);
}
}
@@ -4483,4 +4559,662 @@ TEST_P(SpdyNetworkTransactionTest, VerifyRetryOnConnectionReset) {
helper.VerifyDataConsumed();
}
}
+
+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);
+}
+
+TEST_P(SpdyNetworkTransactionTest, ServerPushCrossOriginCorrectness) {
+ // In this test we want to verify that we can't accidentally push content
+ // which can't be pushed by this content server.
+ // This test assumes that:
+ // - if we're requesting http://www.foo.com/barbaz
+ // - the browser has made a connection to "www.foo.com".
+
+ // A list of the URL to fetch, followed by the URL being pushed.
+ static const char* const kTestCases[] = {
+ "http://www.google.com/foo.html",
+ "http://www.google.com:81/foo.js", // Bad port
+
+ "http://www.google.com/foo.html",
+ "https://www.google.com/foo.js", // Bad protocol
+
+ "http://www.google.com/foo.html",
+ "ftp://www.google.com/foo.js", // Invalid Protocol
+
+ "http://www.google.com/foo.html",
+ "http://blat.www.google.com/foo.js", // Cross subdomain
+
+ "http://www.google.com/foo.html",
+ "http://www.foo.com/foo.js", // Cross domain
+ };
+
+
+ static const unsigned char kPushBodyFrame[] = {
+ 0x00, 0x00, 0x00, 0x02, // header, ID
+ 0x01, 0x00, 0x00, 0x06, // FIN, length
+ 'p', 'u', 's', 'h', 'e', 'd' // "pushed"
+ };
+
+ for (size_t index = 0; index < arraysize(kTestCases); index += 2) {
+ const char* url_to_fetch = kTestCases[index];
+ const char* url_to_push = kTestCases[index + 1];
+
+ scoped_ptr<spdy::SpdyFrame>
+ stream1_syn(ConstructSpdyGet(url_to_fetch, false, 1, LOWEST));
+ scoped_ptr<spdy::SpdyFrame>
+ stream1_body(ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<spdy::SpdyFrame> push_rst(
+ ConstructSpdyRstStream(2, spdy::REFUSED_STREAM));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ CreateMockWrite(*push_rst, 4),
+ };
+
+ scoped_ptr<spdy::SpdyFrame>
+ stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<spdy::SpdyFrame>
+ stream2_syn(ConstructSpdyPush(NULL,
+ 0,
+ 2,
+ 1,
+ url_to_push));
+ scoped_ptr<spdy::SpdyFrame> rst(
+ ConstructSpdyRstStream(2, spdy::CANCEL));
+
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply, 2),
+ CreateMockRead(*stream2_syn, 3),
+ 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;
+ scoped_refptr<OrderedSocketData> data(new OrderedSocketData(
+ reads,
+ arraysize(reads),
+ writes,
+ arraysize(writes)));
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL(url_to_fetch);
+ request.load_flags = 0;
+ NormalSpdyTransactionHelper helper(request,
+ BoundNetLog(), GetParam());
+ helper.RunPreTestSetup();
+ helper.AddData(data);
+
+ HttpNetworkTransaction* trans = helper.trans();
+
+ // Start the transaction with basic parameters.
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, &callback, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+
+ // 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 the SYN_REPLY.
+ // Copy the response info, because trans goes away.
+ response = *trans->GetResponseInfo();
+
+ VerifyStreamsClosed(helper);
+
+ // Verify the SYN_REPLY.
+ EXPECT_TRUE(response.headers != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+ }
+}
+
} // 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 588af81..3c8ae55 100644
--- a/net/spdy/spdy_proxy_client_socket.cc
+++ b/net/spdy/spdy_proxy_client_socket.cc
@@ -370,13 +370,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 30062b0..f2a3cd8 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;
@@ -73,43 +72,21 @@ class SpdyProxyClientSocket : public ClientSocket, public SpdyStream::Delegate {
virtual bool WasEverUsed() 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 81d1291..a9b372c 100644
--- a/net/spdy/spdy_session.cc
+++ b/net/spdy/spdy_session.cc
@@ -28,6 +28,36 @@
namespace net {
+NetLogSpdySynParameter::NetLogSpdySynParameter(
+ const linked_ptr<spdy::SpdyHeaderBlock>& headers,
+ spdy::SpdyControlFlags flags,
+ spdy::SpdyStreamId id,
+ spdy::SpdyStreamId associated_stream)
+ : headers_(headers),
+ flags_(flags),
+ id_(id),
+ associated_stream_(associated_stream) {
+}
+
+NetLogSpdySynParameter::~NetLogSpdySynParameter() {
+}
+
+Value* NetLogSpdySynParameter::ToValue() const {
+ DictionaryValue* dict = new DictionaryValue();
+ ListValue* headers_list = new ListValue();
+ for (spdy::SpdyHeaderBlock::const_iterator it = headers_->begin();
+ it != headers_->end(); ++it) {
+ headers_list->Append(new StringValue(base::StringPrintf(
+ "%s: %s", it->first.c_str(), it->second.c_str())));
+ }
+ dict->SetInteger("flags", flags_);
+ dict->Set("headers", headers_list);
+ dict->SetInteger("id", id_);
+ if (associated_stream_)
+ dict->SetInteger("associated_stream", associated_stream_);
+ return dict;
+}
+
namespace {
const int kReadBufferSize = 8 * 1024;
@@ -58,37 +88,6 @@ class NetLogSpdySessionParameter : public NetLog::EventParameters {
DISALLOW_COPY_AND_ASSIGN(NetLogSpdySessionParameter);
};
-class NetLogSpdySynParameter : public NetLog::EventParameters {
- public:
- NetLogSpdySynParameter(const linked_ptr<spdy::SpdyHeaderBlock>& headers,
- spdy::SpdyControlFlags flags,
- spdy::SpdyStreamId id)
- : headers_(headers), flags_(flags), id_(id) {}
-
- Value* ToValue() const {
- DictionaryValue* dict = new DictionaryValue();
- ListValue* headers_list = new ListValue();
- for (spdy::SpdyHeaderBlock::const_iterator it = headers_->begin();
- it != headers_->end(); ++it) {
- headers_list->Append(new StringValue(base::StringPrintf(
- "%s: %s", it->first.c_str(), it->second.c_str())));
- }
- dict->SetInteger("flags", flags_);
- dict->Set("headers", headers_list);
- dict->SetInteger("id", id_);
- return dict;
- }
-
- private:
- ~NetLogSpdySynParameter() {}
-
- const linked_ptr<spdy::SpdyHeaderBlock> headers_;
- const spdy::SpdyControlFlags flags_;
- const spdy::SpdyStreamId id_;
-
- DISALLOW_COPY_AND_ASSIGN(NetLogSpdySynParameter);
-};
-
class NetLogSpdySettingsParameter : public NetLog::EventParameters {
public:
explicit NetLogSpdySettingsParameter(const spdy::SpdySettings& settings)
@@ -246,8 +245,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)) {
net_log_.BeginEvent(
NetLog::TYPE_SPDY_SESSION,
@@ -323,9 +322,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_++;
@@ -472,7 +469,7 @@ int SpdySession::WriteSynStream(
if (net_log().IsLoggingAll()) {
net_log().AddEvent(
NetLog::TYPE_SPDY_SESSION_SYN_STREAM,
- new NetLogSpdySynParameter(headers, flags, stream_id));
+ new NetLogSpdySynParameter(headers, flags, stream_id, 0));
}
return ERR_IO_PENDING;
@@ -958,7 +955,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);
@@ -979,24 +975,24 @@ void SpdySession::OnSyn(const spdy::SpdySynStreamControlFrame& frame,
NetLog::TYPE_SPDY_SESSION_PUSHED_SYN_STREAM,
new NetLogSpdySynParameter(
headers, static_cast<spdy::SpdyControlFlags>(frame.flags()),
- stream_id));
+ stream_id, associated_stream_id));
}
// Server-initiated streams should have even sequence numbers.
if ((stream_id & 0x1) != 0) {
- LOG(ERROR) << "Received invalid OnSyn stream id " << stream_id;
+ LOG(WARNING) << "Received invalid OnSyn stream id " << stream_id;
return;
}
if (IsStreamActive(stream_id)) {
- LOG(ERROR) << "Received OnSyn for active stream " << stream_id;
+ LOG(WARNING) << "Received OnSyn for active stream " << stream_id;
return;
}
if (associated_stream_id == 0) {
- LOG(ERROR) << "Received invalid OnSyn associated stream id "
- << associated_stream_id
- << " for stream " << stream_id;
+ LOG(WARNING) << "Received invalid OnSyn associated stream id "
+ << associated_stream_id
+ << " for stream " << stream_id;
ResetStream(stream_id, spdy::INVALID_STREAM);
return;
}
@@ -1005,29 +1001,44 @@ 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 : "";
-
// Verify that the response had a URL for us.
- if (path.empty()) {
+ const std::string& url = ContainsKey(*headers, "url") ?
+ headers->find("url")->second : "";
+ if (url.empty()) {
ResetStream(stream_id, spdy::PROTOCOL_ERROR);
- LOG(WARNING) << "Pushed stream did not contain a path.";
+ 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 url was invalid: " << url;
+ return;
+ }
+
+ // Verify we have a valid stream association.
if (!IsStreamActive(associated_stream_id)) {
- LOG(ERROR) << "Received OnSyn with inactive associated stream "
+ LOG(WARNING) << "Received OnSyn with inactive associated stream "
<< associated_stream_id;
ResetStream(stream_id, spdy::INVALID_ASSOCIATED_STREAM);
return;
}
- // TODO(erikchen): Actually do something with the associated id.
+ scoped_refptr<SpdyStream> associated_stream =
+ active_streams_[associated_stream_id];
+ GURL associated_url(associated_stream->GetUrl());
+ if (associated_url.GetOrigin() != gurl.GetOrigin()) {
+ LOG(WARNING) << "Rejected Cross Origin Push Stream "
+ << associated_stream_id;
+ ResetStream(stream_id, spdy::REFUSED_STREAM);
+ return;
+ }
// 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(WARNING) << "Received duplicate pushed stream with url: " << url;
ResetStream(stream_id, spdy::PROTOCOL_ERROR);
return;
}
@@ -1035,9 +1046,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();
@@ -1077,25 +1088,62 @@ void SpdySession::OnSynReply(const spdy::SpdySynReplyControlFrame& frame,
NetLog::TYPE_SPDY_SESSION_SYN_REPLY,
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().IsLoggingAll()) {
+ 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;
@@ -1119,6 +1167,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 7d8db93..8f7dc15 100644
--- a/net/spdy/spdy_session.h
+++ b/net/spdy/spdy_session.h
@@ -251,6 +251,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);
@@ -415,6 +417,30 @@ class SpdySession : public base::RefCounted<SpdySession>,
static bool use_flow_control_;
};
+class NetLogSpdySynParameter : public NetLog::EventParameters {
+ public:
+ NetLogSpdySynParameter(const linked_ptr<spdy::SpdyHeaderBlock>& headers,
+ spdy::SpdyControlFlags flags,
+ spdy::SpdyStreamId id,
+ spdy::SpdyStreamId associated_stream);
+
+ virtual Value* ToValue() const;
+
+ const linked_ptr<spdy::SpdyHeaderBlock>& GetHeaders() const {
+ return headers_;
+ }
+
+ private:
+ virtual ~NetLogSpdySynParameter();
+
+ const linked_ptr<spdy::SpdyHeaderBlock> headers_;
+ const spdy::SpdyControlFlags flags_;
+ const spdy::SpdyStreamId id_;
+ const spdy::SpdyStreamId associated_stream_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetLogSpdySynParameter);
+};
+
} // namespace net
#endif // NET_SPDY_SPDY_SESSION_H_
diff --git a/net/spdy/spdy_stream.cc b/net/spdy/spdy_stream.cc
index 102d318..04f9ffb 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,13 +60,10 @@ SpdyStream::SpdyStream(SpdySession* session,
net_log_(net_log),
send_bytes_(0),
recv_bytes_(0) {
- net_log_.BeginEvent(NetLog::TYPE_SPDY_STREAM,
- new NetLogIntegerParameter("stream_id", stream_id_));
}
SpdyStream::~SpdyStream() {
UpdateHistograms();
- net_log_.EndEvent(NetLog::TYPE_SPDY_STREAM, NULL);
}
void SpdyStream::SetDelegate(Delegate* delegate) {
@@ -87,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) {
@@ -248,9 +253,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.
@@ -267,15 +307,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) {
@@ -361,6 +393,43 @@ bool SpdyStream::GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info) {
return session_->GetSSLCertRequestInfo(cert_request_info);
}
+bool SpdyStream::HasUrl() const {
+ if (pushed_)
+ return response_received();
+ return request_.get() != NULL;
+}
+
+GURL SpdyStream::GetUrl() const {
+ DCHECK(HasUrl());
+
+ if (pushed_) {
+ // assemble from the response
+ std::string url;
+ spdy::SpdyHeaderBlock::const_iterator it;
+ it = response_->find("url");
+ if (it != (*response_).end())
+ url = it->second;
+ return GURL(url);
+ }
+
+ // assemble from the request
+ std::string scheme;
+ std::string host_port;
+ std::string path;
+ spdy::SpdyHeaderBlock::const_iterator it;
+ it = request_->find("scheme");
+ if (it != (*request_).end())
+ scheme = it->second;
+ it = request_->find("host");
+ if (it != (*request_).end())
+ host_port = it->second;
+ it = request_->find("path");
+ if (it != (*request_).end())
+ path = it->second;
+ std::string url = scheme + "://" + host_port + path;
+ return GURL(url);
+}
+
int SpdyStream::DoLoop(int result) {
do {
State state = io_state_;
diff --git a/net/spdy/spdy_stream.h b/net/spdy/spdy_stream.h
index 4e30a60..9a055c2 100644
--- a/net/spdy/spdy_stream.h
+++ b/net/spdy/spdy_stream.h
@@ -13,6 +13,7 @@
#include "base/linked_ptr.h"
#include "base/ref_counted.h"
#include "base/scoped_ptr.h"
+#include "googleurl/src/gurl.h"
#include "net/base/bandwidth_metrics.h"
#include "net/base/io_buffer.h"
#include "net/base/net_log.h"
@@ -53,8 +54,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 +162,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.
@@ -206,6 +215,13 @@ class SpdyStream : public base::RefCounted<SpdyStream> {
int response_status() const { return response_status_; }
+ // Returns true if the URL for this stream is known.
+ bool HasUrl() const;
+
+ // Get the URL associated with this stream. Only valid when has_url() is
+ // true.
+ GURL GetUrl() const;
+
private:
enum State {
STATE_NONE,
diff --git a/net/spdy/spdy_stream_unittest.cc b/net/spdy/spdy_stream_unittest.cc
index 5ece945..7350271 100644
--- a/net/spdy/spdy_stream_unittest.cc
+++ b/net/spdy/spdy_stream_unittest.cc
@@ -150,8 +150,12 @@ TEST_F(SpdyStreamTest, SendDataAfterOpen) {
static const char* const kGetHeaders[] = {
"method",
"GET",
- "url",
- "http://www.google.com/",
+ "scheme",
+ "http",
+ "host",
+ "www.google.com",
+ "path",
+ "/",
"version",
"HTTP/1.1",
};
@@ -189,7 +193,8 @@ TEST_F(SpdyStreamTest, SendDataAfterOpen) {
SpdySession::SetSSLMode(false);
scoped_refptr<SpdySession> session(CreateSpdySession());
- GURL url("http://www.google.com/");
+ const char* kStreamUrl = "http://www.google.com/";
+ GURL url(kStreamUrl);
HostPortPair host_port_pair("www.google.com", 80);
scoped_refptr<TCPSocketParams> tcp_params =
@@ -213,11 +218,17 @@ TEST_F(SpdyStreamTest, SendDataAfterOpen) {
new TestSpdyStreamDelegate(stream.get(), buf.get(), &callback));
stream->SetDelegate(delegate.get());
+ EXPECT_FALSE(stream->HasUrl());
+
linked_ptr<spdy::SpdyHeaderBlock> headers(new spdy::SpdyHeaderBlock);
(*headers)["method"] = "GET";
- (*headers)["url"] = "http://www.google.com/";
+ (*headers)["scheme"] = url.scheme();
+ (*headers)["host"] = url.host();
+ (*headers)["path"] = url.path();
(*headers)["version"] = "HTTP/1.1";
stream->set_spdy_headers(headers);
+ EXPECT_TRUE(stream->HasUrl());
+ EXPECT_EQ(kStreamUrl, stream->GetUrl().spec());
EXPECT_EQ(ERR_IO_PENDING, stream->SendRequest(true));
@@ -231,4 +242,39 @@ TEST_F(SpdyStreamTest, SendDataAfterOpen) {
EXPECT_TRUE(delegate->closed());
}
+TEST_F(SpdyStreamTest, PushedStream) {
+ const char kStreamUrl[] = "http://www.google.com/";
+
+ SpdySessionDependencies session_deps;
+ session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps);
+ SpdySessionPoolPeer pool_peer_(session_->spdy_session_pool());
+ scoped_refptr<SpdySession> spdy_session(CreateSpdySession());
+ BoundNetLog net_log;
+
+ // Conjure up a stream.
+ scoped_refptr<SpdyStream> stream = new SpdyStream(spdy_session,
+ 2,
+ true,
+ net_log);
+ EXPECT_FALSE(stream->response_received());
+ EXPECT_FALSE(stream->HasUrl());
+
+ // Set a couple of headers.
+ spdy::SpdyHeaderBlock response;
+ response["url"] = kStreamUrl;
+ stream->OnResponseReceived(response);
+
+ // Send some basic headers.
+ spdy::SpdyHeaderBlock headers;
+ response["status"] = "200";
+ response["version"] = "OK";
+ stream->OnHeaders(headers);
+
+ stream->set_response_received();
+ EXPECT_TRUE(stream->response_received());
+ EXPECT_TRUE(stream->HasUrl());
+ EXPECT_EQ(kStreamUrl, stream->GetUrl().spec());
+}
+
+
} // namespace net
diff --git a/net/spdy/spdy_test_util.cc b/net/spdy/spdy_test_util.cc
index 1727717..554af99 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,
@@ -330,7 +334,11 @@ spdy::SpdyFrame* ConstructSpdyGet(const char* const url,
// This is so ugly. Why are we using char* in here again?
std::string str_path = gurl.PathForRequest();
std::string str_scheme = gurl.scheme();
- std::string str_host = gurl.host(); // TODO(mbelshe): should have a port.
+ std::string str_host = gurl.host();
+ if (gurl.has_port()) {
+ str_host += ":";
+ str_host += gurl.port();
+ }
scoped_array<char> req(new char[str_path.size() + 1]);
scoped_array<char> scheme(new char[str_scheme.size() + 1]);
scoped_array<char> host(new char[str_host.size() + 1]);
@@ -439,16 +447,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"
};
@@ -468,15 +474,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",
@@ -501,6 +504,46 @@ spdy::SpdyFrame* ConstructSpdyPush(const char* const extra_headers[],
// 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.
+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* ConstructSpdyGetSynReplyRedirect(int stream_id) {
static const char* const kStandardGetHeaders[] = {
@@ -536,8 +579,6 @@ spdy::SpdyFrame* ConstructSpdyGetSynReply(const char* const extra_headers[],
"bye",
"status",
"200",
- "url",
- "/index.php",
"version",
"HTTP/1.1"
};
diff --git a/net/spdy/spdy_test_util.h b/net/spdy/spdy_test_util.h
index 6304c74..0514d69 100644
--- a/net/spdy/spdy_test_util.h
+++ b/net/spdy/spdy_test_util.h
@@ -224,16 +224,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.