blob: 6a56ae35bf6a4cd39ae6cc3ea9b9e134da27344f [file] [log] [blame]
// 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 "net/spdy/spdy_stream.h"
#include <stdint.h>
#include <cstddef>
#include <limits>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "base/strings/string_piece.h"
#include "net/base/completion_callback.h"
#include "net/base/request_priority.h"
#include "net/log/test_net_log.h"
#include "net/log/test_net_log_entry.h"
#include "net/log/test_net_log_util.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_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 {
enum TestCase {
// Test using the SPDY/3.1 protocol.
kTestCaseSPDY31,
// Test using the HTTP/2 protocol, without specifying a stream
// dependency based on the RequestPriority.
kTestCaseHTTP2NoPriorityDependencies,
// Test using the HTTP/2 protocol, specifying a stream
// dependency based on the RequestPriority.
kTestCaseHTTP2PriorityDependencies
};
const char kStreamUrl[] = "http://www.example.org/";
const char kPostBody[] = "\0hello!\xff";
const size_t kPostBodyLength = arraysize(kPostBody);
const base::StringPiece kPostBodyStringPiece(kPostBody, kPostBodyLength);
} // namespace
class SpdyStreamTest : public ::testing::Test,
public ::testing::WithParamInterface<TestCase> {
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_t)>
UnstallFunction;
SpdyStreamTest()
: spdy_util_(GetProtocol(), GetDependenciesFromPriority()),
session_deps_(GetProtocol()),
offset_(0) {
session_deps_.enable_priority_dependencies = GetDependenciesFromPriority();
session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_);
}
~SpdyStreamTest() {
}
base::WeakPtr<SpdySession> CreateDefaultSpdySession() {
SpdySessionKey key(HostPortPair("www.example.org", 80),
ProxyServer::Direct(), PRIVACY_MODE_DISABLED);
return CreateInsecureSpdySession(session_.get(), key, BoundNetLog());
}
void TearDown() override { base::MessageLoop::current()->RunUntilIdle(); }
NextProto GetProtocol() const {
return GetParam() == kTestCaseSPDY31 ? kProtoSPDY31 : kProtoHTTP2;
}
bool GetDependenciesFromPriority() const {
return GetParam() == kTestCaseHTTP2PriorityDependencies;
}
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 SpdySerializedFrame& frame) {
reads_.push_back(CreateMockRead(frame, offset_++));
}
void AddWrite(const SpdySerializedFrame& frame) {
writes_.push_back(CreateMockWrite(frame, offset_++));
}
void AddReadEOF() {
reads_.push_back(MockRead(ASYNC, 0, offset_++));
}
void AddWritePause() {
writes_.push_back(MockWrite(ASYNC, ERR_IO_PENDING, offset_++));
}
void AddReadPause() {
reads_.push_back(MockRead(ASYNC, ERR_IO_PENDING, offset_++));
}
MockRead* GetReads() { return reads_.data(); }
size_t GetNumReads() const {
return reads_.size();
}
MockWrite* GetWrites() { return writes_.data(); }
int GetNumWrites() const {
return writes_.size();
}
void ActivatePushStream(SpdySession* session, SpdyStream* stream) {
std::unique_ptr<SpdyStream> activated =
session->ActivateCreatedStream(stream);
activated->set_stream_id(2);
session->InsertActivatedStream(std::move(activated));
}
SpdyTestUtil spdy_util_;
SpdySessionDependencies session_deps_;
std::unique_ptr<HttpNetworkSession> session_;
private:
// Used by Add{Read,Write}() above.
std::vector<MockWrite> writes_;
std::vector<MockRead> reads_;
int offset_;
};
INSTANTIATE_TEST_CASE_P(ProtoPlusDepend,
SpdyStreamTest,
testing::Values(kTestCaseSPDY31,
kTestCaseHTTP2NoPriorityDependencies,
kTestCaseHTTP2PriorityDependencies));
TEST_P(SpdyStreamTest, SendDataAfterOpen) {
GURL url(kStreamUrl);
std::unique_ptr<SpdySerializedFrame> req(spdy_util_.ConstructSpdyPost(
kStreamUrl, 1, kPostBodyLength, LOWEST, NULL, 0));
AddWrite(*req);
std::unique_ptr<SpdySerializedFrame> resp(
spdy_util_.ConstructSpdyPostSynReply(NULL, 0));
AddRead(*resp);
std::unique_ptr<SpdySerializedFrame> msg(
spdy_util_.ConstructSpdyBodyFrame(1, kPostBody, kPostBodyLength, false));
AddWrite(*msg);
std::unique_ptr<SpdySerializedFrame> echo(
spdy_util_.ConstructSpdyBodyFrame(1, kPostBody, kPostBodyLength, false));
AddRead(*echo);
AddReadEOF();
SequencedSocketData 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());
std::unique_ptr<SpdyHeaderBlock> headers(
spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kPostBodyLength));
EXPECT_EQ(ERR_IO_PENDING,
stream->SendRequestHeaders(std::move(headers), 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(std::string(kPostBody, kPostBodyLength),
delegate.TakeReceivedData());
EXPECT_TRUE(data.AllWriteDataConsumed());
}
// Delegate that receives trailers.
class StreamDelegateWithTrailers : public test::StreamDelegateWithBody {
public:
StreamDelegateWithTrailers(const base::WeakPtr<SpdyStream>& stream,
base::StringPiece data)
: StreamDelegateWithBody(stream, data) {}
~StreamDelegateWithTrailers() override {}
void OnTrailers(const SpdyHeaderBlock& trailers) override {
trailers_ = trailers;
}
const SpdyHeaderBlock& trailers() const { return trailers_; }
private:
SpdyHeaderBlock trailers_;
};
// Regression test for crbug.com/481033.
TEST_P(SpdyStreamTest, Trailers) {
GURL url(kStreamUrl);
std::unique_ptr<SpdySerializedFrame> req(spdy_util_.ConstructSpdyPost(
kStreamUrl, 1, kPostBodyLength, LOWEST, NULL, 0));
AddWrite(*req);
std::unique_ptr<SpdySerializedFrame> msg(
spdy_util_.ConstructSpdyBodyFrame(1, kPostBody, kPostBodyLength, true));
AddWrite(*msg);
std::unique_ptr<SpdySerializedFrame> resp(
spdy_util_.ConstructSpdyPostSynReply(NULL, 0));
AddRead(*resp);
std::unique_ptr<SpdySerializedFrame> echo(
spdy_util_.ConstructSpdyBodyFrame(1, kPostBody, kPostBodyLength, false));
AddRead(*echo);
SpdyHeaderBlock late_headers;
late_headers["foo"] = "bar";
std::unique_ptr<SpdySerializedFrame> trailers(
spdy_util_.ConstructSpdyResponseHeaders(1, late_headers, false));
AddRead(*trailers);
AddReadEOF();
SequencedSocketData 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);
StreamDelegateWithTrailers delegate(stream, kPostBodyStringPiece);
stream->SetDelegate(&delegate);
EXPECT_FALSE(stream->HasUrlFromHeaders());
std::unique_ptr<SpdyHeaderBlock> headers(
spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kPostBodyLength));
EXPECT_EQ(ERR_IO_PENDING,
stream->SendRequestHeaders(std::move(headers), 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()));
const SpdyHeaderBlock& received_trailers = delegate.trailers();
SpdyHeaderBlock::const_iterator it = received_trailers.find("foo");
EXPECT_EQ("bar", it->second);
EXPECT_EQ(std::string(kPostBody, kPostBodyLength),
delegate.TakeReceivedData());
EXPECT_TRUE(data.AllWriteDataConsumed());
}
TEST_P(SpdyStreamTest, PushedStream) {
AddReadEOF();
SequencedSocketData 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.
SpdyStreamRequest stream_request;
int result = stream_request.StartRequest(SPDY_PUSH_STREAM, spdy_session,
GURL(), DEFAULT_PRIORITY,
BoundNetLog(), CompletionCallback());
ASSERT_EQ(OK, result);
base::WeakPtr<SpdyStream> stream = stream_request.ReleaseStream();
ActivatePushStream(spdy_session.get(), stream.get());
EXPECT_FALSE(stream->HasUrlFromHeaders());
// Set required request headers.
SpdyHeaderBlock request_headers;
spdy_util_.AddUrlToHeaderBlock(kStreamUrl, &request_headers);
stream->OnPushPromiseHeadersReceived(request_headers);
base::Time response_time = base::Time::Now();
base::TimeTicks first_byte_time = base::TimeTicks::Now();
// Send some basic response headers.
SpdyHeaderBlock response;
response[spdy_util_.GetStatusKey()] = "200";
response[spdy_util_.GetVersionKey()] = "OK";
stream->OnInitialResponseHeadersReceived(response, response_time,
first_byte_time);
// And some more headers.
// TODO(baranovich): not valid for HTTP 2.
SpdyHeaderBlock headers;
headers["alpha"] = "beta";
stream->OnAdditionalResponseHeadersReceived(headers);
EXPECT_TRUE(stream->HasUrlFromHeaders());
EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec());
StreamDelegateDoNothing delegate(stream->GetWeakPtr());
stream->SetDelegate(&delegate);
LoadTimingInfo load_timing_info;
EXPECT_TRUE(stream->GetLoadTimingInfo(&load_timing_info));
EXPECT_EQ(first_byte_time, load_timing_info.push_start);
EXPECT_TRUE(load_timing_info.push_end.is_null());
stream->OnDataReceived(nullptr);
LoadTimingInfo load_timing_info2;
EXPECT_TRUE(stream->GetLoadTimingInfo(&load_timing_info2));
EXPECT_FALSE(load_timing_info2.push_end.is_null());
base::RunLoop().RunUntilIdle();
EXPECT_EQ("200", delegate.GetResponseHeaderValue(spdy_util_.GetStatusKey()));
EXPECT_EQ("beta", delegate.GetResponseHeaderValue("alpha"));
EXPECT_TRUE(spdy_session == NULL);
}
TEST_P(SpdyStreamTest, StreamError) {
GURL url(kStreamUrl);
std::unique_ptr<SpdySerializedFrame> req(spdy_util_.ConstructSpdyPost(
kStreamUrl, 1, kPostBodyLength, LOWEST, NULL, 0));
AddWrite(*req);
std::unique_ptr<SpdySerializedFrame> resp(
spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
AddRead(*resp);
std::unique_ptr<SpdySerializedFrame> msg(
spdy_util_.ConstructSpdyBodyFrame(1, kPostBody, kPostBodyLength, false));
AddWrite(*msg);
std::unique_ptr<SpdySerializedFrame> echo(
spdy_util_.ConstructSpdyBodyFrame(1, kPostBody, kPostBodyLength, false));
AddRead(*echo);
AddReadEOF();
BoundTestNetLog log;
SequencedSocketData 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());
std::unique_ptr<SpdyHeaderBlock> headers(
spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kPostBodyLength));
EXPECT_EQ(ERR_IO_PENDING,
stream->SendRequestHeaders(std::move(headers), 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(std::string(kPostBody, kPostBodyLength),
delegate.TakeReceivedData());
EXPECT_TRUE(data.AllWriteDataConsumed());
// Check that the NetLog was filled reasonably.
TestNetLogEntry::List entries;
log.GetEntries(&entries);
EXPECT_LT(0u, entries.size());
// Check that we logged SPDY_STREAM_ERROR correctly.
int pos = ExpectLogContainsSomewhere(
entries, 0, NetLog::TYPE_HTTP2_STREAM_ERROR, 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);
std::unique_ptr<SpdySerializedFrame> req(spdy_util_.ConstructSpdyPost(
kStreamUrl, 1, kPostBodyLength, LOWEST, NULL, 0));
AddWrite(*req);
std::string chunk_data(kMaxSpdyFrameChunkSize, 'x');
std::unique_ptr<SpdySerializedFrame> chunk(spdy_util_.ConstructSpdyBodyFrame(
1, chunk_data.data(), chunk_data.length(), false));
AddWrite(*chunk);
AddWrite(*chunk);
std::unique_ptr<SpdySerializedFrame> last_chunk(
spdy_util_.ConstructSpdyBodyFrame(1, chunk_data.data(),
chunk_data.length(), true));
AddWrite(*last_chunk);
std::unique_ptr<SpdySerializedFrame> resp(
spdy_util_.ConstructSpdyPostSynReply(NULL, 0));
AddRead(*resp);
AddReadEOF();
SequencedSocketData 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());
std::unique_ptr<SpdyHeaderBlock> headers(
spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kPostBodyLength));
EXPECT_EQ(ERR_IO_PENDING,
stream->SendRequestHeaders(std::move(headers), 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(std::string(), delegate.TakeReceivedData());
EXPECT_TRUE(data.AllWriteDataConsumed());
}
// 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);
std::unique_ptr<SpdySerializedFrame> req(spdy_util_.ConstructSpdyPost(
kStreamUrl, 1, kPostBodyLength, LOWEST, NULL, 0));
AddWrite(*req);
std::unique_ptr<SpdySerializedFrame> resp(
spdy_util_.ConstructSpdyPostSynReply(NULL, 0));
AddRead(*resp);
std::string chunk_data(kMaxSpdyFrameChunkSize, 'x');
std::unique_ptr<SpdySerializedFrame> chunk(spdy_util_.ConstructSpdyBodyFrame(
1, chunk_data.data(), chunk_data.length(), false));
AddWrite(*chunk);
AddWrite(*chunk);
AddWrite(*chunk);
AddReadEOF();
SequencedSocketData 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());
std::unique_ptr<SpdyHeaderBlock> headers(
spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kPostBodyLength));
EXPECT_EQ(ERR_IO_PENDING,
stream->SendRequestHeaders(std::move(headers), 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(std::string(), delegate.TakeReceivedData());
EXPECT_TRUE(data.AllWriteDataConsumed());
}
// Receiving a header with uppercase ASCII should result in a protocol
// error.
TEST_P(SpdyStreamTest, UpperCaseHeaders) {
GURL url(kStreamUrl);
std::unique_ptr<SpdySerializedFrame> syn(
spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST, true));
AddWrite(*syn);
const char* const kExtraHeaders[] = {"X-UpperCase", "yes"};
std::unique_ptr<SpdySerializedFrame> reply(
spdy_util_.ConstructSpdyGetSynReply(kExtraHeaders, 1, 1));
AddRead(*reply);
std::unique_ptr<SpdySerializedFrame> rst(
spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_PROTOCOL_ERROR));
AddWrite(*rst);
AddReadEOF();
SequencedSocketData 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);
StreamDelegateDoNothing delegate(stream);
stream->SetDelegate(&delegate);
EXPECT_FALSE(stream->HasUrlFromHeaders());
std::unique_ptr<SpdyHeaderBlock> headers(
spdy_util_.ConstructGetHeaderBlock(kStreamUrl));
EXPECT_EQ(ERR_IO_PENDING, stream->SendRequestHeaders(std::move(headers),
NO_MORE_DATA_TO_SEND));
EXPECT_TRUE(stream->HasUrlFromHeaders());
EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec());
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);
std::unique_ptr<SpdySerializedFrame> syn(
spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST, true));
AddWrite(*syn);
std::unique_ptr<SpdySerializedFrame> reply(
spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
AddRead(*reply);
const char* const extra_headers[] = {"X-UpperCase", "yes"};
std::unique_ptr<SpdySerializedFrame> push(
spdy_util_.ConstructSpdyPush(extra_headers, 1, 2, 1, kStreamUrl));
AddRead(*push);
std::unique_ptr<SpdySerializedFrame> rst(
spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_PROTOCOL_ERROR));
AddWrite(*rst);
AddReadPause();
AddReadEOF();
SequencedSocketData 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);
StreamDelegateDoNothing delegate(stream);
stream->SetDelegate(&delegate);
EXPECT_FALSE(stream->HasUrlFromHeaders());
std::unique_ptr<SpdyHeaderBlock> headers(
spdy_util_.ConstructGetHeaderBlock(kStreamUrl));
EXPECT_EQ(ERR_IO_PENDING, stream->SendRequestHeaders(std::move(headers),
NO_MORE_DATA_TO_SEND));
EXPECT_TRUE(stream->HasUrlFromHeaders());
EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec());
data.RunUntilPaused();
base::WeakPtr<SpdyStream> push_stream;
EXPECT_EQ(OK, session->GetPushStream(url, &push_stream, BoundNetLog()));
EXPECT_FALSE(push_stream);
data.Resume();
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);
std::unique_ptr<SpdySerializedFrame> syn(
spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST, true));
AddWrite(*syn);
std::unique_ptr<SpdySerializedFrame> reply(
spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
AddRead(*reply);
std::unique_ptr<SpdySerializedFrame> push(
spdy_util_.ConstructSpdyPush(NULL, 0, 2, 1, kStreamUrl));
AddRead(*push);
AddReadPause();
SpdyHeaderBlock late_headers;
late_headers["X-UpperCase"] = "yes";
std::unique_ptr<SpdySerializedFrame> headers_frame(
spdy_util_.ConstructSpdyReply(2, late_headers));
AddRead(*headers_frame);
AddWritePause();
std::unique_ptr<SpdySerializedFrame> rst(
spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_PROTOCOL_ERROR));
AddWrite(*rst);
AddReadEOF();
SequencedSocketData 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);
StreamDelegateDoNothing delegate(stream);
stream->SetDelegate(&delegate);
EXPECT_FALSE(stream->HasUrlFromHeaders());
std::unique_ptr<SpdyHeaderBlock> headers(
spdy_util_.ConstructGetHeaderBlock(kStreamUrl));
EXPECT_EQ(ERR_IO_PENDING, stream->SendRequestHeaders(std::move(headers),
NO_MORE_DATA_TO_SEND));
EXPECT_TRUE(stream->HasUrlFromHeaders());
EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec());
data.RunUntilPaused();
base::WeakPtr<SpdyStream> push_stream;
EXPECT_EQ(OK, session->GetPushStream(url, &push_stream, BoundNetLog()));
EXPECT_TRUE(push_stream);
data.Resume();
data.RunUntilPaused();
EXPECT_EQ(OK, session->GetPushStream(url, &push_stream, BoundNetLog()));
EXPECT_FALSE(push_stream);
data.Resume();
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);
std::unique_ptr<SpdySerializedFrame> syn(
spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST, true));
AddWrite(*syn);
std::unique_ptr<SpdySerializedFrame> reply(
spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
AddRead(*reply);
std::unique_ptr<SpdySerializedFrame> push(
spdy_util_.ConstructSpdyPush(NULL, 0, 2, 1, kStreamUrl));
AddRead(*push);
AddReadPause();
SpdyHeaderBlock late_headers;
late_headers[spdy_util_.GetStatusKey()] = "500 Server Error";
std::unique_ptr<SpdySerializedFrame> headers_frame(
spdy_util_.ConstructSpdyReply(2, late_headers));
AddRead(*headers_frame);
AddReadPause();
std::unique_ptr<SpdySerializedFrame> rst(
spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_PROTOCOL_ERROR));
AddWrite(*rst);
AddReadEOF();
SequencedSocketData 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);
StreamDelegateDoNothing delegate(stream);
stream->SetDelegate(&delegate);
EXPECT_FALSE(stream->HasUrlFromHeaders());
std::unique_ptr<SpdyHeaderBlock> headers(
spdy_util_.ConstructGetHeaderBlock(kStreamUrl));
EXPECT_EQ(ERR_IO_PENDING, stream->SendRequestHeaders(std::move(headers),
NO_MORE_DATA_TO_SEND));
EXPECT_TRUE(stream->HasUrlFromHeaders());
EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec());
data.RunUntilPaused();
base::WeakPtr<SpdyStream> push_stream;
EXPECT_EQ(OK, session->GetPushStream(url, &push_stream, BoundNetLog()));
EXPECT_TRUE(push_stream);
data.Resume();
data.RunUntilPaused();
EXPECT_EQ(OK, session->GetPushStream(url, &push_stream, BoundNetLog()));
EXPECT_FALSE(push_stream);
data.Resume();
EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose());
}
// Call IncreaseSendWindowSize on a stream with a large enough delta
// to overflow an int32_t. The SpdyStream should handle that case
// gracefully.
TEST_P(SpdyStreamTest, IncreaseSendWindowSizeOverflow) {
std::unique_ptr<SpdySerializedFrame> req(spdy_util_.ConstructSpdyPost(
kStreamUrl, 1, kPostBodyLength, LOWEST, NULL, 0));
AddWrite(*req);
AddReadPause();
// Triggered by the overflowing call to IncreaseSendWindowSize
// below.
std::unique_ptr<SpdySerializedFrame> rst(
spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_FLOW_CONTROL_ERROR));
AddWrite(*rst);
AddReadEOF();
BoundTestNetLog log;
SequencedSocketData 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());
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);
std::unique_ptr<SpdyHeaderBlock> headers(
spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kPostBodyLength));
EXPECT_EQ(ERR_IO_PENDING,
stream->SendRequestHeaders(std::move(headers), MORE_DATA_TO_SEND));
EXPECT_TRUE(stream->HasUrlFromHeaders());
EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec());
data.RunUntilPaused();
int32_t old_send_window_size = stream->send_window_size();
ASSERT_GT(old_send_window_size, 0);
int32_t delta_window_size =
std::numeric_limits<int32_t>::max() - old_send_window_size + 1;
stream->IncreaseSendWindowSize(delta_window_size);
EXPECT_EQ(NULL, stream.get());
data.Resume();
base::RunLoop().RunUntilIdle();
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_t 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_t 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);
std::unique_ptr<SpdySerializedFrame> req(spdy_util_.ConstructSpdyPost(
kStreamUrl, 1, kPostBodyLength, LOWEST, NULL, 0));
AddWrite(*req);
std::unique_ptr<SpdySerializedFrame> body(
spdy_util_.ConstructSpdyBodyFrame(1, kPostBody, kPostBodyLength, true));
AddWrite(*body);
std::unique_ptr<SpdySerializedFrame> resp(
spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
AddRead(*resp);
AddReadEOF();
SequencedSocketData 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);
StreamDelegateWithBody delegate(stream, kPostBodyStringPiece);
stream->SetDelegate(&delegate);
EXPECT_FALSE(stream->HasUrlFromHeaders());
EXPECT_FALSE(stream->send_stalled_by_flow_control());
std::unique_ptr<SpdyHeaderBlock> headers(
spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kPostBodyLength));
EXPECT_EQ(ERR_IO_PENDING,
stream->SendRequestHeaders(std::move(headers), MORE_DATA_TO_SEND));
EXPECT_TRUE(stream->HasUrlFromHeaders());
EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec());
StallStream(stream);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(stream->send_stalled_by_flow_control());
unstall_function.Run(stream, kPostBodyLength);
EXPECT_FALSE(stream->send_stalled_by_flow_control());
EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose());
EXPECT_TRUE(delegate.send_headers_completed());
EXPECT_EQ("200", delegate.GetResponseHeaderValue(":status"));
EXPECT_EQ(std::string(), delegate.TakeReceivedData());
EXPECT_TRUE(data.AllWriteDataConsumed());
}
TEST_P(SpdyStreamTest, ResumeAfterSendWindowSizeIncreaseRequestResponse) {
RunResumeAfterUnstallRequestResponseTest(
base::Bind(&IncreaseStreamSendWindowSize));
}
TEST_P(SpdyStreamTest, ResumeAfterSendWindowSizeAdjustRequestResponse) {
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);
std::unique_ptr<SpdySerializedFrame> req(spdy_util_.ConstructSpdyPost(
kStreamUrl, 1, kPostBodyLength, LOWEST, NULL, 0));
AddWrite(*req);
AddReadPause();
std::unique_ptr<SpdySerializedFrame> resp(
spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
AddRead(*resp);
std::unique_ptr<SpdySerializedFrame> msg(
spdy_util_.ConstructSpdyBodyFrame(1, kPostBody, kPostBodyLength, false));
AddWrite(*msg);
std::unique_ptr<SpdySerializedFrame> echo(
spdy_util_.ConstructSpdyBodyFrame(1, kPostBody, kPostBodyLength, false));
AddRead(*echo);
AddReadEOF();
SequencedSocketData 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());
std::unique_ptr<SpdyHeaderBlock> headers(
spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kPostBodyLength));
EXPECT_EQ(ERR_IO_PENDING,
stream->SendRequestHeaders(std::move(headers), MORE_DATA_TO_SEND));
EXPECT_TRUE(stream->HasUrlFromHeaders());
EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec());
data.RunUntilPaused();
EXPECT_FALSE(stream->send_stalled_by_flow_control());
StallStream(stream);
data.Resume();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(stream->send_stalled_by_flow_control());
unstall_function.Run(stream, kPostBodyLength);
EXPECT_FALSE(stream->send_stalled_by_flow_control());
EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose());
EXPECT_TRUE(delegate.send_headers_completed());
EXPECT_EQ("200", delegate.GetResponseHeaderValue(":status"));
EXPECT_EQ(std::string(kPostBody, kPostBodyLength),
delegate.TakeReceivedData());
EXPECT_TRUE(data.AllWriteDataConsumed());
}
TEST_P(SpdyStreamTest, ResumeAfterSendWindowSizeIncreaseBidirectional) {
RunResumeAfterUnstallBidirectionalTest(
base::Bind(&IncreaseStreamSendWindowSize));
}
TEST_P(SpdyStreamTest, ResumeAfterSendWindowSizeAdjustBidirectional) {
RunResumeAfterUnstallBidirectionalTest(
base::Bind(&AdjustStreamSendWindowSize));
}
// Test calculation of amount of bytes received from network.
TEST_P(SpdyStreamTest, ReceivedBytes) {
GURL url(kStreamUrl);
std::unique_ptr<SpdySerializedFrame> syn(
spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST, true));
AddWrite(*syn);
AddReadPause();
std::unique_ptr<SpdySerializedFrame> reply(
spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
AddRead(*reply);
AddReadPause();
std::unique_ptr<SpdySerializedFrame> msg(
spdy_util_.ConstructSpdyBodyFrame(1, kPostBody, kPostBodyLength, false));
AddRead(*msg);
AddReadPause();
AddReadEOF();
SequencedSocketData 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);
StreamDelegateDoNothing delegate(stream);
stream->SetDelegate(&delegate);
EXPECT_FALSE(stream->HasUrlFromHeaders());
std::unique_ptr<SpdyHeaderBlock> headers(
spdy_util_.ConstructGetHeaderBlock(kStreamUrl));
EXPECT_EQ(ERR_IO_PENDING, stream->SendRequestHeaders(std::move(headers),
NO_MORE_DATA_TO_SEND));
EXPECT_TRUE(stream->HasUrlFromHeaders());
EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec());
int64_t reply_frame_len = reply->size();
int64_t data_header_len = SpdyConstants::GetDataFrameMinimumSize(
NextProtoToSpdyMajorVersion(GetProtocol()));
int64_t data_frame_len = data_header_len + kPostBodyLength;
int64_t response_len = reply_frame_len + data_frame_len;
EXPECT_EQ(0, stream->raw_received_bytes());
// SYN
data.RunUntilPaused();
EXPECT_EQ(0, stream->raw_received_bytes());
// REPLY
data.Resume();
data.RunUntilPaused();
EXPECT_EQ(reply_frame_len, stream->raw_received_bytes());
// DATA
data.Resume();
data.RunUntilPaused();
EXPECT_EQ(response_len, stream->raw_received_bytes());
// FIN
data.Resume();
EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose());
}
} // namespace test
} // namespace net