summaryrefslogtreecommitdiff
path: root/chromium/net/spdy/spdy_stream_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/net/spdy/spdy_stream_unittest.cc')
-rw-r--r--chromium/net/spdy/spdy_stream_unittest.cc999
1 files changed, 999 insertions, 0 deletions
diff --git a/chromium/net/spdy/spdy_stream_unittest.cc b/chromium/net/spdy/spdy_stream_unittest.cc
new file mode 100644
index 00000000000..c98ba937be0
--- /dev/null
+++ b/chromium/net/spdy/spdy_stream_unittest.cc
@@ -0,0 +1,999 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <cstddef>
+#include <string>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "base/strings/string_piece.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/request_priority.h"
+#include "net/socket/next_proto.h"
+#include "net/socket/socket_test_util.h"
+#include "net/spdy/buffered_spdy_framer.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_stream.h"
+#include "net/spdy/spdy_stream_test_util.h"
+#include "net/spdy/spdy_test_util_common.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// TODO(ukai): factor out common part with spdy_http_stream_unittest.cc
+//
+namespace net {
+
+namespace test {
+
+namespace {
+
+const char kStreamUrl[] = "http://www.google.com/";
+const char kPostBody[] = "\0hello!\xff";
+const size_t kPostBodyLength = arraysize(kPostBody);
+const base::StringPiece kPostBodyStringPiece(kPostBody, kPostBodyLength);
+
+class SpdyStreamTest : public ::testing::Test,
+ public ::testing::WithParamInterface<NextProto> {
+ protected:
+ // A function that takes a SpdyStream and the number of bytes which
+ // will unstall the next frame completely.
+ typedef base::Callback<void(const base::WeakPtr<SpdyStream>&, int32)>
+ UnstallFunction;
+
+ SpdyStreamTest()
+ : spdy_util_(GetParam()),
+ session_deps_(GetParam()),
+ offset_(0) {}
+
+ base::WeakPtr<SpdySession> CreateDefaultSpdySession() {
+ SpdySessionKey key(HostPortPair("www.google.com", 80),
+ ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+ return CreateInsecureSpdySession(session_, key, BoundNetLog());
+ }
+
+ virtual void TearDown() {
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ void RunResumeAfterUnstallRequestResponseTest(
+ const UnstallFunction& unstall_function);
+
+ void RunResumeAfterUnstallBidirectionalTest(
+ const UnstallFunction& unstall_function);
+
+ // Add{Read,Write}() populates lists that are eventually passed to a
+ // SocketData class. |frame| must live for the whole test.
+
+ void AddRead(const SpdyFrame& frame) {
+ reads_.push_back(CreateMockRead(frame, offset_++));
+ }
+
+ void AddWrite(const SpdyFrame& frame) {
+ writes_.push_back(CreateMockWrite(frame, offset_++));
+ }
+
+ void AddReadEOF() {
+ reads_.push_back(MockRead(ASYNC, 0, offset_++));
+ }
+
+ MockRead* GetReads() {
+ return vector_as_array(&reads_);
+ }
+
+ size_t GetNumReads() const {
+ return reads_.size();
+ }
+
+ MockWrite* GetWrites() {
+ return vector_as_array(&writes_);
+ }
+
+ int GetNumWrites() const {
+ return writes_.size();
+ }
+
+ SpdyTestUtil spdy_util_;
+ SpdySessionDependencies session_deps_;
+ scoped_refptr<HttpNetworkSession> session_;
+
+ private:
+ // Used by Add{Read,Write}() above.
+ std::vector<MockWrite> writes_;
+ std::vector<MockRead> reads_;
+ int offset_;
+};
+
+INSTANTIATE_TEST_CASE_P(
+ NextProto,
+ SpdyStreamTest,
+ testing::Values(kProtoSPDY2, kProtoSPDY3, kProtoSPDY31, kProtoSPDY4a2,
+ kProtoHTTP2Draft04));
+
+TEST_P(SpdyStreamTest, SendDataAfterOpen) {
+ GURL url(kStreamUrl);
+
+ session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_);
+
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyPost(
+ kStreamUrl, 1, kPostBodyLength, LOWEST, NULL, 0));
+ AddWrite(*req);
+
+ scoped_ptr<SpdyFrame> resp(
+ spdy_util_.ConstructSpdyPostSynReply(NULL, 0));
+ AddRead(*resp);
+
+ scoped_ptr<SpdyFrame> msg(
+ spdy_util_.ConstructSpdyBodyFrame(1, kPostBody, kPostBodyLength, false));
+ AddWrite(*msg);
+
+ scoped_ptr<SpdyFrame> echo(
+ spdy_util_.ConstructSpdyBodyFrame(1, kPostBody, kPostBodyLength, false));
+ AddRead(*echo);
+
+ AddReadEOF();
+
+ OrderedSocketData data(GetReads(), GetNumReads(),
+ GetWrites(), GetNumWrites());
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ base::WeakPtr<SpdySession> session(CreateDefaultSpdySession());
+
+ base::WeakPtr<SpdyStream> stream =
+ CreateStreamSynchronously(
+ SPDY_BIDIRECTIONAL_STREAM, session, url, LOWEST, BoundNetLog());
+ ASSERT_TRUE(stream.get() != NULL);
+
+ StreamDelegateSendImmediate delegate(stream, kPostBodyStringPiece);
+ stream->SetDelegate(&delegate);
+
+ EXPECT_FALSE(stream->HasUrlFromHeaders());
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kPostBodyLength));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream->HasUrlFromHeaders());
+ EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec());
+
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose());
+
+ EXPECT_TRUE(delegate.send_headers_completed());
+ EXPECT_EQ("200", delegate.GetResponseHeaderValue(spdy_util_.GetStatusKey()));
+ EXPECT_EQ("HTTP/1.1",
+ delegate.GetResponseHeaderValue(spdy_util_.GetVersionKey()));
+ EXPECT_EQ(std::string(kPostBody, kPostBodyLength),
+ delegate.TakeReceivedData());
+ EXPECT_TRUE(data.at_write_eof());
+}
+
+TEST_P(SpdyStreamTest, PushedStream) {
+ session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_);
+
+ AddReadEOF();
+
+ OrderedSocketData data(GetReads(), GetNumReads(),
+ GetWrites(), GetNumWrites());
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ base::WeakPtr<SpdySession> spdy_session(CreateDefaultSpdySession());
+
+ // Conjure up a stream.
+ SpdyStream stream(SPDY_PUSH_STREAM,
+ spdy_session,
+ GURL(),
+ DEFAULT_PRIORITY,
+ kSpdyStreamInitialWindowSize,
+ kSpdyStreamInitialWindowSize,
+ BoundNetLog());
+ stream.set_stream_id(2);
+ EXPECT_FALSE(stream.HasUrlFromHeaders());
+
+ // Set a couple of headers.
+ SpdyHeaderBlock response;
+ spdy_util_.AddUrlToHeaderBlock(kStreamUrl, &response);
+ stream.OnInitialResponseHeadersReceived(
+ response, base::Time::Now(), base::TimeTicks::Now());
+
+ // Send some basic headers.
+ SpdyHeaderBlock headers;
+ headers[spdy_util_.GetStatusKey()] = "200";
+ headers[spdy_util_.GetVersionKey()] = "OK";
+ stream.OnAdditionalResponseHeadersReceived(headers);
+
+ EXPECT_TRUE(stream.HasUrlFromHeaders());
+ EXPECT_EQ(kStreamUrl, stream.GetUrlFromHeaders().spec());
+
+ StreamDelegateDoNothing delegate(stream.GetWeakPtr());
+ stream.SetDelegate(&delegate);
+
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_EQ("200", delegate.GetResponseHeaderValue(spdy_util_.GetStatusKey()));
+
+ EXPECT_TRUE(spdy_session == NULL);
+}
+
+TEST_P(SpdyStreamTest, StreamError) {
+ GURL url(kStreamUrl);
+
+ session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_);
+
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyPost(
+ kStreamUrl, 1, kPostBodyLength, LOWEST, NULL, 0));
+ AddWrite(*req);
+
+ scoped_ptr<SpdyFrame> resp(
+ spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ AddRead(*resp);
+
+ scoped_ptr<SpdyFrame> msg(
+ spdy_util_.ConstructSpdyBodyFrame(1, kPostBody, kPostBodyLength, false));
+ AddWrite(*msg);
+
+ scoped_ptr<SpdyFrame> echo(
+ spdy_util_.ConstructSpdyBodyFrame(1, kPostBody, kPostBodyLength, false));
+ AddRead(*echo);
+
+ AddReadEOF();
+
+ CapturingBoundNetLog log;
+
+ OrderedSocketData data(GetReads(), GetNumReads(),
+ GetWrites(), GetNumWrites());
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ base::WeakPtr<SpdySession> session(CreateDefaultSpdySession());
+
+ base::WeakPtr<SpdyStream> stream =
+ CreateStreamSynchronously(
+ SPDY_BIDIRECTIONAL_STREAM, session, url, LOWEST, log.bound());
+ ASSERT_TRUE(stream.get() != NULL);
+
+ StreamDelegateSendImmediate delegate(stream, kPostBodyStringPiece);
+ stream->SetDelegate(&delegate);
+
+ EXPECT_FALSE(stream->HasUrlFromHeaders());
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kPostBodyLength));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream->HasUrlFromHeaders());
+ EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec());
+
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose());
+
+ const SpdyStreamId stream_id = delegate.stream_id();
+
+ EXPECT_TRUE(delegate.send_headers_completed());
+ EXPECT_EQ("200", delegate.GetResponseHeaderValue(spdy_util_.GetStatusKey()));
+ EXPECT_EQ("HTTP/1.1",
+ delegate.GetResponseHeaderValue(spdy_util_.GetVersionKey()));
+ EXPECT_EQ(std::string(kPostBody, kPostBodyLength),
+ delegate.TakeReceivedData());
+ EXPECT_TRUE(data.at_write_eof());
+
+ // Check that the NetLog was filled reasonably.
+ net::CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+ EXPECT_LT(0u, entries.size());
+
+ // Check that we logged SPDY_STREAM_ERROR correctly.
+ int pos = net::ExpectLogContainsSomewhere(
+ entries, 0,
+ net::NetLog::TYPE_SPDY_STREAM_ERROR,
+ net::NetLog::PHASE_NONE);
+
+ int stream_id2;
+ ASSERT_TRUE(entries[pos].GetIntegerValue("stream_id", &stream_id2));
+ EXPECT_EQ(static_cast<int>(stream_id), stream_id2);
+}
+
+// Make sure that large blocks of data are properly split up into
+// frame-sized chunks for a request/response (i.e., an HTTP-like)
+// stream.
+TEST_P(SpdyStreamTest, SendLargeDataAfterOpenRequestResponse) {
+ GURL url(kStreamUrl);
+
+ session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_);
+
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyPost(
+ kStreamUrl, 1, kPostBodyLength, LOWEST, NULL, 0));
+ AddWrite(*req);
+
+ std::string chunk_data(kMaxSpdyFrameChunkSize, 'x');
+ scoped_ptr<SpdyFrame> chunk(
+ spdy_util_.ConstructSpdyBodyFrame(
+ 1, chunk_data.data(), chunk_data.length(), false));
+ AddWrite(*chunk);
+ AddWrite(*chunk);
+
+ scoped_ptr<SpdyFrame> last_chunk(
+ spdy_util_.ConstructSpdyBodyFrame(
+ 1, chunk_data.data(), chunk_data.length(), true));
+ AddWrite(*last_chunk);
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0));
+ AddRead(*resp);
+
+ AddReadEOF();
+
+ OrderedSocketData data(GetReads(), GetNumReads(),
+ GetWrites(), GetNumWrites());
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ base::WeakPtr<SpdySession> session(CreateDefaultSpdySession());
+
+ base::WeakPtr<SpdyStream> stream =
+ CreateStreamSynchronously(
+ SPDY_REQUEST_RESPONSE_STREAM, session, url, LOWEST, BoundNetLog());
+ ASSERT_TRUE(stream.get() != NULL);
+
+ std::string body_data(3 * kMaxSpdyFrameChunkSize, 'x');
+ StreamDelegateWithBody delegate(stream, body_data);
+ stream->SetDelegate(&delegate);
+
+ EXPECT_FALSE(stream->HasUrlFromHeaders());
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kPostBodyLength));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream->HasUrlFromHeaders());
+ EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec());
+
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose());
+
+ EXPECT_TRUE(delegate.send_headers_completed());
+ EXPECT_EQ("200", delegate.GetResponseHeaderValue(spdy_util_.GetStatusKey()));
+ EXPECT_EQ("HTTP/1.1",
+ delegate.GetResponseHeaderValue(spdy_util_.GetVersionKey()));
+ EXPECT_EQ(std::string(), delegate.TakeReceivedData());
+ EXPECT_TRUE(data.at_write_eof());
+}
+
+// Make sure that large blocks of data are properly split up into
+// frame-sized chunks for a bidirectional (i.e., non-HTTP-like)
+// stream.
+TEST_P(SpdyStreamTest, SendLargeDataAfterOpenBidirectional) {
+ GURL url(kStreamUrl);
+
+ session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_);
+
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyPost(
+ kStreamUrl, 1, kPostBodyLength, LOWEST, NULL, 0));
+ AddWrite(*req);
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0));
+ AddRead(*resp);
+
+ std::string chunk_data(kMaxSpdyFrameChunkSize, 'x');
+ scoped_ptr<SpdyFrame> chunk(
+ spdy_util_.ConstructSpdyBodyFrame(
+ 1, chunk_data.data(), chunk_data.length(), false));
+ AddWrite(*chunk);
+ AddWrite(*chunk);
+ AddWrite(*chunk);
+
+ AddReadEOF();
+
+ OrderedSocketData data(GetReads(), GetNumReads(),
+ GetWrites(), GetNumWrites());
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ base::WeakPtr<SpdySession> session(CreateDefaultSpdySession());
+
+ base::WeakPtr<SpdyStream> stream =
+ CreateStreamSynchronously(
+ SPDY_BIDIRECTIONAL_STREAM, session, url, LOWEST, BoundNetLog());
+ ASSERT_TRUE(stream.get() != NULL);
+
+ std::string body_data(3 * kMaxSpdyFrameChunkSize, 'x');
+ StreamDelegateSendImmediate delegate(stream, body_data);
+ stream->SetDelegate(&delegate);
+
+ EXPECT_FALSE(stream->HasUrlFromHeaders());
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kPostBodyLength));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream->HasUrlFromHeaders());
+ EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec());
+
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose());
+
+ EXPECT_TRUE(delegate.send_headers_completed());
+ EXPECT_EQ("200", delegate.GetResponseHeaderValue(spdy_util_.GetStatusKey()));
+ EXPECT_EQ("HTTP/1.1",
+ delegate.GetResponseHeaderValue(spdy_util_.GetVersionKey()));
+ EXPECT_EQ(std::string(), delegate.TakeReceivedData());
+ EXPECT_TRUE(data.at_write_eof());
+}
+
+// Receiving a header with uppercase ASCII should result in a protocol
+// error.
+TEST_P(SpdyStreamTest, UpperCaseHeaders) {
+ GURL url(kStreamUrl);
+
+ session_ =
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_);
+
+ scoped_ptr<SpdyFrame> syn(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ AddWrite(*syn);
+
+ const char* const kExtraHeaders[] = {"X-UpperCase", "yes"};
+ scoped_ptr<SpdyFrame>
+ reply(spdy_util_.ConstructSpdyGetSynReply(kExtraHeaders, 1, 1));
+ AddRead(*reply);
+
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_PROTOCOL_ERROR));
+ AddWrite(*rst);
+
+ AddReadEOF();
+
+ DeterministicSocketData data(GetReads(), GetNumReads(),
+ GetWrites(), GetNumWrites());
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ base::WeakPtr<SpdySession> session(CreateDefaultSpdySession());
+
+ base::WeakPtr<SpdyStream> stream =
+ CreateStreamSynchronously(
+ SPDY_REQUEST_RESPONSE_STREAM, session, url, LOWEST, BoundNetLog());
+ ASSERT_TRUE(stream.get() != NULL);
+
+ StreamDelegateDoNothing delegate(stream);
+ stream->SetDelegate(&delegate);
+
+ EXPECT_FALSE(stream->HasUrlFromHeaders());
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructGetHeaderBlock(kStreamUrl));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream->HasUrlFromHeaders());
+ EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec());
+
+ data.RunFor(4);
+
+ EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, delegate.WaitForClose());
+}
+
+// Receiving a header with uppercase ASCII should result in a protocol
+// error even for a push stream.
+TEST_P(SpdyStreamTest, UpperCaseHeadersOnPush) {
+ GURL url(kStreamUrl);
+
+ session_ =
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_);
+
+ scoped_ptr<SpdyFrame> syn(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ AddWrite(*syn);
+
+ scoped_ptr<SpdyFrame>
+ reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ AddRead(*reply);
+
+ const char* const extra_headers[] = {"X-UpperCase", "yes"};
+ scoped_ptr<SpdyFrame>
+ push(spdy_util_.ConstructSpdyPush(extra_headers, 1, 2, 1, kStreamUrl));
+ AddRead(*push);
+
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_PROTOCOL_ERROR));
+ AddWrite(*rst);
+
+ AddReadEOF();
+
+ DeterministicSocketData data(GetReads(), GetNumReads(),
+ GetWrites(), GetNumWrites());
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ base::WeakPtr<SpdySession> session(CreateDefaultSpdySession());
+
+ base::WeakPtr<SpdyStream> stream =
+ CreateStreamSynchronously(
+ SPDY_REQUEST_RESPONSE_STREAM, session, url, LOWEST, BoundNetLog());
+ ASSERT_TRUE(stream.get() != NULL);
+
+ StreamDelegateDoNothing delegate(stream);
+ stream->SetDelegate(&delegate);
+
+ EXPECT_FALSE(stream->HasUrlFromHeaders());
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructGetHeaderBlock(kStreamUrl));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream->HasUrlFromHeaders());
+ EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec());
+
+ data.RunFor(4);
+
+ base::WeakPtr<SpdyStream> push_stream;
+ EXPECT_EQ(OK, session->GetPushStream(url, &push_stream, BoundNetLog()));
+ EXPECT_FALSE(push_stream);
+
+ data.RunFor(1);
+
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose());
+}
+
+// Receiving a header with uppercase ASCII in a HEADERS frame should
+// result in a protocol error.
+TEST_P(SpdyStreamTest, UpperCaseHeadersInHeadersFrame) {
+ GURL url(kStreamUrl);
+
+ session_ =
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_);
+
+ scoped_ptr<SpdyFrame> syn(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ AddWrite(*syn);
+
+ scoped_ptr<SpdyFrame>
+ reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ AddRead(*reply);
+
+ scoped_ptr<SpdyFrame>
+ push(spdy_util_.ConstructSpdyPush(NULL, 0, 2, 1, kStreamUrl));
+ AddRead(*push);
+
+ scoped_ptr<SpdyHeaderBlock> late_headers(new SpdyHeaderBlock());
+ (*late_headers)["X-UpperCase"] = "yes";
+ scoped_ptr<SpdyFrame> headers_frame(
+ spdy_util_.ConstructSpdyControlFrame(late_headers.Pass(),
+ false,
+ 2,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ 0));
+ AddRead(*headers_frame);
+
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_PROTOCOL_ERROR));
+ AddWrite(*rst);
+
+ AddReadEOF();
+
+ DeterministicSocketData data(GetReads(), GetNumReads(),
+ GetWrites(), GetNumWrites());
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ base::WeakPtr<SpdySession> session(CreateDefaultSpdySession());
+
+ base::WeakPtr<SpdyStream> stream =
+ CreateStreamSynchronously(
+ SPDY_REQUEST_RESPONSE_STREAM, session, url, LOWEST, BoundNetLog());
+ ASSERT_TRUE(stream.get() != NULL);
+
+ StreamDelegateDoNothing delegate(stream);
+ stream->SetDelegate(&delegate);
+
+ EXPECT_FALSE(stream->HasUrlFromHeaders());
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructGetHeaderBlock(kStreamUrl));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream->HasUrlFromHeaders());
+ EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec());
+
+ data.RunFor(3);
+
+ base::WeakPtr<SpdyStream> push_stream;
+ EXPECT_EQ(OK, session->GetPushStream(url, &push_stream, BoundNetLog()));
+ EXPECT_TRUE(push_stream);
+
+ data.RunFor(1);
+
+ EXPECT_EQ(OK, session->GetPushStream(url, &push_stream, BoundNetLog()));
+ EXPECT_FALSE(push_stream);
+
+ data.RunFor(2);
+
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose());
+}
+
+// Receiving a duplicate header in a HEADERS frame should result in a
+// protocol error.
+TEST_P(SpdyStreamTest, DuplicateHeaders) {
+ GURL url(kStreamUrl);
+
+ session_ =
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_);
+
+ scoped_ptr<SpdyFrame> syn(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ AddWrite(*syn);
+
+ scoped_ptr<SpdyFrame>
+ reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ AddRead(*reply);
+
+ scoped_ptr<SpdyFrame>
+ push(spdy_util_.ConstructSpdyPush(NULL, 0, 2, 1, kStreamUrl));
+ AddRead(*push);
+
+ scoped_ptr<SpdyHeaderBlock> late_headers(new SpdyHeaderBlock());
+ (*late_headers)[spdy_util_.GetStatusKey()] = "500 Server Error";
+ scoped_ptr<SpdyFrame> headers_frame(
+ spdy_util_.ConstructSpdyControlFrame(late_headers.Pass(),
+ false,
+ 2,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ 0));
+ AddRead(*headers_frame);
+
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_PROTOCOL_ERROR));
+ AddWrite(*rst);
+
+ AddReadEOF();
+
+ DeterministicSocketData data(GetReads(), GetNumReads(),
+ GetWrites(), GetNumWrites());
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ base::WeakPtr<SpdySession> session(CreateDefaultSpdySession());
+
+ base::WeakPtr<SpdyStream> stream =
+ CreateStreamSynchronously(
+ SPDY_REQUEST_RESPONSE_STREAM, session, url, LOWEST, BoundNetLog());
+ ASSERT_TRUE(stream.get() != NULL);
+
+ StreamDelegateDoNothing delegate(stream);
+ stream->SetDelegate(&delegate);
+
+ EXPECT_FALSE(stream->HasUrlFromHeaders());
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructGetHeaderBlock(kStreamUrl));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream->HasUrlFromHeaders());
+ EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec());
+
+ data.RunFor(3);
+
+ base::WeakPtr<SpdyStream> push_stream;
+ EXPECT_EQ(OK, session->GetPushStream(url, &push_stream, BoundNetLog()));
+ EXPECT_TRUE(push_stream);
+
+ data.RunFor(1);
+
+ EXPECT_EQ(OK, session->GetPushStream(url, &push_stream, BoundNetLog()));
+ EXPECT_FALSE(push_stream);
+
+ data.RunFor(2);
+
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose());
+}
+
+// The tests below are only for SPDY/3 and above.
+
+// Call IncreaseSendWindowSize on a stream with a large enough delta
+// to overflow an int32. The SpdyStream should handle that case
+// gracefully.
+TEST_P(SpdyStreamTest, IncreaseSendWindowSizeOverflow) {
+ if (spdy_util_.protocol() < kProtoSPDY3)
+ return;
+
+ session_ =
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_);
+
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyPost(
+ kStreamUrl, 1, kPostBodyLength, LOWEST, NULL, 0));
+ AddWrite(*req);
+
+ // Triggered by the overflowing call to IncreaseSendWindowSize
+ // below.
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_FLOW_CONTROL_ERROR));
+ AddWrite(*rst);
+
+ AddReadEOF();
+
+ CapturingBoundNetLog log;
+
+ DeterministicSocketData data(GetReads(), GetNumReads(),
+ GetWrites(), GetNumWrites());
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ base::WeakPtr<SpdySession> session(CreateDefaultSpdySession());
+ GURL url(kStreamUrl);
+
+ base::WeakPtr<SpdyStream> stream =
+ CreateStreamSynchronously(
+ SPDY_BIDIRECTIONAL_STREAM, session, url, LOWEST, log.bound());
+ ASSERT_TRUE(stream.get() != NULL);
+ StreamDelegateSendImmediate delegate(stream, kPostBodyStringPiece);
+ stream->SetDelegate(&delegate);
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kPostBodyLength));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream->HasUrlFromHeaders());
+ EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec());
+
+ data.RunFor(1);
+
+ int32 old_send_window_size = stream->send_window_size();
+ ASSERT_GT(old_send_window_size, 0);
+ int32 delta_window_size = kint32max - old_send_window_size + 1;
+ stream->IncreaseSendWindowSize(delta_window_size);
+ EXPECT_EQ(NULL, stream.get());
+
+ data.RunFor(2);
+
+ EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, delegate.WaitForClose());
+}
+
+// Functions used with
+// RunResumeAfterUnstall{RequestResponse,Bidirectional}Test().
+
+void StallStream(const base::WeakPtr<SpdyStream>& stream) {
+ // Reduce the send window size to 0 to stall.
+ while (stream->send_window_size() > 0) {
+ stream->DecreaseSendWindowSize(
+ std::min(kMaxSpdyFrameChunkSize, stream->send_window_size()));
+ }
+}
+
+void IncreaseStreamSendWindowSize(const base::WeakPtr<SpdyStream>& stream,
+ int32 delta_window_size) {
+ EXPECT_TRUE(stream->send_stalled_by_flow_control());
+ stream->IncreaseSendWindowSize(delta_window_size);
+ EXPECT_FALSE(stream->send_stalled_by_flow_control());
+}
+
+void AdjustStreamSendWindowSize(const base::WeakPtr<SpdyStream>& stream,
+ int32 delta_window_size) {
+ // Make sure that negative adjustments are handled properly.
+ EXPECT_TRUE(stream->send_stalled_by_flow_control());
+ stream->AdjustSendWindowSize(-delta_window_size);
+ EXPECT_TRUE(stream->send_stalled_by_flow_control());
+ stream->AdjustSendWindowSize(+delta_window_size);
+ EXPECT_TRUE(stream->send_stalled_by_flow_control());
+ stream->AdjustSendWindowSize(+delta_window_size);
+ EXPECT_FALSE(stream->send_stalled_by_flow_control());
+}
+
+// Given an unstall function, runs a test to make sure that a
+// request/response (i.e., an HTTP-like) stream resumes after a stall
+// and unstall.
+void SpdyStreamTest::RunResumeAfterUnstallRequestResponseTest(
+ const UnstallFunction& unstall_function) {
+ GURL url(kStreamUrl);
+
+ session_ =
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_);
+
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyPost(
+ kStreamUrl, 1, kPostBodyLength, LOWEST, NULL, 0));
+ AddWrite(*req);
+
+ scoped_ptr<SpdyFrame> body(
+ spdy_util_.ConstructSpdyBodyFrame(1, kPostBody, kPostBodyLength, true));
+ AddWrite(*body);
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ AddRead(*resp);
+
+ AddReadEOF();
+
+ DeterministicSocketData data(GetReads(), GetNumReads(),
+ GetWrites(), GetNumWrites());
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ base::WeakPtr<SpdySession> session(CreateDefaultSpdySession());
+
+ base::WeakPtr<SpdyStream> stream =
+ CreateStreamSynchronously(
+ SPDY_REQUEST_RESPONSE_STREAM, session, url, LOWEST, BoundNetLog());
+ ASSERT_TRUE(stream.get() != NULL);
+
+ StreamDelegateWithBody delegate(stream, kPostBodyStringPiece);
+ stream->SetDelegate(&delegate);
+
+ EXPECT_FALSE(stream->HasUrlFromHeaders());
+ EXPECT_FALSE(stream->send_stalled_by_flow_control());
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kPostBodyLength));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream->HasUrlFromHeaders());
+ EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec());
+
+ StallStream(stream);
+
+ data.RunFor(1);
+
+ EXPECT_TRUE(stream->send_stalled_by_flow_control());
+
+ unstall_function.Run(stream, kPostBodyLength);
+
+ EXPECT_FALSE(stream->send_stalled_by_flow_control());
+
+ data.RunFor(3);
+
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose());
+
+ EXPECT_TRUE(delegate.send_headers_completed());
+ EXPECT_EQ("200", delegate.GetResponseHeaderValue(":status"));
+ EXPECT_EQ("HTTP/1.1", delegate.GetResponseHeaderValue(":version"));
+ EXPECT_EQ(std::string(), delegate.TakeReceivedData());
+ EXPECT_TRUE(data.at_write_eof());
+}
+
+TEST_P(SpdyStreamTest, ResumeAfterSendWindowSizeIncreaseRequestResponse) {
+ if (spdy_util_.protocol() < kProtoSPDY3)
+ return;
+
+ RunResumeAfterUnstallRequestResponseTest(
+ base::Bind(&IncreaseStreamSendWindowSize));
+}
+
+TEST_P(SpdyStreamTest, ResumeAfterSendWindowSizeAdjustRequestResponse) {
+ if (spdy_util_.protocol() < kProtoSPDY3)
+ return;
+
+ RunResumeAfterUnstallRequestResponseTest(
+ base::Bind(&AdjustStreamSendWindowSize));
+}
+
+// Given an unstall function, runs a test to make sure that a
+// bidirectional (i.e., non-HTTP-like) stream resumes after a stall
+// and unstall.
+void SpdyStreamTest::RunResumeAfterUnstallBidirectionalTest(
+ const UnstallFunction& unstall_function) {
+ GURL url(kStreamUrl);
+
+ session_ =
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_);
+
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyPost(
+ kStreamUrl, 1, kPostBodyLength, LOWEST, NULL, 0));
+ AddWrite(*req);
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ AddRead(*resp);
+
+ scoped_ptr<SpdyFrame> msg(
+ spdy_util_.ConstructSpdyBodyFrame(1, kPostBody, kPostBodyLength, false));
+ AddWrite(*msg);
+
+ scoped_ptr<SpdyFrame> echo(
+ spdy_util_.ConstructSpdyBodyFrame(1, kPostBody, kPostBodyLength, false));
+ AddRead(*echo);
+
+ AddReadEOF();
+
+ DeterministicSocketData data(GetReads(), GetNumReads(),
+ GetWrites(), GetNumWrites());
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ base::WeakPtr<SpdySession> session(CreateDefaultSpdySession());
+
+ base::WeakPtr<SpdyStream> stream =
+ CreateStreamSynchronously(
+ SPDY_BIDIRECTIONAL_STREAM, session, url, LOWEST, BoundNetLog());
+ ASSERT_TRUE(stream.get() != NULL);
+
+ StreamDelegateSendImmediate delegate(stream, kPostBodyStringPiece);
+ stream->SetDelegate(&delegate);
+
+ EXPECT_FALSE(stream->HasUrlFromHeaders());
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kPostBodyLength));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream->HasUrlFromHeaders());
+ EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec());
+
+ data.RunFor(1);
+
+ EXPECT_FALSE(stream->send_stalled_by_flow_control());
+
+ StallStream(stream);
+
+ data.RunFor(1);
+
+ EXPECT_TRUE(stream->send_stalled_by_flow_control());
+
+ unstall_function.Run(stream, kPostBodyLength);
+
+ EXPECT_FALSE(stream->send_stalled_by_flow_control());
+
+ data.RunFor(3);
+
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose());
+
+ EXPECT_TRUE(delegate.send_headers_completed());
+ EXPECT_EQ("200", delegate.GetResponseHeaderValue(":status"));
+ EXPECT_EQ("HTTP/1.1", delegate.GetResponseHeaderValue(":version"));
+ EXPECT_EQ(std::string(kPostBody, kPostBodyLength),
+ delegate.TakeReceivedData());
+ EXPECT_TRUE(data.at_write_eof());
+}
+
+TEST_P(SpdyStreamTest, ResumeAfterSendWindowSizeIncreaseBidirectional) {
+ if (spdy_util_.protocol() < kProtoSPDY3)
+ return;
+
+ RunResumeAfterUnstallBidirectionalTest(
+ base::Bind(&IncreaseStreamSendWindowSize));
+}
+
+TEST_P(SpdyStreamTest, ResumeAfterSendWindowSizeAdjustBidirectional) {
+ if (spdy_util_.protocol() < kProtoSPDY3)
+ return;
+
+ RunResumeAfterUnstallBidirectionalTest(
+ base::Bind(&AdjustStreamSendWindowSize));
+}
+
+} // namespace
+
+} // namespace test
+
+} // namespace net