summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrtenneti@chromium.org <rtenneti@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-12-18 22:21:08 +0000
committerrtenneti@chromium.org <rtenneti@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-12-18 22:21:08 +0000
commit4d640797720f35d3fab791863c2d2cd9e1ce1e68 (patch)
treeb8b9c9b76afda5479a2f0a6fd2562ff7c8965aa0
parent91162f3da2ef9c114f10d5cbb175704573145ab3 (diff)
downloadchromium_src-4d640797720f35d3fab791863c2d2cd9e1ce1e68.zip
chromium_src-4d640797720f35d3fab791863c2d2cd9e1ce1e68.tar.gz
chromium_src-4d640797720f35d3fab791863c2d2cd9e1ce1e68.tar.bz2
Implement a QuicHeadersStream to handle reliable in-order delivery of
headers. Changes the way headers are delivered in QUIC from being the first bytes on a stream, to being delivered as SPDY SYN_STREAM/SYN_REPLY frames on a dedicated headers stream. This also creates QUIC_VERSION_13. Since the intra-stream serialization format changes, it is not possible for a client to handle a version negotiation across this boundary. Merge internal change: 58313427 R=rch@chromium.org Review URL: https://codereview.chromium.org/116513003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@241682 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--net/net.gyp3
-rw-r--r--net/quic/quic_client_session_test.cc16
-rw-r--r--net/quic/quic_connection.h5
-rw-r--r--net/quic/quic_data_stream.cc58
-rw-r--r--net/quic/quic_data_stream.h28
-rw-r--r--net/quic/quic_data_stream_test.cc333
-rw-r--r--net/quic/quic_framer.h5
-rw-r--r--net/quic/quic_headers_stream.cc261
-rw-r--r--net/quic/quic_headers_stream.h81
-rw-r--r--net/quic/quic_headers_stream_test.cc329
-rw-r--r--net/quic/quic_protocol.cc3
-rw-r--r--net/quic/quic_protocol.h12
-rw-r--r--net/quic/quic_session.cc97
-rw-r--r--net/quic/quic_session.h24
-rw-r--r--net/quic/quic_session_test.cc165
-rw-r--r--net/quic/quic_stream_sequencer.cc20
-rw-r--r--net/quic/quic_stream_sequencer.h4
-rw-r--r--net/quic/quic_stream_sequencer_test.cc31
-rw-r--r--net/quic/quic_utils.cc1
-rw-r--r--net/quic/test_tools/crypto_test_utils.cc5
-rw-r--r--net/quic/test_tools/quic_connection_peer.cc5
-rw-r--r--net/quic/test_tools/quic_connection_peer.h1
-rw-r--r--net/quic/test_tools/quic_session_peer.cc5
-rw-r--r--net/quic/test_tools/quic_session_peer.h3
-rw-r--r--net/quic/test_tools/quic_test_utils.cc23
-rw-r--r--net/quic/test_tools/quic_test_utils.h23
-rw-r--r--net/quic/test_tools/simple_quic_framer.cc4
-rw-r--r--net/quic/test_tools/simple_quic_framer.h1
-rw-r--r--net/spdy/spdy_protocol.h25
-rw-r--r--net/tools/quic/end_to_end_test.cc14
-rw-r--r--net/tools/quic/quic_client.h7
-rw-r--r--net/tools/quic/quic_client_session_test.cc16
-rw-r--r--net/tools/quic/quic_server_session_test.cc111
-rw-r--r--net/tools/quic/quic_spdy_client_stream.cc42
-rw-r--r--net/tools/quic/quic_spdy_client_stream.h9
-rw-r--r--net/tools/quic/quic_spdy_client_stream_test.cc24
-rw-r--r--net/tools/quic/quic_spdy_server_stream.cc8
-rw-r--r--net/tools/quic/quic_spdy_server_stream_test.cc106
-rw-r--r--net/tools/quic/quic_time_wait_list_manager.cc9
-rw-r--r--net/tools/quic/test_tools/quic_test_client.cc5
-rw-r--r--net/tools/quic/test_tools/quic_test_utils.cc11
-rw-r--r--net/tools/quic/test_tools/quic_test_utils.h9
42 files changed, 1607 insertions, 335 deletions
diff --git a/net/net.gyp b/net/net.gyp
index 974c395..bb383a8 100644
--- a/net/net.gyp
+++ b/net/net.gyp
@@ -874,6 +874,8 @@
'quic/quic_fec_group.h',
'quic/quic_framer.cc',
'quic/quic_framer.h',
+ 'quic/quic_headers_stream.cc',
+ 'quic/quic_headers_stream.h',
'quic/quic_http_stream.cc',
'quic/quic_http_stream.h',
'quic/quic_http_utils.cc',
@@ -1866,6 +1868,7 @@
'quic/quic_data_writer_test.cc',
'quic/quic_fec_group_test.cc',
'quic/quic_framer_test.cc',
+ 'quic/quic_headers_stream_test.cc',
'quic/quic_http_stream_test.cc',
'quic/quic_http_utils_test.cc',
'quic/quic_network_transaction_unittest.cc',
diff --git a/net/quic/quic_client_session_test.cc b/net/quic/quic_client_session_test.cc
index f0b4dd4..2606db7 100644
--- a/net/quic/quic_client_session_test.cc
+++ b/net/quic/quic_client_session_test.cc
@@ -61,11 +61,12 @@ class TestPacketWriter : public QuicDefaultPacketWriter {
QuicPacketHeader header_;
};
-class QuicClientSessionTest : public ::testing::Test {
+class QuicClientSessionTest : public ::testing::TestWithParam<QuicVersion> {
protected:
QuicClientSessionTest()
: writer_(new TestPacketWriter()),
- connection_(new PacketSavingConnection(false)),
+ connection_(new PacketSavingConnection(false,
+ SupportedVersions(GetParam()))),
session_(connection_, GetSocket().Pass(), writer_.Pass(), NULL, NULL,
kServerHostname, DefaultQuicConfig(), &crypto_config_,
&net_log_) {
@@ -105,11 +106,14 @@ class QuicClientSessionTest : public ::testing::Test {
QuicCryptoClientConfig crypto_config_;
};
-TEST_F(QuicClientSessionTest, CryptoConnect) {
+INSTANTIATE_TEST_CASE_P(Tests, QuicClientSessionTest,
+ ::testing::ValuesIn(QuicSupportedVersions()));
+
+TEST_P(QuicClientSessionTest, CryptoConnect) {
CompleteCryptoHandshake();
}
-TEST_F(QuicClientSessionTest, MaxNumStreams) {
+TEST_P(QuicClientSessionTest, MaxNumStreams) {
CompleteCryptoHandshake();
std::vector<QuicReliableClientStream*> streams;
@@ -125,7 +129,7 @@ TEST_F(QuicClientSessionTest, MaxNumStreams) {
EXPECT_TRUE(session_.CreateOutgoingDataStream());
}
-TEST_F(QuicClientSessionTest, MaxNumStreamsViaRequest) {
+TEST_P(QuicClientSessionTest, MaxNumStreamsViaRequest) {
CompleteCryptoHandshake();
std::vector<QuicReliableClientStream*> streams;
@@ -149,7 +153,7 @@ TEST_F(QuicClientSessionTest, MaxNumStreamsViaRequest) {
EXPECT_TRUE(stream != NULL);
}
-TEST_F(QuicClientSessionTest, GoAwayReceived) {
+TEST_P(QuicClientSessionTest, GoAwayReceived) {
CompleteCryptoHandshake();
// After receiving a GoAway, I should no longer be able to create outgoing
diff --git a/net/quic/quic_connection.h b/net/quic/quic_connection.h
index b7cf2d2..5fefd6e 100644
--- a/net/quic/quic_connection.h
+++ b/net/quic/quic_connection.h
@@ -712,11 +712,6 @@ class NET_EXPORT_PRIVATE QuicConnection
// This is checked later on validating a data or version negotiation packet.
bool address_migrating_;
- // An AckNotifier can register to be informed when ACKs have been received for
- // all packets that a given block of data was sent in. The AckNotifierManager
- // maintains the currently active notifiers.
- AckNotifierManager ack_notifier_manager_;
-
// If non-empty this contains the set of versions received in a
// version negotiation packet.
QuicVersionVector server_supported_versions_;
diff --git a/net/quic/quic_data_stream.cc b/net/quic/quic_data_stream.cc
index 3c992a7..0e450f1 100644
--- a/net/quic/quic_data_stream.cc
+++ b/net/quic/quic_data_stream.cc
@@ -56,11 +56,25 @@ QuicDataStream::QuicDataStream(QuicStreamId id,
decompression_failed_(false),
priority_parsed_(false) {
DCHECK_NE(kCryptoStreamId, id);
+ if (version() > QUIC_VERSION_12) {
+ // Don't receive any callbacks from the sequencer until headers
+ // are complete.
+ sequencer()->SetBlockedUntilFlush();
+ }
}
QuicDataStream::~QuicDataStream() {
}
+size_t QuicDataStream::WriteHeaders(const SpdyHeaderBlock& header_block,
+ bool fin) {
+ size_t bytes_written = session()->WriteHeaders(id(), header_block, fin);
+ if (fin) {
+ CloseWriteSide();
+ }
+ return bytes_written;
+}
+
size_t QuicDataStream::Readv(const struct iovec* iov, size_t iov_len) {
if (FinishedReadingHeaders()) {
// If the headers have been read, simply delegate to the sequencer's
@@ -81,6 +95,9 @@ size_t QuicDataStream::Readv(const struct iovec* iov, size_t iov_len) {
++iov_index;
}
decompressed_headers_.erase(0, bytes_consumed);
+ if (FinishedReadingHeaders()) {
+ sequencer()->FlushBufferedFrames();
+ }
return bytes_consumed;
}
@@ -118,6 +135,18 @@ QuicPriority QuicDataStream::EffectivePriority() const {
}
uint32 QuicDataStream::ProcessRawData(const char* data, uint32 data_len) {
+ if (version() <= QUIC_VERSION_12) {
+ return ProcessRawData12(data, data_len);
+ }
+
+ if (!FinishedReadingHeaders()) {
+ LOG(DFATAL) << "ProcessRawData called before headers have been finished";
+ return 0;
+ }
+ return ProcessData(data, data_len);
+}
+
+uint32 QuicDataStream::ProcessRawData12(const char* data, uint32 data_len) {
DCHECK_NE(0u, data_len);
uint32 total_bytes_consumed = 0;
@@ -237,6 +266,7 @@ uint32 QuicDataStream::ProcessHeaderData() {
}
void QuicDataStream::OnDecompressorAvailable() {
+ DCHECK_LE(QUIC_VERSION_12, version());
DCHECK_EQ(headers_id_,
session()->decompressor()->current_header_id());
DCHECK(!headers_decompressed_);
@@ -273,16 +303,41 @@ void QuicDataStream::OnDecompressorAvailable() {
}
bool QuicDataStream::OnDecompressedData(StringPiece data) {
+ DCHECK_GE(QUIC_VERSION_12, version());
data.AppendToString(&decompressed_headers_);
return true;
}
void QuicDataStream::OnDecompressionError() {
+ DCHECK_LE(QUIC_VERSION_12, version());
DCHECK(!decompression_failed_);
decompression_failed_ = true;
session()->connection()->SendConnectionClose(QUIC_DECOMPRESSION_FAILURE);
}
+void QuicDataStream::OnStreamHeaders(StringPiece headers_data) {
+ DCHECK_LT(QUIC_VERSION_12, version());
+ headers_data.AppendToString(&decompressed_headers_);
+ ProcessHeaderData();
+}
+
+void QuicDataStream::OnStreamHeadersPriority(QuicPriority priority) {
+ DCHECK(session()->connection()->is_server());
+ set_priority(priority);
+}
+
+void QuicDataStream::OnStreamHeadersComplete(bool fin, size_t frame_len) {
+ DCHECK_LT(QUIC_VERSION_12, version());
+ headers_decompressed_ = true;
+ if (fin) {
+ sequencer()->OnStreamFrame(QuicStreamFrame(id(), fin, 0, IOVector()));
+ }
+ ProcessHeaderData();
+ if (FinishedReadingHeaders()) {
+ sequencer()->FlushBufferedFrames();
+ }
+}
+
void QuicDataStream::OnClose() {
ReliableQuicStream::OnClose();
@@ -327,7 +382,8 @@ uint32 QuicDataStream::StripPriorityAndHeaderId(
}
bool QuicDataStream::FinishedReadingHeaders() {
- return headers_decompressed_ && decompressed_headers_.empty();
+ return (headers_id_ != 0 || version() > QUIC_VERSION_12) &&
+ headers_decompressed_ && decompressed_headers_.empty();
}
} // namespace net
diff --git a/net/quic/quic_data_stream.h b/net/quic/quic_data_stream.h
index 87a5230..9e8c10e 100644
--- a/net/quic/quic_data_stream.h
+++ b/net/quic/quic_data_stream.h
@@ -69,8 +69,33 @@ class NET_EXPORT_PRIVATE QuicDataStream : public ReliableQuicStream,
virtual bool OnDecompressedData(base::StringPiece data) OVERRIDE;
virtual void OnDecompressionError() OVERRIDE;
+ // Overridden by subclasses to process data. For QUIC_VERSION_12 or less,
+ // data will be delivered in order, first the decompressed headers, then
+ // the body. For later QUIC versions, the headers will be delivered via
+ // OnStreamHeaders, and only the data will be delivered through this method.
virtual uint32 ProcessData(const char* data, uint32 data_len) = 0;
+ // Called by the session when decompressed headers data is received
+ // for this stream. Only called for versions greater than QUIC_VERSION_12.
+ // May be called multiple times, with each call providing additional headers
+ // data until OnStreamHeadersComplete is called.
+ virtual void OnStreamHeaders(base::StringPiece headers_data);
+
+ // Called by the session when headers with a priority have been received
+ // for this stream. This method will only be called for server streams.
+ virtual void OnStreamHeadersPriority(QuicPriority priority);
+
+ // Called by the session when decompressed headers have been completely
+ // delilvered to this stream. If |fin| is true, then this stream
+ // should be closed; no more data will be sent by the peer.
+ // Only called for versions greater than QUIC_VERSION_12.
+ virtual void OnStreamHeadersComplete(bool fin, size_t frame_len);
+
+ // Writes the headers contained in |header_block| to the dedicated
+ // headers stream.
+ virtual size_t WriteHeaders(const SpdyHeaderBlock& header_block,
+ bool fin);
+
// This block of functions wraps the sequencer's functions of the same
// name. These methods return uncompressed data until that has
// been fully processed. Then they simply delegate to the sequencer.
@@ -108,6 +133,9 @@ class NET_EXPORT_PRIVATE QuicDataStream : public ReliableQuicStream,
friend class test::ReliableQuicStreamPeer;
friend class QuicStreamUtils;
+ // Processes raw stream data for QUIC_VERSION_12 and earlier.
+ uint32 ProcessRawData12(const char* data, uint32 data_len);
+
uint32 ProcessHeaderData();
uint32 StripPriorityAndHeaderId(const char* data, uint32 data_len);
diff --git a/net/quic/quic_data_stream_test.cc b/net/quic/quic_data_stream_test.cc
index 551ef44..25144f8 100644
--- a/net/quic/quic_data_stream_test.cc
+++ b/net/quic/quic_data_stream_test.cc
@@ -57,7 +57,7 @@ class TestStream : public QuicDataStream {
string data_;
};
-class QuicDataStreamTest : public ::testing::TestWithParam<bool> {
+class QuicDataStreamTest : public ::testing::TestWithParam<QuicVersion> {
public:
QuicDataStreamTest() {
headers_[":host"] = "www.google.com";
@@ -90,8 +90,9 @@ class QuicDataStreamTest : public ::testing::TestWithParam<bool> {
}
void Initialize(bool stream_should_process_data) {
- connection_ = new StrictMock<MockConnection>(kIsServer);
- session_.reset(new StrictMock<MockSession>(connection_));
+ connection_ = new testing::StrictMock<MockConnection>(
+ kIsServer, SupportedVersions(GetParam()));
+ session_.reset(new testing::StrictMock<MockSession>(connection_));
stream_.reset(new TestStream(kStreamId, session_.get(),
stream_should_process_data));
stream2_.reset(new TestStream(kStreamId + 2, session_.get(),
@@ -102,6 +103,14 @@ class QuicDataStreamTest : public ::testing::TestWithParam<bool> {
QuicSessionPeer::GetWriteblockedStreams(session_.get());
}
+ string CompressHeaders(QuicPriority priority) {
+ return compressor_->CompressHeadersWithPriority(priority, headers_);
+ }
+
+ size_t CompressedHeadersSize() {
+ return CompressHeaders(QuicUtils::HighestPriority()).size();
+ }
+
protected:
MockConnection* connection_;
scoped_ptr<MockSession> session_;
@@ -113,23 +122,38 @@ class QuicDataStreamTest : public ::testing::TestWithParam<bool> {
WriteBlockedList<QuicStreamId>* write_blocked_list_;
};
-TEST_F(QuicDataStreamTest, ProcessHeaders) {
- Initialize(kShouldProcessData);
+INSTANTIATE_TEST_CASE_P(Tests, QuicDataStreamTest,
+ ::testing::ValuesIn(QuicSupportedVersions()));
- string compressed_headers = compressor_->CompressHeadersWithPriority(
- QuicUtils::HighestPriority(), headers_);
- QuicStreamFrame frame(kStreamId, false, 0, MakeIOVector(compressed_headers));
+TEST_P(QuicDataStreamTest, ProcessHeaders) {
+ Initialize(kShouldProcessData);
- stream_->OnStreamFrame(frame);
- EXPECT_EQ(SpdyUtils::SerializeUncompressedHeaders(headers_), stream_->data());
+ string headers = SpdyUtils::SerializeUncompressedHeaders(headers_);
+ if (GetParam() > QUIC_VERSION_12) {
+ stream_->OnStreamHeadersPriority(QuicUtils::HighestPriority());
+ stream_->OnStreamHeaders(headers);
+ EXPECT_EQ(headers, stream_->data());
+ stream_->OnStreamHeadersComplete(false, CompressedHeadersSize());
+ } else {
+ string compressed_headers = CompressHeaders(QuicUtils::HighestPriority());
+ QuicStreamFrame frame(kStreamId, false, 0,
+ MakeIOVector(compressed_headers));
+
+ stream_->OnStreamFrame(frame);
+ }
EXPECT_EQ(QuicUtils::HighestPriority(), stream_->EffectivePriority());
+ EXPECT_EQ(headers, stream_->data());
+ EXPECT_FALSE(stream_->IsDoneReading());
}
-TEST_F(QuicDataStreamTest, ProcessHeadersWithInvalidHeaderId) {
+TEST_P(QuicDataStreamTest, ProcessHeadersWithInvalidHeaderId) {
+ if (GetParam() > QUIC_VERSION_12) {
+ // Header ID is v12 specific.
+ return;
+ }
Initialize(kShouldProcessData);
- string compressed_headers = compressor_->CompressHeadersWithPriority(
- QuicUtils::HighestPriority(), headers_);
+ string compressed_headers = CompressHeaders(QuicUtils::HighestPriority());
compressed_headers[4] = '\xFF'; // Illegal header id.
QuicStreamFrame frame(kStreamId, false, 0, MakeIOVector(compressed_headers));
@@ -137,11 +161,14 @@ TEST_F(QuicDataStreamTest, ProcessHeadersWithInvalidHeaderId) {
stream_->OnStreamFrame(frame);
}
-TEST_F(QuicDataStreamTest, ProcessHeadersWithInvalidPriority) {
+TEST_P(QuicDataStreamTest, ProcessHeadersWithInvalidPriority) {
+ if (GetParam() > QUIC_VERSION_12) {
+ // Invalid priority is handled in QuicHeadersStream.
+ return;
+ }
Initialize(kShouldProcessData);
- string compressed_headers = compressor_->CompressHeadersWithPriority(
- QuicUtils::HighestPriority(), headers_);
+ string compressed_headers = CompressHeaders(QuicUtils::HighestPriority());
compressed_headers[0] = '\xFF'; // Illegal priority.
QuicStreamFrame frame(kStreamId, false, 0, MakeIOVector(compressed_headers));
@@ -149,130 +176,212 @@ TEST_F(QuicDataStreamTest, ProcessHeadersWithInvalidPriority) {
stream_->OnStreamFrame(frame);
}
-TEST_F(QuicDataStreamTest, ProcessHeadersAndBody) {
+TEST_P(QuicDataStreamTest, ProcessHeadersAndBody) {
Initialize(kShouldProcessData);
- string compressed_headers = compressor_->CompressHeadersWithPriority(
- QuicUtils::HighestPriority(), headers_);
+ string headers = SpdyUtils::SerializeUncompressedHeaders(headers_);
string body = "this is the body";
- string data = compressed_headers + body;
- QuicStreamFrame frame(kStreamId, false, 0, MakeIOVector(data));
- stream_->OnStreamFrame(frame);
- EXPECT_EQ(SpdyUtils::SerializeUncompressedHeaders(headers_) + body,
- stream_->data());
-}
+ if (GetParam() > QUIC_VERSION_12) {
+ stream_->OnStreamHeaders(headers);
+ EXPECT_EQ(headers, stream_->data());
+ stream_->OnStreamHeadersComplete(false, CompressedHeadersSize());
+ QuicStreamFrame frame(kStreamId, false, 0, MakeIOVector(body));
+ stream_->OnStreamFrame(frame);
+ } else {
+ string compressed_headers = CompressHeaders(QuicUtils::HighestPriority());
+ string data = compressed_headers + body;
+ QuicStreamFrame frame(kStreamId, false, 0, MakeIOVector(data));
+ stream_->OnStreamFrame(frame);
+ }
-TEST_F(QuicDataStreamTest, ProcessHeadersAndBodyFragments) {
- Initialize(kShouldProcessData);
+ EXPECT_EQ(headers + body, stream_->data());
+}
- string compressed_headers = compressor_->CompressHeadersWithPriority(
- QuicUtils::LowestPriority(), headers_);
+TEST_P(QuicDataStreamTest, ProcessHeadersAndBodyFragments) {
+ string headers = SpdyUtils::SerializeUncompressedHeaders(headers_);
string body = "this is the body";
- string data = compressed_headers + body;
- for (size_t fragment_size = 1; fragment_size < data.size(); ++fragment_size) {
+ if (GetParam() > QUIC_VERSION_12) {
+ for (size_t fragment_size = 1; fragment_size < body.size();
+ ++fragment_size) {
+ Initialize(kShouldProcessData);
+ for (size_t offset = 0; offset < headers.size();
+ offset += fragment_size) {
+ size_t remaining_data = headers.size() - offset;
+ StringPiece fragment(headers.data() + offset,
+ min(fragment_size, remaining_data));
+ stream_->OnStreamHeaders(fragment);
+ }
+ stream_->OnStreamHeadersComplete(false, CompressedHeadersSize());
+ for (size_t offset = 0; offset < body.size(); offset += fragment_size) {
+ size_t remaining_data = body.size() - offset;
+ StringPiece fragment(body.data() + offset,
+ min(fragment_size, remaining_data));
+ QuicStreamFrame frame(kStreamId, false, offset, MakeIOVector(fragment));
+ stream_->OnStreamFrame(frame);
+ }
+ ASSERT_EQ(headers + body,
+ stream_->data()) << "fragment_size: " << fragment_size;
+ }
+ } else {
Initialize(kShouldProcessData);
- for (size_t offset = 0; offset < data.size(); offset += fragment_size) {
- size_t remaining_data = data.length() - offset;
- StringPiece fragment(data.data() + offset,
- min(fragment_size, remaining_data));
- QuicStreamFrame frame(kStreamId, false, offset, MakeIOVector(fragment));
-
- stream_->OnStreamFrame(frame);
+ string compressed_headers = CompressHeaders(QuicUtils::HighestPriority());
+ string data = compressed_headers + body;
+ for (size_t fragment_size = 1; fragment_size < data.size();
+ ++fragment_size) {
+ Initialize(kShouldProcessData);
+ for (size_t offset = 0; offset < data.size(); offset += fragment_size) {
+ size_t remaining_data = data.size() - offset;
+ StringPiece fragment(data.data() + offset,
+ min(fragment_size, remaining_data));
+ QuicStreamFrame frame(kStreamId, false, offset, MakeIOVector(fragment));
+ stream_->OnStreamFrame(frame);
+ }
+ ASSERT_EQ(headers + body,
+ stream_->data()) << "fragment_size: " << fragment_size;
}
- ASSERT_EQ(SpdyUtils::SerializeUncompressedHeaders(headers_) + body,
- stream_->data()) << "fragment_size: " << fragment_size;
}
+}
+
+TEST_P(QuicDataStreamTest, ProcessHeadersAndBodyFragmentsSplit) {
+ string headers = SpdyUtils::SerializeUncompressedHeaders(headers_);
+ string body = "this is the body";
- for (size_t split_point = 1; split_point < data.size() - 1; ++split_point) {
+ if (GetParam() > QUIC_VERSION_12) {
+ for (size_t split_point = 1; split_point < body.size() - 1; ++split_point) {
+ Initialize(kShouldProcessData);
+ StringPiece headers1(headers.data(), split_point);
+ stream_->OnStreamHeaders(headers1);
+
+ StringPiece headers2(headers.data() + split_point,
+ headers.size() - split_point);
+ stream_->OnStreamHeaders(headers2);
+ stream_->OnStreamHeadersComplete(false, CompressedHeadersSize());
+
+ StringPiece fragment1(body.data(), split_point);
+ QuicStreamFrame frame1(kStreamId, false, 0, MakeIOVector(fragment1));
+ stream_->OnStreamFrame(frame1);
+
+ StringPiece fragment2(body.data() + split_point,
+ body.size() - split_point);
+ QuicStreamFrame frame2(
+ kStreamId, false, split_point, MakeIOVector(fragment2));
+ stream_->OnStreamFrame(frame2);
+
+ ASSERT_EQ(headers + body,
+ stream_->data()) << "split_point: " << split_point;
+ }
+ } else {
Initialize(kShouldProcessData);
+ string compressed_headers = CompressHeaders(QuicUtils::LowestPriority());
+ string data = compressed_headers + body;
+
+ for (size_t split_point = 1; split_point < data.size() - 1; ++split_point) {
+ Initialize(kShouldProcessData);
- StringPiece fragment1(data.data(), split_point);
- QuicStreamFrame frame1(kStreamId, false, 0, MakeIOVector(fragment1));
- stream_->OnStreamFrame(frame1);
+ StringPiece fragment1(data.data(), split_point);
+ QuicStreamFrame frame1(kStreamId, false, 0, MakeIOVector(fragment1));
+ stream_->OnStreamFrame(frame1);
- StringPiece fragment2(data.data() + split_point, data.size() - split_point);
- QuicStreamFrame frame2(
- kStreamId, false, split_point, MakeIOVector(fragment2));
- stream_->OnStreamFrame(frame2);
+ StringPiece fragment2(data.data() + split_point,
+ data.size() - split_point);
+ QuicStreamFrame frame2(
+ kStreamId, false, split_point, MakeIOVector(fragment2));
+ stream_->OnStreamFrame(frame2);
- ASSERT_EQ(SpdyUtils::SerializeUncompressedHeaders(headers_) + body,
- stream_->data()) << "split_point: " << split_point;
+ ASSERT_EQ(SpdyUtils::SerializeUncompressedHeaders(headers_) + body,
+ stream_->data()) << "split_point: " << split_point;
+ }
+ EXPECT_EQ(QuicUtils::LowestPriority(), stream_->EffectivePriority());
}
- EXPECT_EQ(QuicUtils::LowestPriority(), stream_->EffectivePriority());
}
-TEST_F(QuicDataStreamTest, ProcessHeadersAndBodyReadv) {
+TEST_P(QuicDataStreamTest, ProcessHeadersAndBodyReadv) {
Initialize(!kShouldProcessData);
- string compressed_headers = compressor_->CompressHeadersWithPriority(
- QuicUtils::HighestPriority(), headers_);
+ string headers = SpdyUtils::SerializeUncompressedHeaders(headers_);
string body = "this is the body";
- string data = compressed_headers + body;
- QuicStreamFrame frame(kStreamId, false, 0, MakeIOVector(data));
- string uncompressed_headers =
- SpdyUtils::SerializeUncompressedHeaders(headers_);
- string uncompressed_data = uncompressed_headers + body;
- stream_->OnStreamFrame(frame);
- EXPECT_EQ(uncompressed_headers, stream_->data());
+ if (GetParam() > QUIC_VERSION_12) {
+ stream_->OnStreamHeaders(headers);
+ EXPECT_EQ(headers, stream_->data());
+ stream_->OnStreamHeadersComplete(false, CompressedHeadersSize());
+ QuicStreamFrame frame(kStreamId, false, 0, MakeIOVector(body));
+ stream_->OnStreamFrame(frame);
+ } else {
+ string compressed_headers = CompressHeaders(QuicUtils::HighestPriority());
+ string data = compressed_headers + body;
+ QuicStreamFrame frame(kStreamId, false, 0, MakeIOVector(data));
+ stream_->OnStreamFrame(frame);
+ EXPECT_EQ(headers, stream_->data());
+ }
char buffer[2048];
- ASSERT_LT(data.length(), arraysize(buffer));
+ ASSERT_LT(headers.length() + body.length(), arraysize(buffer));
struct iovec vec;
vec.iov_base = buffer;
vec.iov_len = arraysize(buffer);
size_t bytes_read = stream_->Readv(&vec, 1);
- EXPECT_EQ(uncompressed_headers.length(), bytes_read);
- EXPECT_EQ(uncompressed_headers, string(buffer, bytes_read));
+ EXPECT_EQ(headers.length(), bytes_read);
+ EXPECT_EQ(headers, string(buffer, bytes_read));
bytes_read = stream_->Readv(&vec, 1);
EXPECT_EQ(body.length(), bytes_read);
EXPECT_EQ(body, string(buffer, bytes_read));
}
-TEST_F(QuicDataStreamTest, ProcessHeadersAndBodyIncrementalReadv) {
+TEST_P(QuicDataStreamTest, ProcessHeadersAndBodyIncrementalReadv) {
Initialize(!kShouldProcessData);
- string compressed_headers = compressor_->CompressHeadersWithPriority(
- QuicUtils::HighestPriority(), headers_);
+ string headers = SpdyUtils::SerializeUncompressedHeaders(headers_);
string body = "this is the body";
- string data = compressed_headers + body;
- QuicStreamFrame frame(kStreamId, false, 0, MakeIOVector(data));
- string uncompressed_headers =
- SpdyUtils::SerializeUncompressedHeaders(headers_);
- string uncompressed_data = uncompressed_headers + body;
-
- stream_->OnStreamFrame(frame);
- EXPECT_EQ(uncompressed_headers, stream_->data());
+ if (GetParam() > QUIC_VERSION_12) {
+ stream_->OnStreamHeaders(headers);
+ EXPECT_EQ(headers, stream_->data());
+ stream_->OnStreamHeadersComplete(false, CompressedHeadersSize());
+ QuicStreamFrame frame(kStreamId, false, 0, MakeIOVector(body));
+ stream_->OnStreamFrame(frame);
+ } else {
+ string compressed_headers = CompressHeaders(QuicUtils::HighestPriority());
+ string data = compressed_headers + body;
+ QuicStreamFrame frame(kStreamId, false, 0, MakeIOVector(data));
+ stream_->OnStreamFrame(frame);
+ EXPECT_EQ(headers, stream_->data());
+ }
char buffer[1];
struct iovec vec;
vec.iov_base = buffer;
vec.iov_len = arraysize(buffer);
- for (size_t i = 0; i < uncompressed_data.length(); ++i) {
+
+ string data = headers + body;
+ for (size_t i = 0; i < data.length(); ++i) {
size_t bytes_read = stream_->Readv(&vec, 1);
ASSERT_EQ(1u, bytes_read);
- EXPECT_EQ(uncompressed_data.data()[i], buffer[0]);
+ EXPECT_EQ(data.data()[i], buffer[0]);
}
}
-TEST_F(QuicDataStreamTest, ProcessHeadersUsingReadvWithMultipleIovecs) {
+TEST_P(QuicDataStreamTest, ProcessHeadersUsingReadvWithMultipleIovecs) {
Initialize(!kShouldProcessData);
- string compressed_headers = compressor_->CompressHeadersWithPriority(
- QuicUtils::HighestPriority(), headers_);
+ string headers = SpdyUtils::SerializeUncompressedHeaders(headers_);
string body = "this is the body";
- string data = compressed_headers + body;
- QuicStreamFrame frame(kStreamId, false, 0, MakeIOVector(data));
- string uncompressed_headers =
- SpdyUtils::SerializeUncompressedHeaders(headers_);
- string uncompressed_data = uncompressed_headers + body;
-
- stream_->OnStreamFrame(frame);
- EXPECT_EQ(uncompressed_headers, stream_->data());
+ if (GetParam() > QUIC_VERSION_12) {
+ stream_->OnStreamHeaders(headers);
+ EXPECT_EQ(headers, stream_->data());
+ stream_->OnStreamHeadersComplete(false, CompressedHeadersSize());
+ QuicStreamFrame frame(kStreamId, false, 0, MakeIOVector(body));
+ stream_->OnStreamFrame(frame);
+ } else {
+ string compressed_headers = CompressHeaders(QuicUtils::HighestPriority());
+ string data = compressed_headers + body;
+ QuicStreamFrame frame(kStreamId, false, 0, MakeIOVector(data));
+ stream_->OnStreamFrame(frame);
+ EXPECT_EQ(headers, stream_->data());
+ }
char buffer1[1];
char buffer2[1];
@@ -281,27 +390,29 @@ TEST_F(QuicDataStreamTest, ProcessHeadersUsingReadvWithMultipleIovecs) {
vec[0].iov_len = arraysize(buffer1);
vec[1].iov_base = buffer2;
vec[1].iov_len = arraysize(buffer2);
- for (size_t i = 0; i < uncompressed_data.length(); i += 2) {
+ string data = headers + body;
+ for (size_t i = 0; i < data.length(); i += 2) {
size_t bytes_read = stream_->Readv(vec, 2);
ASSERT_EQ(2u, bytes_read) << i;
- ASSERT_EQ(uncompressed_data.data()[i], buffer1[0]) << i;
- ASSERT_EQ(uncompressed_data.data()[i + 1], buffer2[0]) << i;
+ ASSERT_EQ(data.data()[i], buffer1[0]) << i;
+ ASSERT_EQ(data.data()[i + 1], buffer2[0]) << i;
}
}
-TEST_F(QuicDataStreamTest, ProcessCorruptHeadersEarly) {
+TEST_P(QuicDataStreamTest, ProcessCorruptHeadersEarly) {
+ if (GetParam() > QUIC_VERSION_12) {
+ return;
+ }
Initialize(kShouldProcessData);
- string compressed_headers1 = compressor_->CompressHeadersWithPriority(
- QuicUtils::HighestPriority(), headers_);
+ string compressed_headers1 = CompressHeaders(QuicUtils::HighestPriority());
QuicStreamFrame frame1(
stream_->id(), false, 0, MakeIOVector(compressed_headers1));
string decompressed_headers1 =
SpdyUtils::SerializeUncompressedHeaders(headers_);
headers_["content-type"] = "text/plain";
- string compressed_headers2 = compressor_->CompressHeadersWithPriority(
- QuicUtils::HighestPriority(), headers_);
+ string compressed_headers2 = CompressHeaders(QuicUtils::HighestPriority());
// Corrupt the compressed data.
compressed_headers2[compressed_headers2.length() - 1] ^= 0xA1;
QuicStreamFrame frame2(
@@ -331,19 +442,20 @@ TEST_F(QuicDataStreamTest, ProcessCorruptHeadersEarly) {
EXPECT_EQ("", stream2_->data());
}
-TEST_F(QuicDataStreamTest, ProcessPartialHeadersEarly) {
+TEST_P(QuicDataStreamTest, ProcessPartialHeadersEarly) {
+ if (GetParam() > QUIC_VERSION_12) {
+ return;
+ }
Initialize(kShouldProcessData);
- string compressed_headers1 = compressor_->CompressHeadersWithPriority(
- QuicUtils::HighestPriority(), headers_);
+ string compressed_headers1 = CompressHeaders(QuicUtils::HighestPriority());
QuicStreamFrame frame1(
stream_->id(), false, 0, MakeIOVector(compressed_headers1));
string decompressed_headers1 =
SpdyUtils::SerializeUncompressedHeaders(headers_);
headers_["content-type"] = "text/plain";
- string compressed_headers2 = compressor_->CompressHeadersWithPriority(
- QuicUtils::HighestPriority(), headers_);
+ string compressed_headers2 = CompressHeaders(QuicUtils::HighestPriority());
string partial_compressed_headers =
compressed_headers2.substr(0, compressed_headers2.length() / 2);
QuicStreamFrame frame2(
@@ -384,19 +496,20 @@ TEST_F(QuicDataStreamTest, ProcessPartialHeadersEarly) {
EXPECT_EQ(decompressed_headers2, stream2_->data());
}
-TEST_F(QuicDataStreamTest, ProcessHeadersEarly) {
+TEST_P(QuicDataStreamTest, ProcessHeadersEarly) {
+ if (GetParam() > QUIC_VERSION_12) {
+ return;
+ }
Initialize(kShouldProcessData);
- string compressed_headers1 = compressor_->CompressHeadersWithPriority(
- QuicUtils::HighestPriority(), headers_);
+ string compressed_headers1 = CompressHeaders(QuicUtils::HighestPriority());
QuicStreamFrame frame1(
stream_->id(), false, 0, MakeIOVector(compressed_headers1));
string decompressed_headers1 =
SpdyUtils::SerializeUncompressedHeaders(headers_);
headers_["content-type"] = "text/plain";
- string compressed_headers2 = compressor_->CompressHeadersWithPriority(
- QuicUtils::HighestPriority(), headers_);
+ string compressed_headers2 = CompressHeaders(QuicUtils::HighestPriority());
QuicStreamFrame frame2(
stream2_->id(), false, 0, MakeIOVector(compressed_headers2));
string decompressed_headers2 =
@@ -422,11 +535,13 @@ TEST_F(QuicDataStreamTest, ProcessHeadersEarly) {
EXPECT_EQ(decompressed_headers2, stream2_->data());
}
-TEST_F(QuicDataStreamTest, ProcessHeadersDelay) {
+TEST_P(QuicDataStreamTest, ProcessHeadersDelay) {
+ if (GetParam() > QUIC_VERSION_12) {
+ return;
+ }
Initialize(!kShouldProcessData);
- string compressed_headers = compressor_->CompressHeadersWithPriority(
- QuicUtils::HighestPriority(), headers_);
+ string compressed_headers = CompressHeaders(QuicUtils::HighestPriority());
QuicStreamFrame frame1(
stream_->id(), false, 0, MakeIOVector(compressed_headers));
string decompressed_headers =
diff --git a/net/quic/quic_framer.h b/net/quic/quic_framer.h
index 1323752..01280f7 100644
--- a/net/quic/quic_framer.h
+++ b/net/quic/quic_framer.h
@@ -361,6 +361,11 @@ class NET_EXPORT_PRIVATE QuicFramer {
static QuicSequenceNumberLength GetMinSequenceNumberLength(
QuicPacketSequenceNumber sequence_number);
+ void SetSupportedVersions(const QuicVersionVector& versions) {
+ supported_versions_ = versions;
+ quic_version_ = versions[0];
+ }
+
private:
friend class test::QuicFramerPeer;
diff --git a/net/quic/quic_headers_stream.cc b/net/quic/quic_headers_stream.cc
new file mode 100644
index 0000000..c3e0733
--- /dev/null
+++ b/net/quic/quic_headers_stream.cc
@@ -0,0 +1,261 @@
+// 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/quic/quic_headers_stream.h"
+
+#include "net/quic/quic_session.h"
+#include "net/quic/quic_spdy_decompressor.h"
+
+using base::StringPiece;
+
+namespace net {
+
+namespace {
+
+const QuicStreamId kInvalidStreamId = 0;
+
+} // namespace
+
+// A SpdyFramer visitor which passed SYN_STREAM and SYN_REPLY frames to
+// the QuicDataStream, and closes the connection if any unexpected frames
+// are received.
+class QuicHeadersStream::SpdyFramerVisitor
+ : public SpdyFramerVisitorInterface,
+ public SpdyFramerDebugVisitorInterface {
+ public:
+ explicit SpdyFramerVisitor(QuicHeadersStream* stream) : stream_(stream) {}
+
+ // SpdyFramerVisitorInterface implementation
+ virtual void OnSynStream(SpdyStreamId stream_id,
+ SpdyStreamId associated_stream_id,
+ SpdyPriority priority,
+ uint8 credential_slot,
+ bool fin,
+ bool unidirectional) OVERRIDE {
+ if (!stream_->IsConnected()) {
+ return;
+ }
+
+ if (associated_stream_id != 0) {
+ CloseConnection("associated_stream_id != 0");
+ return;
+ }
+
+ if (credential_slot != 0) {
+ CloseConnection("credential_slot != 0");
+ return;
+ }
+
+ if (unidirectional != 0) {
+ CloseConnection("unidirectional != 0");
+ return;
+ }
+
+ stream_->OnSynStream(stream_id, priority, fin);
+ }
+
+ virtual void OnSynReply(SpdyStreamId stream_id, bool fin) OVERRIDE {
+ if (!stream_->IsConnected()) {
+ return;
+ }
+
+ stream_->OnSynReply(stream_id, fin);
+ }
+
+ virtual bool OnControlFrameHeaderData(SpdyStreamId stream_id,
+ const char* header_data,
+ size_t len) OVERRIDE {
+ if (!stream_->IsConnected()) {
+ return false;
+ }
+ stream_->OnControlFrameHeaderData(stream_id, header_data, len);
+ return true;
+ }
+
+ virtual void OnStreamFrameData(SpdyStreamId stream_id,
+ const char* data,
+ size_t len,
+ bool fin) OVERRIDE {
+ if (fin && len == 0) {
+ // The framer invokes OnStreamFrameData with zero-length data and
+ // fin = true after processing a SYN_STREAM or SYN_REPLY frame
+ // that had the fin bit set.
+ return;
+ }
+ CloseConnection("SPDY DATA frame recevied.");
+ }
+
+ virtual void OnError(SpdyFramer* framer) OVERRIDE {
+ CloseConnection("SPDY framing error.");
+ }
+
+ virtual void OnDataFrameHeader(SpdyStreamId stream_id,
+ size_t length,
+ bool fin) OVERRIDE {
+ CloseConnection("SPDY DATA frame recevied.");
+ }
+
+ virtual void OnRstStream(SpdyStreamId stream_id,
+ SpdyRstStreamStatus status) OVERRIDE {
+ CloseConnection("SPDY RST_STREAM frame recevied.");
+ }
+
+ virtual void OnSetting(SpdySettingsIds id,
+ uint8 flags,
+ uint32 value) OVERRIDE {
+ CloseConnection("SPDY SETTINGS frame recevied.");
+ }
+
+ virtual void OnPing(uint32 unique_id) OVERRIDE {
+ CloseConnection("SPDY PING frame recevied.");
+ }
+
+ virtual void OnGoAway(SpdyStreamId last_accepted_stream_id,
+ SpdyGoAwayStatus status) OVERRIDE {
+ CloseConnection("SPDY GOAWAY frame recevied.");
+ }
+
+ virtual void OnHeaders(SpdyStreamId stream_id, bool fin) OVERRIDE {
+ CloseConnection("SPDY HEADERS frame recevied.");
+ }
+
+ virtual void OnWindowUpdate(SpdyStreamId stream_id,
+ uint32 delta_window_size) OVERRIDE {
+ CloseConnection("SPDY WINDOW_UPDATE frame recevied.");
+ }
+
+ virtual bool OnCredentialFrameData(const char* credential_data,
+ size_t len) OVERRIDE {
+ CloseConnection("SPDY CREDENTIAL frame recevied.");
+ return false;
+ }
+
+ virtual void OnPushPromise(SpdyStreamId stream_id,
+ SpdyStreamId promised_stream_id) OVERRIDE {
+ LOG(DFATAL) << "PUSH_PROMISE frame received from a SPDY/3 framer";
+ CloseConnection("SPDY PUSH_PROMISE frame recevied.");
+ }
+
+ // SpdyFramerDebugVisitorInterface implementation
+ virtual void OnSendCompressedFrame(SpdyStreamId stream_id,
+ SpdyFrameType type,
+ size_t payload_len,
+ size_t frame_len) OVERRIDE {}
+
+ virtual void OnReceiveCompressedFrame(SpdyStreamId stream_id,
+ SpdyFrameType type,
+ size_t frame_len) OVERRIDE {
+ if (stream_->IsConnected()) {
+ stream_->OnCompressedFrameSize(frame_len);
+ }
+ }
+
+ private:
+ void CloseConnection(const string& details) {
+ if (stream_->IsConnected()) {
+ stream_->CloseConnectionWithDetails(
+ QUIC_INVALID_HEADERS_STREAM_DATA, details);
+ }
+ }
+
+ private:
+ QuicHeadersStream* stream_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyFramerVisitor);
+};
+
+QuicHeadersStream::QuicHeadersStream(QuicSession* session)
+ : ReliableQuicStream(kHeadersStreamId, session),
+ stream_id_(kInvalidStreamId),
+ fin_(false),
+ frame_len_(0),
+ spdy_framer_(SPDY3),
+ spdy_framer_visitor_(new SpdyFramerVisitor(this)) {
+ spdy_framer_.set_visitor(spdy_framer_visitor_.get());
+ spdy_framer_.set_debug_visitor(spdy_framer_visitor_.get());
+}
+
+QuicHeadersStream::~QuicHeadersStream() {}
+
+size_t QuicHeadersStream::WriteHeaders(QuicStreamId stream_id,
+ const SpdyHeaderBlock& headers,
+ bool fin) {
+ scoped_ptr<SpdySerializedFrame> frame;
+ if (session()->is_server()) {
+ SpdySynReplyIR syn_reply(stream_id);
+ *syn_reply.GetMutableNameValueBlock() = headers;
+ syn_reply.set_fin(fin);
+ frame.reset(spdy_framer_.SerializeFrame(syn_reply));
+ } else {
+ SpdySynStreamIR syn_stream(stream_id);
+ *syn_stream.GetMutableNameValueBlock() = headers;
+ syn_stream.set_fin(fin);
+ frame.reset(spdy_framer_.SerializeFrame(syn_stream));
+ }
+ WriteOrBufferData(StringPiece(frame->data(), frame->size()), false);
+ return frame->size();
+}
+
+uint32 QuicHeadersStream::ProcessRawData(const char* data,
+ uint32 data_len) {
+ return spdy_framer_.ProcessInput(data, data_len);
+}
+
+QuicPriority QuicHeadersStream::EffectivePriority() const { return 0; }
+
+void QuicHeadersStream::OnSynStream(SpdyStreamId stream_id,
+ SpdyPriority priority,
+ bool fin) {
+ if (!session()->is_server()) {
+ CloseConnectionWithDetails(
+ QUIC_INVALID_HEADERS_STREAM_DATA,
+ "SPDY SYN_STREAM frame recevied at the client");
+ return;
+ }
+ DCHECK_EQ(kInvalidStreamId, stream_id_);
+ stream_id_ = stream_id;
+ fin_ = fin;
+ session()->OnStreamHeadersPriority(stream_id, priority);
+}
+
+void QuicHeadersStream::OnSynReply(SpdyStreamId stream_id, bool fin) {
+ if (session()->is_server()) {
+ CloseConnectionWithDetails(
+ QUIC_INVALID_HEADERS_STREAM_DATA,
+ "SPDY SYN_REPLY frame recevied at the server");
+ return;
+ }
+ DCHECK_EQ(kInvalidStreamId, stream_id_);
+ stream_id_ = stream_id;
+ fin_ = fin;
+}
+
+void QuicHeadersStream::OnControlFrameHeaderData(SpdyStreamId stream_id,
+ const char* header_data,
+ size_t len) {
+ DCHECK_EQ(stream_id_, stream_id);
+ if (len == 0) {
+ DCHECK_NE(0u, stream_id_);
+ DCHECK_NE(0u, frame_len_);
+ session()->OnStreamHeadersComplete(stream_id_, fin_, frame_len_);
+ // Reset state for the next frame.
+ stream_id_ = kInvalidStreamId;
+ fin_ = false;
+ frame_len_ = 0;
+ } else {
+ session()->OnStreamHeaders(stream_id_, StringPiece(header_data, len));
+ }
+}
+
+void QuicHeadersStream::OnCompressedFrameSize(size_t frame_len) {
+ DCHECK_EQ(kInvalidStreamId, stream_id_);
+ DCHECK_EQ(0u, frame_len_);
+ frame_len_ = frame_len;
+}
+
+bool QuicHeadersStream::IsConnected() {
+ return session()->connection()->connected();
+}
+
+} // namespace net
diff --git a/net/quic/quic_headers_stream.h b/net/quic/quic_headers_stream.h
new file mode 100644
index 0000000..b44b14f
--- /dev/null
+++ b/net/quic/quic_headers_stream.h
@@ -0,0 +1,81 @@
+// 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.
+
+#ifndef NET_QUIC_QUIC_HEADERS_STREAM_H_
+#define NET_QUIC_QUIC_HEADERS_STREAM_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_spdy_decompressor.h"
+#include "net/quic/reliable_quic_stream.h"
+#include "net/spdy/spdy_framer.h"
+
+namespace net {
+
+// Headers in QUIC are sent as SPDY SYN_STREAM or SYN_REPLY frames
+// over a reserved reliable stream with the id 2. Each endpoint (client
+// and server) will allocate an instance of QuicHeadersStream to send
+// and receive headers.
+class NET_EXPORT_PRIVATE QuicHeadersStream : public ReliableQuicStream {
+ public:
+ explicit QuicHeadersStream(QuicSession* session);
+ virtual ~QuicHeadersStream();
+
+ // Writes |headers| for |stream_id| in a SYN_STREAM or SYN_REPLY
+ // frame to the peer. If |fin| is true, the fin flag will be set on
+ // the SPDY frame. Returns the size, in bytes, of the resulting
+ // SPDY frame.
+ size_t WriteHeaders(QuicStreamId stream_id,
+ const SpdyHeaderBlock& headers,
+ bool fin);
+
+ // ReliableQuicStream implementation
+ virtual uint32 ProcessRawData(const char* data, uint32 data_len) OVERRIDE;
+ virtual QuicPriority EffectivePriority() const OVERRIDE;
+
+ private:
+ class SpdyFramerVisitor;
+
+ // The following methods are called by the SimpleVisitor.
+
+ // Called when a SYN_STREAM frame has been received.
+ void OnSynStream(SpdyStreamId stream_id,
+ SpdyPriority priority,
+ bool fin);
+
+ // Called when a SYN_REPLY frame been received.
+ void OnSynReply(SpdyStreamId stream_id, bool fin);
+
+ // Called when a chunk of header data is available. This is called
+ // after OnSynStream, or OnSynReply.
+ // |stream_id| The stream receiving the header data.
+ // |header_data| A buffer containing the header data chunk received.
+ // |len| The length of the header data buffer. A length of zero indicates
+ // that the header data block has been completely sent.
+ void OnControlFrameHeaderData(SpdyStreamId stream_id,
+ const char* header_data,
+ size_t len);
+
+ // Called when the size of the compressed frame payload is available.
+ void OnCompressedFrameSize(size_t frame_len);
+
+ // Returns true if the session is still connected.
+ bool IsConnected();
+
+ // Data about the stream whose headers are being processed.
+ QuicStreamId stream_id_;
+ bool fin_;
+ size_t frame_len_;
+
+ SpdyFramer spdy_framer_;
+ scoped_ptr<SpdyFramerVisitor> spdy_framer_visitor_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuicHeadersStream);
+};
+
+} // namespace net
+
+#endif // NET_QUIC_QUIC_HEADERS_STREAM_H_
diff --git a/net/quic/quic_headers_stream_test.cc b/net/quic/quic_headers_stream_test.cc
new file mode 100644
index 0000000..cbe8f5d
--- /dev/null
+++ b/net/quic/quic_headers_stream_test.cc
@@ -0,0 +1,329 @@
+// 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/quic/quic_headers_stream.h"
+
+#include "net/quic/quic_utils.h"
+#include "net/quic/spdy_utils.h"
+#include "net/quic/test_tools/quic_connection_peer.h"
+#include "net/quic/test_tools/quic_session_peer.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using std::string;
+using testing::Invoke;
+using testing::Return;
+using testing::StrictMock;
+using testing::WithArgs;
+using testing::_;
+
+namespace net {
+namespace test {
+namespace {
+
+class MockVisitor : public SpdyFramerVisitorInterface {
+ public:
+ MOCK_METHOD1(OnError, void(SpdyFramer* framer));
+ MOCK_METHOD3(OnDataFrameHeader, void(SpdyStreamId stream_id,
+ size_t length,
+ bool fin));
+ MOCK_METHOD4(OnStreamFrameData, void(SpdyStreamId stream_id,
+ const char* data,
+ size_t len,
+ bool fin));
+ MOCK_METHOD3(OnControlFrameHeaderData, bool(SpdyStreamId stream_id,
+ const char* header_data,
+ size_t len));
+ MOCK_METHOD6(OnSynStream, void(SpdyStreamId stream_id,
+ SpdyStreamId associated_stream_id,
+ SpdyPriority priority,
+ uint8 slot,
+ bool fin,
+ bool unidirectional));
+ MOCK_METHOD2(OnSynReply, void(SpdyStreamId stream_id, bool fin));
+ MOCK_METHOD2(OnRstStream, void(SpdyStreamId stream_id,
+ SpdyRstStreamStatus status));
+ MOCK_METHOD1(OnSettings, void(bool clear_persisted));
+ MOCK_METHOD3(OnSetting, void(SpdySettingsIds id, uint8 flags, uint32 value));
+ MOCK_METHOD1(OnPing, void(uint32 unique_id));
+ MOCK_METHOD2(OnGoAway, void(SpdyStreamId last_accepted_stream_id,
+ SpdyGoAwayStatus status));
+ MOCK_METHOD2(OnHeaders, void(SpdyStreamId stream_id, bool fin));
+ MOCK_METHOD2(OnWindowUpdate, void(SpdyStreamId stream_id,
+ uint32 delta_window_size));
+ MOCK_METHOD2(OnCredentialFrameData, bool(const char* credential_data,
+ size_t len));
+ MOCK_METHOD1(OnBlocked, void(SpdyStreamId stream_id));
+ MOCK_METHOD2(OnPushPromise, void(SpdyStreamId stream_id,
+ SpdyStreamId promised_stream_id));
+};
+
+class QuicHeadersStreamTest : public ::testing::TestWithParam<bool> {
+ public:
+ static QuicVersionVector GetVersions() {
+ QuicVersionVector versions;
+ versions.push_back(QUIC_VERSION_13);
+ return versions;
+ }
+
+ QuicHeadersStreamTest()
+ : connection_(new StrictMock<MockConnection>(is_server(), GetVersions())),
+ session_(connection_),
+ headers_stream_(QuicSessionPeer::GetHeadersStream(&session_)),
+ body_("hello world"),
+ framer_(SPDY3) {
+ headers_[":version"] = "HTTP/1.1";
+ headers_[":status"] = "200 Ok";
+ headers_["content-length"] = "11";
+ framer_.set_visitor(&visitor_);
+ EXPECT_EQ(QUIC_VERSION_13, session_.connection()->version());
+ EXPECT_TRUE(headers_stream_ != NULL);
+ }
+
+ QuicConsumedData SaveIov(const struct iovec* iov, int count) {
+ for (int i = 0 ; i < count; ++i) {
+ saved_data_.append(static_cast<char*>(iov[i].iov_base), iov[i].iov_len);
+ }
+ return QuicConsumedData(saved_data_.length(), false);
+ }
+
+ bool SaveHeaderData(const char* data, int len) {
+ saved_header_data_.append(data, len);
+ return true;
+ }
+
+ void SaveHeaderDataStringPiece(StringPiece data) {
+ saved_header_data_.append(data.data(), data.length());
+ }
+
+ void WriteHeadersAndExpectSynStream(QuicStreamId stream_id,
+ bool fin,
+ QuicPriority priority) {
+ WriteHeadersAndCheckData(stream_id, fin, priority, SYN_STREAM);
+ }
+
+ void WriteHeadersAndExpectSynReply(QuicStreamId stream_id,
+ bool fin) {
+ WriteHeadersAndCheckData(stream_id, fin, 0, SYN_REPLY);
+ }
+
+ void WriteHeadersAndCheckData(QuicStreamId stream_id,
+ bool fin,
+ QuicPriority priority,
+ SpdyFrameType type) {
+ // Write the headers and capture the outgoing data
+ EXPECT_CALL(session_, WritevData(kHeadersStreamId, _, _, _, false, NULL))
+ .WillOnce(WithArgs<1, 2>(
+ Invoke(this, &QuicHeadersStreamTest::SaveIov)));
+ headers_stream_->WriteHeaders(stream_id, headers_, fin);
+
+ // Parse the outgoing data and check that it matches was was written.
+ if (type == SYN_STREAM) {
+ EXPECT_CALL(visitor_, OnSynStream(stream_id, kNoAssociatedStream, 0,
+ // priority,
+ _, fin, kNotUnidirectional));
+ } else {
+ EXPECT_CALL(visitor_, OnSynReply(stream_id, fin));
+ }
+ EXPECT_CALL(visitor_, OnControlFrameHeaderData(stream_id, _, _))
+ .WillRepeatedly(WithArgs<1, 2>(
+ Invoke(this, &QuicHeadersStreamTest::SaveHeaderData)));
+ if (fin) {
+ EXPECT_CALL(visitor_, OnStreamFrameData(stream_id, NULL, 0, true));
+ }
+ framer_.ProcessInput(saved_data_.data(), saved_data_.length());
+ EXPECT_FALSE(framer_.HasError()) << framer_.error_code();
+
+ CheckHeaders();
+ saved_data_.clear();
+ }
+
+ void CheckHeaders() {
+ SpdyHeaderBlock headers;
+ EXPECT_EQ(saved_header_data_.length(),
+ framer_.ParseHeaderBlockInBuffer(saved_header_data_.data(),
+ saved_header_data_.length(),
+ &headers));
+ EXPECT_EQ(headers_, headers);
+ saved_header_data_.clear();
+ }
+
+ bool is_server() {
+ return GetParam();
+ }
+
+ void CloseConnection() {
+ QuicConnectionPeer::CloseConnection(connection_);
+ }
+
+ static const bool kNotUnidirectional = false;
+ static const bool kNoAssociatedStream = false;
+
+ StrictMock<MockConnection>* connection_;
+ StrictMock<MockSession> session_;
+ QuicHeadersStream* headers_stream_;
+ SpdyHeaderBlock headers_;
+ string body_;
+ string saved_data_;
+ string saved_header_data_;
+ SpdyFramer framer_;
+ StrictMock<MockVisitor> visitor_;
+};
+
+INSTANTIATE_TEST_CASE_P(Tests, QuicHeadersStreamTest, testing::Bool());
+
+TEST_P(QuicHeadersStreamTest, StreamId) {
+ EXPECT_EQ(3u, headers_stream_->id());
+}
+
+TEST_P(QuicHeadersStreamTest, EffectivePriority) {
+ EXPECT_EQ(0u, headers_stream_->EffectivePriority());
+}
+
+TEST_P(QuicHeadersStreamTest, WriteHeaders) {
+ for (QuicStreamId stream_id = 5; stream_id < 9; stream_id +=2) {
+ for (int count = 0; count < 2; ++count) {
+ bool fin = (count == 0);
+ if (is_server()) {
+ WriteHeadersAndExpectSynReply(stream_id, fin);
+ } else {
+ for (QuicPriority priority = 0; priority < 7; ++priority) {
+ WriteHeadersAndExpectSynStream(stream_id, fin, priority);
+ }
+ }
+ }
+ }
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessRawData) {
+ for (QuicStreamId stream_id = 5; stream_id < 9; stream_id +=2) {
+ for (int count = 0; count < 2; ++count) {
+ bool fin = (count == 0);
+ for (QuicPriority priority = 0; priority < 7; ++priority) {
+ // Replace with "WriteHeadersAndSaveData"
+ scoped_ptr<SpdySerializedFrame> frame;
+ if (is_server()) {
+ SpdySynStreamIR syn_stream(stream_id);
+ *syn_stream.GetMutableNameValueBlock() = headers_;
+ syn_stream.set_fin(fin);
+ frame.reset(framer_.SerializeSynStream(syn_stream));
+ EXPECT_CALL(session_, OnStreamHeadersPriority(stream_id, 0));
+ } else {
+ SpdySynReplyIR syn_reply(stream_id);
+ *syn_reply.GetMutableNameValueBlock() = headers_;
+ syn_reply.set_fin(fin);
+ frame.reset(framer_.SerializeSynReply(syn_reply));
+ }
+ EXPECT_CALL(session_, OnStreamHeaders(stream_id, _))
+ .WillRepeatedly(WithArgs<1>(
+ Invoke(this,
+ &QuicHeadersStreamTest::SaveHeaderDataStringPiece)));
+ EXPECT_CALL(session_,
+ OnStreamHeadersComplete(stream_id, fin, frame->size()));
+ headers_stream_->ProcessRawData(frame->data(), frame->size());
+
+ CheckHeaders();
+ }
+ }
+ }
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessSpdyDataFrame) {
+ SpdyDataIR data(2, "");
+ scoped_ptr<SpdySerializedFrame> frame(framer_.SerializeFrame(data));
+ EXPECT_CALL(*connection_,
+ SendConnectionCloseWithDetails(QUIC_INVALID_HEADERS_STREAM_DATA,
+ "SPDY DATA frame recevied."))
+ .WillOnce(InvokeWithoutArgs(this,
+ &QuicHeadersStreamTest::CloseConnection));
+ headers_stream_->ProcessRawData(frame->data(), frame->size());
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessSpdyRstStreamFrame) {
+ SpdyRstStreamIR data(2, RST_STREAM_PROTOCOL_ERROR);
+ scoped_ptr<SpdySerializedFrame> frame(framer_.SerializeFrame(data));
+ EXPECT_CALL(*connection_,
+ SendConnectionCloseWithDetails(
+ QUIC_INVALID_HEADERS_STREAM_DATA,
+ "SPDY RST_STREAM frame recevied."))
+ .WillOnce(InvokeWithoutArgs(this,
+ &QuicHeadersStreamTest::CloseConnection));
+ headers_stream_->ProcessRawData(frame->data(), frame->size());
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessSpdySettingsFrame) {
+ SpdySettingsIR data;
+ data.AddSetting(SETTINGS_UPLOAD_BANDWIDTH, true, true, 0);
+ scoped_ptr<SpdySerializedFrame> frame(framer_.SerializeFrame(data));
+ EXPECT_CALL(*connection_,
+ SendConnectionCloseWithDetails(
+ QUIC_INVALID_HEADERS_STREAM_DATA,
+ "SPDY SETTINGS frame recevied."))
+ .WillOnce(InvokeWithoutArgs(this,
+ &QuicHeadersStreamTest::CloseConnection));
+ headers_stream_->ProcessRawData(frame->data(), frame->size());
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessSpdyPingFrame) {
+ SpdyPingIR data(1);
+ scoped_ptr<SpdySerializedFrame> frame(framer_.SerializeFrame(data));
+ EXPECT_CALL(*connection_,
+ SendConnectionCloseWithDetails(QUIC_INVALID_HEADERS_STREAM_DATA,
+ "SPDY PING frame recevied."))
+ .WillOnce(InvokeWithoutArgs(this,
+ &QuicHeadersStreamTest::CloseConnection));
+ headers_stream_->ProcessRawData(frame->data(), frame->size());
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessSpdyGoAwayFrame) {
+ SpdyGoAwayIR data(1, GOAWAY_PROTOCOL_ERROR);
+ scoped_ptr<SpdySerializedFrame> frame(framer_.SerializeFrame(data));
+ EXPECT_CALL(*connection_,
+ SendConnectionCloseWithDetails(QUIC_INVALID_HEADERS_STREAM_DATA,
+ "SPDY GOAWAY frame recevied."))
+ .WillOnce(InvokeWithoutArgs(this,
+ &QuicHeadersStreamTest::CloseConnection));
+ headers_stream_->ProcessRawData(frame->data(), frame->size());
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessSpdyHeadersFrame) {
+ SpdyHeadersIR data(1);
+ scoped_ptr<SpdySerializedFrame> frame(framer_.SerializeFrame(data));
+ EXPECT_CALL(*connection_,
+ SendConnectionCloseWithDetails(QUIC_INVALID_HEADERS_STREAM_DATA,
+ "SPDY HEADERS frame recevied."))
+ .WillOnce(InvokeWithoutArgs(this,
+ &QuicHeadersStreamTest::CloseConnection));
+ headers_stream_->ProcessRawData(frame->data(), frame->size());
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessSpdyWindowUpdateFrame) {
+ SpdyWindowUpdateIR data(1, 1);
+ scoped_ptr<SpdySerializedFrame> frame(framer_.SerializeFrame(data));
+ EXPECT_CALL(*connection_,
+ SendConnectionCloseWithDetails(
+ QUIC_INVALID_HEADERS_STREAM_DATA,
+ "SPDY WINDOW_UPDATE frame recevied."))
+ .WillOnce(InvokeWithoutArgs(this,
+ &QuicHeadersStreamTest::CloseConnection));
+ headers_stream_->ProcessRawData(frame->data(), frame->size());
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessSpdyCredentialFrame) {
+ SpdyCredentialIR data(1);
+ scoped_ptr<SpdySerializedFrame> frame(framer_.SerializeFrame(data));
+ EXPECT_CALL(*connection_,
+ SendConnectionCloseWithDetails(
+ QUIC_INVALID_HEADERS_STREAM_DATA,
+ "SPDY CREDENTIAL frame recevied."))
+ .WillOnce(InvokeWithoutArgs(this,
+ &QuicHeadersStreamTest::CloseConnection));
+ headers_stream_->ProcessRawData(frame->data(), frame->size());
+}
+
+} // namespace
+} // namespace test
+} // namespace net
diff --git a/net/quic/quic_protocol.cc b/net/quic/quic_protocol.cc
index e53daec..07a2f4d 100644
--- a/net/quic/quic_protocol.cc
+++ b/net/quic/quic_protocol.cc
@@ -144,6 +144,8 @@ QuicTag QuicVersionToQuicTag(const QuicVersion version) {
switch (version) {
case QUIC_VERSION_12:
return MakeQuicTag('Q', '0', '1', '2');
+ case QUIC_VERSION_13:
+ return MakeQuicTag('Q', '0', '1', '3');
default:
// This shold be an ERROR because we should never attempt to convert an
// invalid QuicVersion to be written to the wire.
@@ -171,6 +173,7 @@ return #x
string QuicVersionToString(const QuicVersion version) {
switch (version) {
RETURN_STRING_LITERAL(QUIC_VERSION_12);
+ RETURN_STRING_LITERAL(QUIC_VERSION_13);
default:
return "QUIC_VERSION_UNSUPPORTED";
}
diff --git a/net/quic/quic_protocol.h b/net/quic/quic_protocol.h
index e97e2fc..eca6cee 100644
--- a/net/quic/quic_protocol.h
+++ b/net/quic/quic_protocol.h
@@ -93,9 +93,11 @@ const QuicStreamId kMaxStreamIdDelta = 100;
const QuicHeaderId kMaxHeaderIdDelta = 100;
// Reserved ID for the crypto stream.
-// TODO(rch): ensure that this is not usable by any other streams.
const QuicStreamId kCryptoStreamId = 1;
+// Reserved ID for the headers stream.
+const QuicStreamId kHeadersStreamId = 3;
+
// This is the default network timeout a for connection till the crypto
// handshake succeeds and the negotiated timeout from the handshake is received.
const int64 kDefaultInitialTimeoutSecs = 120; // 2 mins.
@@ -239,7 +241,8 @@ enum QuicVersion {
// Special case to indicate unknown/unsupported QUIC version.
QUIC_VERSION_UNSUPPORTED = 0,
- QUIC_VERSION_12 = 12, // Current version.
+ QUIC_VERSION_12 = 12,
+ QUIC_VERSION_13 = 13, // Current version.
};
// This vector contains QUIC versions which we currently support.
@@ -402,7 +405,8 @@ enum QuicErrorCode {
QUIC_PACKET_READ_ERROR = 51,
// We received a STREAM_FRAME with no data and no fin flag set.
QUIC_INVALID_STREAM_FRAME = 50,
-
+ // We received invalid data on the headers stream.
+ QUIC_INVALID_HEADERS_STREAM_DATA = 56,
// Crypto errors.
@@ -458,7 +462,7 @@ enum QuicErrorCode {
QUIC_VERSION_NEGOTIATION_MISMATCH = 55,
// No error. Used as bound while iterating.
- QUIC_LAST_ERROR = 56,
+ QUIC_LAST_ERROR = 57,
};
struct NET_EXPORT_PRIVATE QuicPacketPublicHeader {
diff --git a/net/quic/quic_session.cc b/net/quic/quic_session.cc
index 67cdf54..5a05af6 100644
--- a/net/quic/quic_session.cc
+++ b/net/quic/quic_session.cc
@@ -7,6 +7,7 @@
#include "base/stl_util.h"
#include "net/quic/crypto/proof_verifier.h"
#include "net/quic/quic_connection.h"
+#include "net/quic/quic_headers_stream.h"
#include "net/ssl/ssl_info.h"
using base::StringPiece;
@@ -96,6 +97,15 @@ QuicSession::QuicSession(QuicConnection* connection,
connection_->SetOverallConnectionTimeout(
config_.max_time_before_crypto_handshake());
}
+ if (connection_->version() > QUIC_VERSION_12) {
+ headers_stream_.reset(new QuicHeadersStream(this));
+ if (!is_server()) {
+ // For version above QUIC v12, the headers stream is stream 3, so the
+ // next available local stream ID should be 5.
+ DCHECK_EQ(kHeadersStreamId, next_stream_id_);
+ next_stream_id_ += 2;
+ }
+ }
}
QuicSession::~QuicSession() {
@@ -162,6 +172,37 @@ bool QuicSession::OnStreamFrames(const vector<QuicStreamFrame>& frames) {
return true;
}
+void QuicSession::OnStreamHeaders(QuicStreamId stream_id,
+ StringPiece headers_data) {
+ QuicDataStream* stream = GetDataStream(stream_id);
+ if (!stream) {
+ // It's quite possible to receive headers after a stream has been reset.
+ return;
+ }
+ stream->OnStreamHeaders(headers_data);
+}
+
+void QuicSession::OnStreamHeadersPriority(QuicStreamId stream_id,
+ QuicPriority priority) {
+ QuicDataStream* stream = GetDataStream(stream_id);
+ if (!stream) {
+ // It's quite possible to receive headers after a stream has been reset.
+ return;
+ }
+ stream->OnStreamHeadersPriority(priority);
+}
+
+void QuicSession::OnStreamHeadersComplete(QuicStreamId stream_id,
+ bool fin,
+ size_t frame_len) {
+ QuicDataStream* stream = GetDataStream(stream_id);
+ if (!stream) {
+ // It's quite possible to receive headers after a stream has been reset.
+ return;
+ }
+ stream->OnStreamHeadersComplete(fin, frame_len);
+}
+
void QuicSession::OnRstStream(const QuicRstStreamFrame& frame) {
if (frame.stream_id == kCryptoStreamId) {
connection()->SendConnectionCloseWithDetails(
@@ -169,6 +210,13 @@ void QuicSession::OnRstStream(const QuicRstStreamFrame& frame) {
"Attempt to reset the crypto stream");
return;
}
+ if (frame.stream_id == kHeadersStreamId &&
+ connection()->version() > QUIC_VERSION_12) {
+ connection()->SendConnectionCloseWithDetails(
+ QUIC_INVALID_STREAM_ID,
+ "Attempt to reset the headers stream");
+ return;
+ }
QuicDataStream* stream = GetDataStream(frame.stream_id);
if (!stream) {
return; // Errors are handled by GetStream.
@@ -183,9 +231,11 @@ void QuicSession::OnRstStream(const QuicRstStreamFrame& frame) {
AddPrematurelyClosedStream(frame.stream_id);
return;
}
- if (stream->stream_bytes_read() > 0 && !stream->headers_decompressed()) {
- connection()->SendConnectionClose(
- QUIC_STREAM_RST_BEFORE_HEADERS_DECOMPRESSED);
+ if (connection()->version() <= QUIC_VERSION_12) {
+ if (stream->stream_bytes_read() > 0 && !stream->headers_decompressed()) {
+ connection()->SendConnectionClose(
+ QUIC_STREAM_RST_BEFORE_HEADERS_DECOMPRESSED);
+ }
}
stream->OnStreamReset(frame.error_code);
}
@@ -261,6 +311,16 @@ QuicConsumedData QuicSession::WritevData(
ack_notifier_delegate);
}
+size_t QuicSession::WriteHeaders(QuicStreamId id,
+ const SpdyHeaderBlock& headers,
+ bool fin) {
+ DCHECK_LT(QUIC_VERSION_12, connection()->version());
+ if (connection()->version() <= QUIC_VERSION_12) {
+ return 0;
+ }
+ return headers_stream_->WriteHeaders(id, headers, fin);
+}
+
void QuicSession::SendRstStream(QuicStreamId id,
QuicRstStreamErrorCode error) {
connection_->SendRstStream(id, error);
@@ -286,7 +346,8 @@ void QuicSession::CloseStreamInner(QuicStreamId stream_id,
return;
}
QuicDataStream* stream = it->second;
- if (connection_->connected() && !stream->headers_decompressed()) {
+ if (connection_->version() <= QUIC_VERSION_12 &&
+ connection_->connected() && !stream->headers_decompressed()) {
// If the stream is being closed locally (for example a client cancelling
// a request before receiving the response) then we need to make sure that
// we keep the stream alive long enough to process any response or
@@ -338,6 +399,9 @@ void QuicSession::CloseZombieStream(QuicStreamId stream_id) {
}
void QuicSession::AddPrematurelyClosedStream(QuicStreamId stream_id) {
+ if (connection()->version() > QUIC_VERSION_12) {
+ return;
+ }
if (prematurely_closed_streams_.size() ==
kMaxPrematurelyClosedStreamsTracked) {
prematurely_closed_streams_.erase(prematurely_closed_streams_.begin());
@@ -411,6 +475,10 @@ ReliableQuicStream* QuicSession::GetStream(const QuicStreamId stream_id) {
if (stream_id == kCryptoStreamId) {
return GetCryptoStream();
}
+ if (stream_id == kHeadersStreamId &&
+ connection_->version() > QUIC_VERSION_12) {
+ return headers_stream_.get();
+ }
return GetDataStream(stream_id);
}
@@ -419,6 +487,11 @@ QuicDataStream* QuicSession::GetDataStream(const QuicStreamId stream_id) {
DLOG(FATAL) << "Attempt to call GetDataStream with the crypto stream id";
return NULL;
}
+ if (stream_id == kHeadersStreamId &&
+ connection_->version() > QUIC_VERSION_12) {
+ DLOG(FATAL) << "Attempt to call GetDataStream with the headers stream id";
+ return NULL;
+ }
DataStreamMap::iterator it = stream_map_.find(stream_id);
if (it != stream_map_.end()) {
@@ -432,7 +505,9 @@ QuicDataStream* QuicSession::GetDataStream(const QuicStreamId stream_id) {
if (stream_id % 2 == next_stream_id_ % 2) {
// We've received a frame for a locally-created stream that is not
// currently active. This is an error.
- connection()->SendConnectionClose(QUIC_PACKET_FOR_NONEXISTENT_STREAM);
+ if (connection()->connected()) {
+ connection()->SendConnectionClose(QUIC_PACKET_FOR_NONEXISTENT_STREAM);
+ }
return NULL;
}
@@ -459,7 +534,11 @@ QuicDataStream* QuicSession::GetIncomingReliableStream(
return NULL;
}
if (largest_peer_created_stream_id_ == 0) {
- largest_peer_created_stream_id_= 1;
+ if (is_server() && connection()->version() > QUIC_VERSION_12) {
+ largest_peer_created_stream_id_= 3;
+ } else {
+ largest_peer_created_stream_id_= 1;
+ }
}
for (QuicStreamId id = largest_peer_created_stream_id_ + 2;
id < stream_id;
@@ -481,6 +560,11 @@ bool QuicSession::IsClosedStream(QuicStreamId id) {
if (id == kCryptoStreamId) {
return false;
}
+ if (connection()->version() > QUIC_VERSION_12) {
+ if (id == kHeadersStreamId) {
+ return false;
+ }
+ }
if (ContainsKey(zombie_streams_, id)) {
return true;
}
@@ -523,6 +607,7 @@ bool QuicSession::HasQueuedData() const {
void QuicSession::MarkDecompressionBlocked(QuicHeaderId header_id,
QuicStreamId stream_id) {
+ DCHECK_GE(QUIC_VERSION_12, connection()->version());
decompression_blocked_streams_[header_id] = stream_id;
}
diff --git a/net/quic/quic_session.h b/net/quic/quic_session.h
index 083990b..3b82d31 100644
--- a/net/quic/quic_session.h
+++ b/net/quic/quic_session.h
@@ -16,6 +16,7 @@
#include "net/quic/quic_connection.h"
#include "net/quic/quic_crypto_stream.h"
#include "net/quic/quic_data_stream.h"
+#include "net/quic/quic_headers_stream.h"
#include "net/quic/quic_packet_creator.h"
#include "net/quic/quic_protocol.h"
#include "net/quic/quic_spdy_compressor.h"
@@ -71,6 +72,21 @@ class NET_EXPORT_PRIVATE QuicSession : public QuicConnectionVisitorInterface {
virtual bool OnCanWrite() OVERRIDE;
virtual bool HasPendingHandshake() const OVERRIDE;
+ // Called by the headers stream when headers have been received for a stream.
+ virtual void OnStreamHeaders(QuicStreamId stream_id,
+ base::StringPiece headers_data);
+ // Called by the headers stream when headers with a priority have been
+ // received for this stream. This method will only be called for server
+ // streams.
+ virtual void OnStreamHeadersPriority(QuicStreamId stream_id,
+ QuicPriority priority);
+ // Called by the headers stream when headers have been completely received
+ // for a stream. |fin| will be true if the fin flag was set in the headers
+ // frame.
+ virtual void OnStreamHeadersComplete(QuicStreamId stream_id,
+ bool fin,
+ size_t frame_len);
+
// Called by streams when they want to write data to the peer.
// Returns a pair with the number of bytes consumed from data, and a boolean
// indicating if the fin bit was consumed. This does not indicate the data
@@ -87,6 +103,12 @@ class NET_EXPORT_PRIVATE QuicSession : public QuicConnectionVisitorInterface {
bool fin,
QuicAckNotifier::DelegateInterface* ack_notifier_delegate);
+ // Writes |headers| for the stream |id| to the dedicated headers stream.
+ // If |fin| is true, then no more data will be sent for the stream |id|.
+ size_t WriteHeaders(QuicStreamId id,
+ const SpdyHeaderBlock& headers,
+ bool fin);
+
// Called by streams when they want to close the stream in both directions.
virtual void SendRstStream(QuicStreamId id, QuicRstStreamErrorCode error);
@@ -245,6 +267,8 @@ class NET_EXPORT_PRIVATE QuicSession : public QuicConnectionVisitorInterface {
scoped_ptr<QuicConnection> connection_;
+ scoped_ptr<QuicHeadersStream> headers_stream_;
+
// Tracks the last 20 streams which closed without decompressing headers.
// This is for best-effort detection of an unrecoverable compression context.
// Ideally this would be a linked_hash_set as the boolean is unused.
diff --git a/net/quic/quic_session_test.cc b/net/quic/quic_session_test.cc
index ecbafb5..9390360 100644
--- a/net/quic/quic_session_test.cc
+++ b/net/quic/quic_session_test.cc
@@ -13,6 +13,7 @@
#include "net/quic/quic_protocol.h"
#include "net/quic/test_tools/quic_connection_peer.h"
#include "net/quic/test_tools/quic_data_stream_peer.h"
+#include "net/quic/test_tools/quic_session_peer.h"
#include "net/quic/test_tools/quic_test_utils.h"
#include "net/quic/test_tools/reliable_quic_stream_peer.h"
#include "net/spdy/spdy_framer.h"
@@ -120,10 +121,10 @@ class TestSession : public QuicSession {
TestCryptoStream crypto_stream_;
};
-class QuicSessionTest : public ::testing::Test {
+class QuicSessionTest : public ::testing::TestWithParam<QuicVersion> {
protected:
QuicSessionTest()
- : connection_(new MockConnection(true)),
+ : connection_(new MockConnection(true, SupportedVersions(GetParam()))),
session_(connection_) {
headers_[":host"] = "www.google.com";
headers_[":path"] = "/index.hml";
@@ -175,25 +176,28 @@ class QuicSessionTest : public ::testing::Test {
SpdyHeaderBlock headers_;
};
-TEST_F(QuicSessionTest, PeerAddress) {
+INSTANTIATE_TEST_CASE_P(Tests, QuicSessionTest,
+ ::testing::ValuesIn(QuicSupportedVersions()));
+
+TEST_P(QuicSessionTest, PeerAddress) {
EXPECT_EQ(IPEndPoint(Loopback4(), kTestPort), session_.peer_address());
}
-TEST_F(QuicSessionTest, IsCryptoHandshakeConfirmed) {
+TEST_P(QuicSessionTest, IsCryptoHandshakeConfirmed) {
EXPECT_FALSE(session_.IsCryptoHandshakeConfirmed());
CryptoHandshakeMessage message;
session_.crypto_stream_.OnHandshakeMessage(message);
EXPECT_TRUE(session_.IsCryptoHandshakeConfirmed());
}
-TEST_F(QuicSessionTest, IsClosedStreamDefault) {
+TEST_P(QuicSessionTest, IsClosedStreamDefault) {
// Ensure that no streams are initially closed.
for (int i = kCryptoStreamId; i < 100; i++) {
- EXPECT_FALSE(session_.IsClosedStream(i));
+ EXPECT_FALSE(session_.IsClosedStream(i)) << "stream id: " << i;
}
}
-TEST_F(QuicSessionTest, ImplicitlyCreatedStreams) {
+TEST_P(QuicSessionTest, ImplicitlyCreatedStreams) {
ASSERT_TRUE(session_.GetIncomingReliableStream(7) != NULL);
// Both 3 and 5 should be implicitly created.
EXPECT_FALSE(session_.IsClosedStream(3));
@@ -202,13 +206,15 @@ TEST_F(QuicSessionTest, ImplicitlyCreatedStreams) {
ASSERT_TRUE(session_.GetIncomingReliableStream(3) != NULL);
}
-TEST_F(QuicSessionTest, IsClosedStreamLocallyCreated) {
+TEST_P(QuicSessionTest, IsClosedStreamLocallyCreated) {
TestStream* stream2 = session_.CreateOutgoingDataStream();
EXPECT_EQ(2u, stream2->id());
- QuicDataStreamPeer::SetHeadersDecompressed(stream2, true);
TestStream* stream4 = session_.CreateOutgoingDataStream();
EXPECT_EQ(4u, stream4->id());
- QuicDataStreamPeer::SetHeadersDecompressed(stream4, true);
+ if (GetParam() <= QUIC_VERSION_12) {
+ QuicDataStreamPeer::SetHeadersDecompressed(stream2, true);
+ QuicDataStreamPeer::SetHeadersDecompressed(stream4, true);
+ }
CheckClosedStreams();
CloseStream(4);
@@ -217,43 +223,63 @@ TEST_F(QuicSessionTest, IsClosedStreamLocallyCreated) {
CheckClosedStreams();
}
-TEST_F(QuicSessionTest, IsClosedStreamPeerCreated) {
- QuicDataStream* stream3 = session_.GetIncomingReliableStream(3);
- QuicDataStreamPeer::SetHeadersDecompressed(stream3, true);
- QuicDataStream* stream5 = session_.GetIncomingReliableStream(5);
- QuicDataStreamPeer::SetHeadersDecompressed(stream5, true);
+TEST_P(QuicSessionTest, IsClosedStreamPeerCreated) {
+ QuicStreamId stream_id1 = GetParam() > QUIC_VERSION_12 ? 5 : 3;
+ QuicStreamId stream_id2 = stream_id1 + 2;
+ QuicDataStream* stream1 = session_.GetIncomingReliableStream(stream_id1);
+ QuicDataStreamPeer::SetHeadersDecompressed(stream1, true);
+ QuicDataStream* stream2 = session_.GetIncomingReliableStream(stream_id2);
+ QuicDataStreamPeer::SetHeadersDecompressed(stream2, true);
CheckClosedStreams();
- CloseStream(3);
+ CloseStream(stream_id1);
CheckClosedStreams();
- CloseStream(5);
- // Create stream id 9, and implicitly 7
- QuicDataStream* stream9 = session_.GetIncomingReliableStream(9);
- QuicDataStreamPeer::SetHeadersDecompressed(stream9, true);
+ CloseStream(stream_id2);
+ // Create a stream explicitly, and another implicitly.
+ QuicDataStream* stream3 = session_.GetIncomingReliableStream(stream_id2 + 4);
+ QuicDataStreamPeer::SetHeadersDecompressed(stream3, true);
CheckClosedStreams();
- // Close 9, but make sure 7 is still not closed
- CloseStream(9);
+ // Close one, but make sure the other is still not closed
+ CloseStream(stream3->id());
CheckClosedStreams();
}
-TEST_F(QuicSessionTest, StreamIdTooLarge) {
- session_.GetIncomingReliableStream(3);
+TEST_P(QuicSessionTest, StreamIdTooLarge) {
+ QuicStreamId stream_id = GetParam() > QUIC_VERSION_12 ? 5 : 3;
+ session_.GetIncomingReliableStream(stream_id);
EXPECT_CALL(*connection_, SendConnectionClose(QUIC_INVALID_STREAM_ID));
- session_.GetIncomingReliableStream(105);
+ session_.GetIncomingReliableStream(stream_id + 102);
}
-TEST_F(QuicSessionTest, DecompressionError) {
- ReliableQuicStream* stream = session_.GetIncomingReliableStream(3);
- EXPECT_CALL(*connection_, SendConnectionClose(QUIC_DECOMPRESSION_FAILURE));
- const char data[] =
- "\0\0\0\0" // priority
- "\1\0\0\0" // headers id
- "\0\0\0\4" // length
- "abcd"; // invalid compressed data
- stream->ProcessRawData(data, arraysize(data));
+TEST_P(QuicSessionTest, DecompressionError) {
+ if (GetParam() > QUIC_VERSION_12) {
+ QuicHeadersStream* stream = QuicSessionPeer::GetHeadersStream(&session_);
+ const unsigned char data[] = {
+ 0x80, 0x03, 0x00, 0x01, // SPDY/3 SYN_STREAM frame
+ 0x00, 0x00, 0x00, 0x25, // flags/length
+ 0x00, 0x00, 0x00, 0x05, // stream id
+ 0x00, 0x00, 0x00, 0x00, // associated stream id
+ 0x00, 0x00,
+ 'a', 'b', 'c', 'd' // invalid compressed data
+ };
+ EXPECT_CALL(*connection_,
+ SendConnectionCloseWithDetails(QUIC_INVALID_HEADERS_STREAM_DATA,
+ "SPDY framing error."));
+ stream->ProcessRawData(reinterpret_cast<const char*>(data),
+ arraysize(data));
+ } else {
+ ReliableQuicStream* stream = session_.GetIncomingReliableStream(3);
+ const char data[] =
+ "\0\0\0\0" // priority
+ "\1\0\0\0" // headers id
+ "\0\0\0\4" // length
+ "abcd"; // invalid compressed data
+ EXPECT_CALL(*connection_, SendConnectionClose(QUIC_DECOMPRESSION_FAILURE));
+ stream->ProcessRawData(data, arraysize(data));
+ }
}
-TEST_F(QuicSessionTest, OnCanWrite) {
+TEST_P(QuicSessionTest, OnCanWrite) {
TestStream* stream2 = session_.CreateOutgoingDataStream();
TestStream* stream4 = session_.CreateOutgoingDataStream();
TestStream* stream6 = session_.CreateOutgoingDataStream();
@@ -273,7 +299,7 @@ TEST_F(QuicSessionTest, OnCanWrite) {
EXPECT_FALSE(session_.OnCanWrite());
}
-TEST_F(QuicSessionTest, BufferedHandshake) {
+TEST_P(QuicSessionTest, BufferedHandshake) {
EXPECT_FALSE(session_.HasPendingHandshake()); // Default value.
// Test that blocking other streams does not change our status.
@@ -320,7 +346,7 @@ TEST_F(QuicSessionTest, BufferedHandshake) {
EXPECT_FALSE(session_.HasPendingHandshake()); // Crypto stream wrote.
}
-TEST_F(QuicSessionTest, OnCanWriteWithClosedStream) {
+TEST_P(QuicSessionTest, OnCanWriteWithClosedStream) {
TestStream* stream2 = session_.CreateOutgoingDataStream();
TestStream* stream4 = session_.CreateOutgoingDataStream();
TestStream* stream6 = session_.CreateOutgoingDataStream();
@@ -337,7 +363,7 @@ TEST_F(QuicSessionTest, OnCanWriteWithClosedStream) {
}
// Regression test for http://crbug.com/248737
-TEST_F(QuicSessionTest, OutOfOrderHeaders) {
+TEST_P(QuicSessionTest, OutOfOrderHeaders) {
QuicSpdyCompressor compressor;
vector<QuicStreamFrame> frames;
QuicPacketHeader header;
@@ -371,7 +397,7 @@ TEST_F(QuicSessionTest, OutOfOrderHeaders) {
connection_->CloseConnection(QUIC_CONNECTION_TIMED_OUT, true);
}
-TEST_F(QuicSessionTest, SendGoAway) {
+TEST_P(QuicSessionTest, SendGoAway) {
// After sending a GoAway, ensure new incoming streams cannot be created and
// result in a RST being sent.
EXPECT_CALL(*connection_,
@@ -383,7 +409,7 @@ TEST_F(QuicSessionTest, SendGoAway) {
EXPECT_FALSE(session_.GetIncomingReliableStream(3u));
}
-TEST_F(QuicSessionTest, IncreasedTimeoutAfterCryptoHandshake) {
+TEST_P(QuicSessionTest, IncreasedTimeoutAfterCryptoHandshake) {
EXPECT_EQ(kDefaultInitialTimeoutSecs,
QuicConnectionPeer::GetNetworkTimeout(connection_).ToSeconds());
CryptoHandshakeMessage msg;
@@ -392,22 +418,24 @@ TEST_F(QuicSessionTest, IncreasedTimeoutAfterCryptoHandshake) {
QuicConnectionPeer::GetNetworkTimeout(connection_).ToSeconds());
}
-TEST_F(QuicSessionTest, ZombieStream) {
+TEST_P(QuicSessionTest, ZombieStream) {
+ QuicStreamId stream_id1 = GetParam() > QUIC_VERSION_12 ? 5 : 3;
+ QuicStreamId stream_id2 = stream_id1 + 2;
StrictMock<MockConnection>* connection =
- new StrictMock<MockConnection>(false);
+ new StrictMock<MockConnection>(false, SupportedVersions(GetParam()));
TestSession session(connection);
- TestStream* stream3 = session.CreateOutgoingDataStream();
- EXPECT_EQ(3u, stream3->id());
- TestStream* stream5 = session.CreateOutgoingDataStream();
- EXPECT_EQ(5u, stream5->id());
+ TestStream* stream1 = session.CreateOutgoingDataStream();
+ EXPECT_EQ(stream_id1, stream1->id());
+ TestStream* stream2 = session.CreateOutgoingDataStream();
+ EXPECT_EQ(stream_id2, stream2->id());
EXPECT_EQ(2u, session.GetNumOpenStreams());
// Reset the stream, but since the headers have not been decompressed
// it will become a zombie and will continue to process data
// until the headers are decompressed.
- EXPECT_CALL(*connection, SendRstStream(3, QUIC_STREAM_CANCELLED));
- session.SendRstStream(3, QUIC_STREAM_CANCELLED);
+ EXPECT_CALL(*connection, SendRstStream(stream_id1, QUIC_STREAM_CANCELLED));
+ session.SendRstStream(stream_id1, QUIC_STREAM_CANCELLED);
EXPECT_EQ(1u, session.GetNumOpenStreams());
@@ -419,12 +447,12 @@ TEST_F(QuicSessionTest, ZombieStream) {
QuicSpdyCompressor compressor;
string compressed_headers1 = compressor.CompressHeaders(headers_);
QuicStreamFrame frame1(
- stream3->id(), false, 0, MakeIOVector(compressed_headers1));
+ stream1->id(), false, 0, MakeIOVector(compressed_headers1));
// Process the second frame first. This will cause the headers to
// be queued up and processed after the first frame is processed.
frames.push_back(frame1);
- EXPECT_FALSE(stream3->headers_decompressed());
+ EXPECT_FALSE(stream1->headers_decompressed());
session.OnStreamFrames(frames);
EXPECT_EQ(1u, session.GetNumOpenStreams());
@@ -432,23 +460,25 @@ TEST_F(QuicSessionTest, ZombieStream) {
EXPECT_TRUE(connection->connected());
}
-TEST_F(QuicSessionTest, ZombieStreamConnectionClose) {
+TEST_P(QuicSessionTest, ZombieStreamConnectionClose) {
+ QuicStreamId stream_id1 = GetParam() > QUIC_VERSION_12 ? 5 : 3;
+ QuicStreamId stream_id2 = stream_id1 + 2;
StrictMock<MockConnection>* connection =
- new StrictMock<MockConnection>(false);
+ new StrictMock<MockConnection>(false, SupportedVersions(GetParam()));
TestSession session(connection);
- TestStream* stream3 = session.CreateOutgoingDataStream();
- EXPECT_EQ(3u, stream3->id());
- TestStream* stream5 = session.CreateOutgoingDataStream();
- EXPECT_EQ(5u, stream5->id());
+ TestStream* stream1 = session.CreateOutgoingDataStream();
+ EXPECT_EQ(stream_id1, stream1->id());
+ TestStream* stream2 = session.CreateOutgoingDataStream();
+ EXPECT_EQ(stream_id2, stream2->id());
EXPECT_EQ(2u, session.GetNumOpenStreams());
- stream3->CloseWriteSide();
+ stream1->CloseWriteSide();
// Reset the stream, but since the headers have not been decompressed
// it will become a zombie and will continue to process data
// until the headers are decompressed.
- EXPECT_CALL(*connection, SendRstStream(3, QUIC_STREAM_CANCELLED));
- session.SendRstStream(3, QUIC_STREAM_CANCELLED);
+ EXPECT_CALL(*connection, SendRstStream(stream_id1, QUIC_STREAM_CANCELLED));
+ session.SendRstStream(stream_id1, QUIC_STREAM_CANCELLED);
EXPECT_EQ(1u, session.GetNumOpenStreams());
@@ -457,20 +487,23 @@ TEST_F(QuicSessionTest, ZombieStreamConnectionClose) {
EXPECT_EQ(0u, session.GetNumOpenStreams());
}
-TEST_F(QuicSessionTest, RstStreamBeforeHeadersDecompressed) {
+TEST_P(QuicSessionTest, RstStreamBeforeHeadersDecompressed) {
+ QuicStreamId stream_id1 = GetParam() > QUIC_VERSION_12 ? 5 : 3;
// Send two bytes of payload.
- QuicStreamFrame data1(3, false, 0, MakeIOVector("HT"));
+ QuicStreamFrame data1(stream_id1, false, 0, MakeIOVector("HT"));
vector<QuicStreamFrame> frames;
frames.push_back(data1);
EXPECT_TRUE(session_.OnStreamFrames(frames));
EXPECT_EQ(1u, session_.GetNumOpenStreams());
- // Send a reset before the headers have been decompressed. This causes
- // an unrecoverable compression context state.
- EXPECT_CALL(*connection_, SendConnectionClose(
- QUIC_STREAM_RST_BEFORE_HEADERS_DECOMPRESSED));
+ if (GetParam() <= QUIC_VERSION_12) {
+ // Send a reset before the headers have been decompressed. This causes
+ // an unrecoverable compression context state.
+ EXPECT_CALL(*connection_, SendConnectionClose(
+ QUIC_STREAM_RST_BEFORE_HEADERS_DECOMPRESSED));
+ }
- QuicRstStreamFrame rst1(3, QUIC_STREAM_NO_ERROR);
+ QuicRstStreamFrame rst1(stream_id1, QUIC_STREAM_NO_ERROR);
session_.OnRstStream(rst1);
EXPECT_EQ(0u, session_.GetNumOpenStreams());
}
diff --git a/net/quic/quic_stream_sequencer.cc b/net/quic/quic_stream_sequencer.cc
index 02fec67..3e6e317c 100644
--- a/net/quic/quic_stream_sequencer.cc
+++ b/net/quic/quic_stream_sequencer.cc
@@ -19,7 +19,8 @@ QuicStreamSequencer::QuicStreamSequencer(ReliableQuicStream* quic_stream)
: stream_(quic_stream),
num_bytes_consumed_(0),
max_frame_memory_(numeric_limits<size_t>::max()),
- close_offset_(numeric_limits<QuicStreamOffset>::max()) {
+ close_offset_(numeric_limits<QuicStreamOffset>::max()),
+ blocked_(false) {
}
QuicStreamSequencer::QuicStreamSequencer(size_t max_frame_memory,
@@ -27,7 +28,8 @@ QuicStreamSequencer::QuicStreamSequencer(size_t max_frame_memory,
: stream_(quic_stream),
num_bytes_consumed_(0),
max_frame_memory_(max_frame_memory),
- close_offset_(numeric_limits<QuicStreamOffset>::max()) {
+ close_offset_(numeric_limits<QuicStreamOffset>::max()),
+ blocked_(false) {
if (max_frame_memory < kMaxPacketSize) {
LOG(DFATAL) << "Setting max frame memory to " << max_frame_memory
<< ". Some frames will be impossible to handle.";
@@ -92,7 +94,7 @@ bool QuicStreamSequencer::OnStreamFrame(const QuicStreamFrame& frame) {
IOVector data;
data.AppendIovec(frame.data.iovec(), frame.data.Size());
- if (byte_offset == num_bytes_consumed_) {
+ if (!blocked_ && byte_offset == num_bytes_consumed_) {
DVLOG(1) << "Processing byte offset " << byte_offset;
size_t bytes_consumed = 0;
for (size_t i = 0; i < data.Size(); ++i) {
@@ -143,19 +145,21 @@ void QuicStreamSequencer::CloseStreamAtOffset(QuicStreamOffset offset) {
}
bool QuicStreamSequencer::MaybeCloseStream() {
- if (IsClosed()) {
+ if (!blocked_ && IsClosed()) {
DVLOG(1) << "Passing up termination, as we've processed "
<< num_bytes_consumed_ << " of " << close_offset_
<< " bytes.";
// Technically it's an error if num_bytes_consumed isn't exactly
// equal, but error handling seems silly at this point.
stream_->OnFinRead();
+ frames_.clear();
return true;
}
return false;
}
int QuicStreamSequencer::GetReadableRegions(iovec* iov, size_t iov_len) {
+ DCHECK(!blocked_);
FrameMap::iterator it = frames_.begin();
size_t index = 0;
QuicStreamOffset offset = num_bytes_consumed_;
@@ -174,6 +178,7 @@ int QuicStreamSequencer::GetReadableRegions(iovec* iov, size_t iov_len) {
}
int QuicStreamSequencer::Readv(const struct iovec* iov, size_t iov_len) {
+ DCHECK(!blocked_);
FrameMap::iterator it = frames_.begin();
size_t iov_index = 0;
size_t iov_offset = 0;
@@ -216,6 +221,7 @@ int QuicStreamSequencer::Readv(const struct iovec* iov, size_t iov_len) {
}
void QuicStreamSequencer::MarkConsumed(size_t num_bytes_consumed) {
+ DCHECK(!blocked_);
size_t end_offset = num_bytes_consumed_ + num_bytes_consumed;
while (!frames_.empty() && end_offset != num_bytes_consumed_) {
FrameMap::iterator it = frames_.begin();
@@ -264,7 +270,12 @@ bool QuicStreamSequencer::IsDuplicate(const QuicStreamFrame& frame) const {
frames_.find(frame.offset) != frames_.end();
}
+void QuicStreamSequencer::SetBlockedUntilFlush() {
+ blocked_ = true;
+}
+
void QuicStreamSequencer::FlushBufferedFrames() {
+ blocked_ = false;
FrameMap::iterator it = frames_.find(num_bytes_consumed_);
while (it != frames_.end()) {
DVLOG(1) << "Flushing buffered packet at offset " << it->first;
@@ -288,6 +299,7 @@ void QuicStreamSequencer::FlushBufferedFrames() {
return;
}
}
+ MaybeCloseStream();
}
} // namespace net
diff --git a/net/quic/quic_stream_sequencer.h b/net/quic/quic_stream_sequencer.h
index e260124..3b478e1 100644
--- a/net/quic/quic_stream_sequencer.h
+++ b/net/quic/quic_stream_sequencer.h
@@ -75,6 +75,9 @@ class NET_EXPORT_PRIVATE QuicStreamSequencer {
// be processed.
void FlushBufferedFrames();
+ // Blocks processing of frames until |FlushBufferedFrames| is called.
+ void SetBlockedUntilFlush();
+
private:
friend class test::QuicStreamSequencerPeer;
@@ -93,6 +96,7 @@ class NET_EXPORT_PRIVATE QuicStreamSequencer {
// The offset, if any, we got a stream termination for. When this many bytes
// have been processed, the sequencer will be closed.
QuicStreamOffset close_offset_;
+ bool blocked_;
};
} // namespace net
diff --git a/net/quic/quic_stream_sequencer_test.cc b/net/quic/quic_stream_sequencer_test.cc
index f898e66..cc21176 100644
--- a/net/quic/quic_stream_sequencer_test.cc
+++ b/net/quic/quic_stream_sequencer_test.cc
@@ -188,6 +188,37 @@ TEST_F(QuicStreamSequencerTest, FullFrameConsumed) {
EXPECT_EQ(3u, sequencer_->num_bytes_consumed());
}
+TEST_F(QuicStreamSequencerTest, BlockedThenFullFrameConsumed) {
+ sequencer_->SetBlockedUntilFlush();
+
+ EXPECT_TRUE(sequencer_->OnFrame(0, "abc"));
+ EXPECT_EQ(1u, sequencer_->frames()->size());
+ EXPECT_EQ(0u, sequencer_->num_bytes_consumed());
+
+ EXPECT_CALL(stream_, ProcessRawData(StrEq("abc"), 3)).WillOnce(Return(3));
+ sequencer_->FlushBufferedFrames();
+ EXPECT_EQ(0u, sequencer_->frames()->size());
+ EXPECT_EQ(3u, sequencer_->num_bytes_consumed());
+
+ EXPECT_CALL(stream_, ProcessRawData(StrEq("def"), 3)).WillOnce(Return(3));
+ EXPECT_CALL(stream_, OnFinRead());
+ EXPECT_TRUE(sequencer_->OnFinFrame(3, "def"));
+}
+
+TEST_F(QuicStreamSequencerTest, BlockedThenFullFrameAndFinConsumed) {
+ sequencer_->SetBlockedUntilFlush();
+
+ EXPECT_TRUE(sequencer_->OnFinFrame(0, "abc"));
+ EXPECT_EQ(1u, sequencer_->frames()->size());
+ EXPECT_EQ(0u, sequencer_->num_bytes_consumed());
+
+ EXPECT_CALL(stream_, ProcessRawData(StrEq("abc"), 3)).WillOnce(Return(3));
+ EXPECT_CALL(stream_, OnFinRead());
+ sequencer_->FlushBufferedFrames();
+ EXPECT_EQ(0u, sequencer_->frames()->size());
+ EXPECT_EQ(3u, sequencer_->num_bytes_consumed());
+}
+
TEST_F(QuicStreamSequencerTest, EmptyFrame) {
EXPECT_CALL(stream_,
CloseConnectionWithDetails(QUIC_INVALID_STREAM_FRAME, _));
diff --git a/net/quic/quic_utils.cc b/net/quic/quic_utils.cc
index 0aff377..a2662c5 100644
--- a/net/quic/quic_utils.cc
+++ b/net/quic/quic_utils.cc
@@ -196,6 +196,7 @@ const char* QuicUtils::ErrorToString(QuicErrorCode error) {
RETURN_STRING_LITERAL(QUIC_PACKET_WRITE_ERROR);
RETURN_STRING_LITERAL(QUIC_PACKET_READ_ERROR);
RETURN_STRING_LITERAL(QUIC_INVALID_STREAM_FRAME);
+ RETURN_STRING_LITERAL(QUIC_INVALID_HEADERS_STREAM_DATA);
RETURN_STRING_LITERAL(QUIC_PROOF_INVALID);
RETURN_STRING_LITERAL(QUIC_CRYPTO_DUPLICATE_TAG);
RETURN_STRING_LITERAL(QUIC_CRYPTO_ENCRYPTION_LEVEL_INCORRECT);
diff --git a/net/quic/test_tools/crypto_test_utils.cc b/net/quic/test_tools/crypto_test_utils.cc
index a6b6cc1..2dbae1e 100644
--- a/net/quic/test_tools/crypto_test_utils.cc
+++ b/net/quic/test_tools/crypto_test_utils.cc
@@ -67,7 +67,7 @@ void MovePackets(PacketSavingConnection* source_conn,
size_t *inout_packet_index,
QuicCryptoStream* dest_stream,
PacketSavingConnection* dest_conn) {
- SimpleQuicFramer framer;
+ SimpleQuicFramer framer(source_conn->supported_versions());
CryptoFramer crypto_framer;
CryptoFramerVisitor crypto_visitor;
@@ -133,7 +133,8 @@ CryptoTestUtils::FakeClientOptions::FakeClientOptions()
int CryptoTestUtils::HandshakeWithFakeServer(
PacketSavingConnection* client_conn,
QuicCryptoClientStream* client) {
- PacketSavingConnection* server_conn = new PacketSavingConnection(true);
+ PacketSavingConnection* server_conn =
+ new PacketSavingConnection(true, client_conn->supported_versions());
TestSession server_session(server_conn, DefaultQuicConfig());
QuicCryptoServerConfig crypto_config(QuicCryptoServerConfig::TESTING,
diff --git a/net/quic/test_tools/quic_connection_peer.cc b/net/quic/test_tools/quic_connection_peer.cc
index 54410b3..1a4406d 100644
--- a/net/quic/test_tools/quic_connection_peer.cc
+++ b/net/quic/test_tools/quic_connection_peer.cc
@@ -205,5 +205,10 @@ void QuicConnectionPeer::SetWriter(QuicConnection* connection,
connection->writer_ = writer;
}
+// static
+void QuicConnectionPeer::CloseConnection(QuicConnection* connection) {
+ connection->connected_ = false;
+}
+
} // namespace test
} // namespace net
diff --git a/net/quic/test_tools/quic_connection_peer.h b/net/quic/test_tools/quic_connection_peer.h
index cd7395f..fab07ad 100644
--- a/net/quic/test_tools/quic_connection_peer.h
+++ b/net/quic/test_tools/quic_connection_peer.h
@@ -104,6 +104,7 @@ class QuicConnectionPeer {
static QuicPacketWriter* GetWriter(QuicConnection* connection);
static void SetWriter(QuicConnection* connection, QuicTestWriter* writer);
+ static void CloseConnection(QuicConnection* connection);
private:
DISALLOW_COPY_AND_ASSIGN(QuicConnectionPeer);
diff --git a/net/quic/test_tools/quic_session_peer.cc b/net/quic/test_tools/quic_session_peer.cc
index c1fe7e0..7d81e0b 100644
--- a/net/quic/test_tools/quic_session_peer.cc
+++ b/net/quic/test_tools/quic_session_peer.cc
@@ -22,6 +22,11 @@ void QuicSessionPeer::SetMaxOpenStreams(QuicSession* session,
}
// static
+QuicHeadersStream* QuicSessionPeer::GetHeadersStream(QuicSession* session) {
+ return session->headers_stream_.get();
+}
+
+// static
WriteBlockedList<QuicStreamId>* QuicSessionPeer::GetWriteblockedStreams(
QuicSession* session) {
return &session->write_blocked_streams_;
diff --git a/net/quic/test_tools/quic_session_peer.h b/net/quic/test_tools/quic_session_peer.h
index 3151d84..337ec59 100644
--- a/net/quic/test_tools/quic_session_peer.h
+++ b/net/quic/test_tools/quic_session_peer.h
@@ -11,8 +11,8 @@
namespace net {
class QuicDataStream;
+class QuicHeadersStream;
class QuicSession;
-class ReliableQuicStream;
namespace test {
@@ -20,6 +20,7 @@ class QuicSessionPeer {
public:
static void SetNextStreamId(QuicSession* session, QuicStreamId id);
static void SetMaxOpenStreams(QuicSession* session, uint32 max_streams);
+ static QuicHeadersStream* GetHeadersStream(QuicSession* session);
static WriteBlockedList<QuicStreamId>* GetWriteblockedStreams(
QuicSession* session);
static QuicDataStream* GetIncomingReliableStream(QuicSession* session,
diff --git a/net/quic/test_tools/quic_test_utils.cc b/net/quic/test_tools/quic_test_utils.cc
index d85f92f..1cf7e7b 100644
--- a/net/quic/test_tools/quic_test_utils.cc
+++ b/net/quic/test_tools/quic_test_utils.cc
@@ -262,6 +262,17 @@ MockConnection::MockConnection(QuicGuid guid,
helper_(helper()) {
}
+MockConnection::MockConnection(bool is_server,
+ const QuicVersionVector& supported_versions)
+ : QuicConnection(kTestGuid,
+ IPEndPoint(Loopback4(), kTestPort),
+ new testing::NiceMock<MockHelper>(),
+ new testing::NiceMock<MockPacketWriter>(),
+ is_server, supported_versions),
+ writer_(QuicConnectionPeer::GetWriter(this)),
+ helper_(helper()) {
+}
+
MockConnection::~MockConnection() {
}
@@ -273,6 +284,12 @@ PacketSavingConnection::PacketSavingConnection(bool is_server)
: MockConnection(is_server) {
}
+PacketSavingConnection::PacketSavingConnection(
+ bool is_server,
+ const QuicVersionVector& supported_versions)
+ : MockConnection(is_server, supported_versions) {
+}
+
PacketSavingConnection::~PacketSavingConnection() {
STLDeleteElements(&packets_);
STLDeleteElements(&encrypted_packets_);
@@ -512,6 +529,12 @@ QuicConfig DefaultQuicConfig() {
return config;
}
+QuicVersionVector SupportedVersions(QuicVersion version) {
+ QuicVersionVector versions;
+ versions.push_back(version);
+ return versions;
+}
+
bool TestDecompressorVisitor::OnDecompressedData(StringPiece data) {
data.AppendToString(&data_);
return true;
diff --git a/net/quic/test_tools/quic_test_utils.h b/net/quic/test_tools/quic_test_utils.h
index 06c3431..aed5f7c 100644
--- a/net/quic/test_tools/quic_test_utils.h
+++ b/net/quic/test_tools/quic_test_utils.h
@@ -63,6 +63,9 @@ size_t GetMinStreamFrameSize(QuicVersion version);
// Returns QuicConfig set to default values.
QuicConfig DefaultQuicConfig();
+// Returns a version vector consisting of |version|.
+QuicVersionVector SupportedVersions(QuicVersion version);
+
template<typename SaveType>
class ValueRestore {
public:
@@ -254,12 +257,13 @@ class MockConnection : public QuicConnection {
explicit MockConnection(bool is_server);
// Uses a MockHelper, GUID of 42.
- MockConnection(IPEndPoint address,
- bool is_server);
+ MockConnection(IPEndPoint address, bool is_server);
// Uses a MockHelper, and 127.0.0.1:123
- MockConnection(QuicGuid guid,
- bool is_server);
+ MockConnection(QuicGuid guid, bool is_server);
+
+ // Uses a Mock helper, GUID of 42, and 127.0.0.1:123.
+ MockConnection(bool is_server, const QuicVersionVector& supported_versions);
virtual ~MockConnection();
@@ -301,6 +305,10 @@ class MockConnection : public QuicConnection {
class PacketSavingConnection : public MockConnection {
public:
explicit PacketSavingConnection(bool is_server);
+
+ PacketSavingConnection(bool is_server,
+ const QuicVersionVector& supported_versions);
+
virtual ~PacketSavingConnection();
virtual bool SendOrQueuePacket(EncryptionLevel level,
@@ -334,6 +342,13 @@ class MockSession : public QuicSession {
QuicStreamOffset offset,
bool fin,
QuicAckNotifier::DelegateInterface*));
+ MOCK_METHOD2(OnStreamHeaders, void(QuicStreamId stream_id,
+ base::StringPiece headers_data));
+ MOCK_METHOD2(OnStreamHeadersPriority, void(QuicStreamId stream_id,
+ QuicPriority priority));
+ MOCK_METHOD3(OnStreamHeadersComplete, void(QuicStreamId stream_id,
+ bool fin,
+ size_t frame_len));
MOCK_METHOD0(IsHandshakeComplete, bool());
MOCK_METHOD0(IsCryptoHandshakeConfirmed, bool());
diff --git a/net/quic/test_tools/simple_quic_framer.cc b/net/quic/test_tools/simple_quic_framer.cc
index 7d6439d..ca3a16b 100644
--- a/net/quic/test_tools/simple_quic_framer.cc
+++ b/net/quic/test_tools/simple_quic_framer.cc
@@ -143,6 +143,10 @@ SimpleQuicFramer::SimpleQuicFramer()
: framer_(QuicSupportedVersions(), QuicTime::Zero(), true) {
}
+SimpleQuicFramer::SimpleQuicFramer(const QuicVersionVector& supported_versions)
+ : framer_(supported_versions, QuicTime::Zero(), true) {
+}
+
SimpleQuicFramer::~SimpleQuicFramer() {
}
diff --git a/net/quic/test_tools/simple_quic_framer.h b/net/quic/test_tools/simple_quic_framer.h
index 4416019..3a112e1 100644
--- a/net/quic/test_tools/simple_quic_framer.h
+++ b/net/quic/test_tools/simple_quic_framer.h
@@ -29,6 +29,7 @@ class SimpleFramerVisitor;
class SimpleQuicFramer {
public:
SimpleQuicFramer();
+ explicit SimpleQuicFramer(const QuicVersionVector& supported_versions);
~SimpleQuicFramer();
bool ProcessPacket(const QuicEncryptedPacket& packet);
diff --git a/net/spdy/spdy_protocol.h b/net/spdy/spdy_protocol.h
index 3c891a5..d361cae 100644
--- a/net/spdy/spdy_protocol.h
+++ b/net/spdy/spdy_protocol.h
@@ -373,7 +373,7 @@ class SpdyFrameVisitor;
// Intermediate representation for SPDY frames.
// TODO(hkhalil): Rename this class to SpdyFrame when the existing SpdyFrame is
// gone.
-class SpdyFrameIR {
+class NET_EXPORT_PRIVATE SpdyFrameIR {
public:
virtual ~SpdyFrameIR() {}
@@ -388,7 +388,7 @@ class SpdyFrameIR {
// Abstract class intended to be inherited by IRs that have a stream associated
// to them.
-class SpdyFrameWithStreamIdIR : public SpdyFrameIR {
+class NET_EXPORT_PRIVATE SpdyFrameWithStreamIdIR : public SpdyFrameIR {
public:
virtual ~SpdyFrameWithStreamIdIR() {}
SpdyStreamId stream_id() const { return stream_id_; }
@@ -410,7 +410,7 @@ class SpdyFrameWithStreamIdIR : public SpdyFrameIR {
// Abstract class intended to be inherited by IRs that have the option of a FIN
// flag. Implies SpdyFrameWithStreamIdIR.
-class SpdyFrameWithFinIR : public SpdyFrameWithStreamIdIR {
+class NET_EXPORT_PRIVATE SpdyFrameWithFinIR : public SpdyFrameWithStreamIdIR {
public:
virtual ~SpdyFrameWithFinIR() {}
bool fin() const { return fin_; }
@@ -521,7 +521,7 @@ class NET_EXPORT_PRIVATE SpdySynStreamIR
DISALLOW_COPY_AND_ASSIGN(SpdySynStreamIR);
};
-class SpdySynReplyIR : public SpdyFrameWithNameValueBlockIR {
+class NET_EXPORT_PRIVATE SpdySynReplyIR : public SpdyFrameWithNameValueBlockIR {
public:
explicit SpdySynReplyIR(SpdyStreamId stream_id)
: SpdyFrameWithNameValueBlockIR(stream_id) {}
@@ -532,7 +532,7 @@ class SpdySynReplyIR : public SpdyFrameWithNameValueBlockIR {
DISALLOW_COPY_AND_ASSIGN(SpdySynReplyIR);
};
-class SpdyRstStreamIR : public SpdyFrameWithStreamIdIR {
+class NET_EXPORT_PRIVATE SpdyRstStreamIR : public SpdyFrameWithStreamIdIR {
public:
SpdyRstStreamIR(SpdyStreamId stream_id, SpdyRstStreamStatus status)
: SpdyFrameWithStreamIdIR(stream_id) {
@@ -555,7 +555,7 @@ class SpdyRstStreamIR : public SpdyFrameWithStreamIdIR {
DISALLOW_COPY_AND_ASSIGN(SpdyRstStreamIR);
};
-class SpdySettingsIR : public SpdyFrameIR {
+class NET_EXPORT_PRIVATE SpdySettingsIR : public SpdyFrameIR {
public:
// Associates flags with a value.
struct Value {
@@ -598,7 +598,7 @@ class SpdySettingsIR : public SpdyFrameIR {
DISALLOW_COPY_AND_ASSIGN(SpdySettingsIR);
};
-class SpdyPingIR : public SpdyFrameIR {
+class NET_EXPORT_PRIVATE SpdyPingIR : public SpdyFrameIR {
public:
explicit SpdyPingIR(SpdyPingId id) : id_(id) {}
SpdyPingId id() const { return id_; }
@@ -611,7 +611,7 @@ class SpdyPingIR : public SpdyFrameIR {
DISALLOW_COPY_AND_ASSIGN(SpdyPingIR);
};
-class SpdyGoAwayIR : public SpdyFrameIR {
+class NET_EXPORT_PRIVATE SpdyGoAwayIR : public SpdyFrameIR {
public:
SpdyGoAwayIR(SpdyStreamId last_good_stream_id, SpdyGoAwayStatus status) {
set_last_good_stream_id(last_good_stream_id);
@@ -638,7 +638,7 @@ class SpdyGoAwayIR : public SpdyFrameIR {
DISALLOW_COPY_AND_ASSIGN(SpdyGoAwayIR);
};
-class SpdyHeadersIR : public SpdyFrameWithNameValueBlockIR {
+class NET_EXPORT_PRIVATE SpdyHeadersIR : public SpdyFrameWithNameValueBlockIR {
public:
explicit SpdyHeadersIR(SpdyStreamId stream_id)
: SpdyFrameWithNameValueBlockIR(stream_id) {}
@@ -649,7 +649,7 @@ class SpdyHeadersIR : public SpdyFrameWithNameValueBlockIR {
DISALLOW_COPY_AND_ASSIGN(SpdyHeadersIR);
};
-class SpdyWindowUpdateIR : public SpdyFrameWithStreamIdIR {
+class NET_EXPORT_PRIVATE SpdyWindowUpdateIR : public SpdyFrameWithStreamIdIR {
public:
SpdyWindowUpdateIR(SpdyStreamId stream_id, int32 delta)
: SpdyFrameWithStreamIdIR(stream_id) {
@@ -670,7 +670,7 @@ class SpdyWindowUpdateIR : public SpdyFrameWithStreamIdIR {
DISALLOW_COPY_AND_ASSIGN(SpdyWindowUpdateIR);
};
-class SpdyCredentialIR : public SpdyFrameIR {
+class NET_EXPORT_PRIVATE SpdyCredentialIR : public SpdyFrameIR {
public:
typedef std::vector<std::string> CertificateList;
@@ -713,7 +713,8 @@ class NET_EXPORT_PRIVATE SpdyBlockedIR
DISALLOW_COPY_AND_ASSIGN(SpdyBlockedIR);
};
-class SpdyPushPromiseIR : public SpdyFrameWithNameValueBlockIR {
+class NET_EXPORT_PRIVATE SpdyPushPromiseIR
+ : public SpdyFrameWithNameValueBlockIR {
public:
SpdyPushPromiseIR(SpdyStreamId stream_id, SpdyStreamId promised_stream_id)
: SpdyFrameWithNameValueBlockIR(stream_id),
diff --git a/net/tools/quic/end_to_end_test.cc b/net/tools/quic/end_to_end_test.cc
index 2b94b0f..6576f920 100644
--- a/net/tools/quic/end_to_end_test.cc
+++ b/net/tools/quic/end_to_end_test.cc
@@ -20,6 +20,7 @@
#include "net/quic/quic_sent_packet_manager.h"
#include "net/quic/test_tools/quic_connection_peer.h"
#include "net/quic/test_tools/quic_session_peer.h"
+#include "net/quic/test_tools/quic_test_utils.h"
#include "net/quic/test_tools/quic_test_writer.h"
#include "net/quic/test_tools/reliable_quic_stream_peer.h"
#include "net/tools/quic/quic_epoll_connection_helper.h"
@@ -98,19 +99,24 @@ struct TestParams {
vector<TestParams> GetTestParams() {
vector<TestParams> params;
QuicVersionVector all_supported_versions = QuicSupportedVersions();
-
for (int use_pacing = 0; use_pacing < 2; ++use_pacing) {
+ // TODO(rch): since 13 is not 0-RTT compatible with 12, we can not
+ // have the client support both at the same time.
+#if 0
// Add an entry for server and client supporting all versions.
params.push_back(TestParams(all_supported_versions,
all_supported_versions,
all_supported_versions[0],
use_pacing != 0));
+#endif
// Test client supporting 1 version and server supporting all versions.
// Simulate an old client and exercise version downgrade in the server.
// No protocol negotiation should occur. Skip the i = 0 case because it
// is essentially the same as the default case.
- for (size_t i = 1; i < all_supported_versions.size(); ++i) {
+ // TODO(rch): When QUIC_VERSION_12 is removed, change the intialization
+ // of i from 0 back to 1.
+ for (size_t i = 0; i < all_supported_versions.size(); ++i) {
QuicVersionVector client_supported_versions;
client_supported_versions.push_back(all_supported_versions[i]);
params.push_back(TestParams(client_supported_versions,
@@ -119,6 +125,9 @@ vector<TestParams> GetTestParams() {
use_pacing != 0));
}
+ // TODO(rch): since 13 is not 0-RTT compatible with 12, we can not
+ // have the client support both at the same time.
+#if 0
// Test client supporting all versions and server supporting 1 version.
// Simulate an old server and exercise version downgrade in the client.
// Protocol negotiation should occur. Skip the i = 0 case because it is
@@ -131,6 +140,7 @@ vector<TestParams> GetTestParams() {
server_supported_versions[0],
use_pacing != 0));
}
+#endif
}
return params;
}
diff --git a/net/tools/quic/quic_client.h b/net/tools/quic/quic_client.h
index 05b3054..78d00d6 100644
--- a/net/tools/quic/quic_client.h
+++ b/net/tools/quic/quic_client.h
@@ -73,7 +73,7 @@ class QuicClient : public EpollCallbackInterface,
// each to complete.
void SendRequestsAndWaitForResponse(const CommandLine::StringVector& args);
- // Returns a newly created CreateReliableClientStream, owned by the
+ // Returns a newly created QuicSpdyClientStream, owned by the
// QuicClient.
QuicSpdyClientStream* CreateReliableClientStream();
@@ -142,6 +142,11 @@ class QuicClient : public EpollCallbackInterface,
crypto_config_.SetChannelIDSigner(signer);
}
+ void SetSupportedVersions(const QuicVersionVector& versions) {
+ DCHECK(!session_.get());
+ supported_versions_ = versions;
+ }
+
protected:
virtual QuicGuid GenerateGuid();
virtual QuicEpollConnectionHelper* CreateQuicConnectionHelper();
diff --git a/net/tools/quic/quic_client_session_test.cc b/net/tools/quic/quic_client_session_test.cc
index c893c20..23d79a7 100644
--- a/net/tools/quic/quic_client_session_test.cc
+++ b/net/tools/quic/quic_client_session_test.cc
@@ -16,6 +16,7 @@
using net::test::CryptoTestUtils;
using net::test::DefaultQuicConfig;
using net::test::PacketSavingConnection;
+using net::test::SupportedVersions;
using testing::_;
namespace net {
@@ -25,10 +26,12 @@ namespace {
const char kServerHostname[] = "www.example.com";
-class ToolsQuicClientSessionTest : public ::testing::Test {
+class ToolsQuicClientSessionTest
+ : public ::testing::TestWithParam<QuicVersion> {
protected:
ToolsQuicClientSessionTest()
- : connection_(new PacketSavingConnection(false)) {
+ : connection_(new PacketSavingConnection(false,
+ SupportedVersions(GetParam()))) {
crypto_config_.SetDefaults();
session_.reset(new QuicClientSession(kServerHostname, DefaultQuicConfig(),
connection_, &crypto_config_));
@@ -46,11 +49,14 @@ class ToolsQuicClientSessionTest : public ::testing::Test {
QuicCryptoClientConfig crypto_config_;
};
-TEST_F(ToolsQuicClientSessionTest, CryptoConnect) {
+INSTANTIATE_TEST_CASE_P(Tests, ToolsQuicClientSessionTest,
+ ::testing::ValuesIn(QuicSupportedVersions()));
+
+TEST_P(ToolsQuicClientSessionTest, CryptoConnect) {
CompleteCryptoHandshake();
}
-TEST_F(ToolsQuicClientSessionTest, MaxNumStreams) {
+TEST_P(ToolsQuicClientSessionTest, MaxNumStreams) {
session_->config()->set_max_streams_per_connection(1, 1);
// FLAGS_max_streams_per_connection = 1;
// Initialize crypto before the client session will create a stream.
@@ -67,7 +73,7 @@ TEST_F(ToolsQuicClientSessionTest, MaxNumStreams) {
EXPECT_TRUE(stream);
}
-TEST_F(ToolsQuicClientSessionTest, GoAwayReceived) {
+TEST_P(ToolsQuicClientSessionTest, GoAwayReceived) {
CompleteCryptoHandshake();
// After receiving a GoAway, I should no longer be able to create outgoing
diff --git a/net/tools/quic/quic_server_session_test.cc b/net/tools/quic/quic_server_session_test.cc
index a3dd996..7482fed 100644
--- a/net/tools/quic/quic_server_session_test.cc
+++ b/net/tools/quic/quic_server_session_test.cc
@@ -20,6 +20,7 @@ using __gnu_cxx::vector;
using net::test::MockConnection;
using net::test::QuicConnectionPeer;
using net::test::QuicDataStreamPeer;
+using net::test::SupportedVersions;
using testing::_;
using testing::StrictMock;
@@ -86,7 +87,7 @@ class TestQuicQuicServerSession : public QuicServerSession {
namespace {
-class QuicServerSessionTest : public ::testing::Test {
+class QuicServerSessionTest : public ::testing::TestWithParam<QuicVersion> {
protected:
QuicServerSessionTest()
: crypto_config_(QuicCryptoServerConfig::TESTING,
@@ -94,7 +95,8 @@ class QuicServerSessionTest : public ::testing::Test {
config_.SetDefaults();
config_.set_max_streams_per_connection(3, 3);
- connection_ = new MockConnection(true);
+ connection_ =
+ new StrictMock<MockConnection>(true, SupportedVersions(GetParam()));
session_.reset(new TestQuicQuicServerSession(
config_, connection_, &owner_));
session_->InitializeSession(crypto_config_);
@@ -109,17 +111,21 @@ class QuicServerSessionTest : public ::testing::Test {
}
StrictMock<MockQuicSessionOwner> owner_;
- MockConnection* connection_;
+ StrictMock<MockConnection>* connection_;
QuicConfig config_;
QuicCryptoServerConfig crypto_config_;
scoped_ptr<TestQuicQuicServerSession> session_;
QuicConnectionVisitorInterface* visitor_;
};
-TEST_F(QuicServerSessionTest, CloseStreamDueToReset) {
+INSTANTIATE_TEST_CASE_P(Tests, QuicServerSessionTest,
+ ::testing::ValuesIn(QuicSupportedVersions()));
+
+TEST_P(QuicServerSessionTest, CloseStreamDueToReset) {
+ QuicStreamId stream_id = GetParam() == QUIC_VERSION_12 ? 3 : 5;
// Open a stream, then reset it.
// Send two bytes of payload to open it.
- QuicStreamFrame data1(3, false, 0, MakeIOVector("HT"));
+ QuicStreamFrame data1(stream_id, false, 0, MakeIOVector("HT"));
vector<QuicStreamFrame> frames;
frames.push_back(data1);
EXPECT_TRUE(visitor_->OnStreamFrames(frames));
@@ -127,10 +133,10 @@ TEST_F(QuicServerSessionTest, CloseStreamDueToReset) {
// Pretend we got full headers, so we won't trigger the 'unrecoverable
// compression context' state.
- MarkHeadersReadForStream(3);
+ MarkHeadersReadForStream(stream_id);
// Send a reset.
- QuicRstStreamFrame rst1(3, QUIC_STREAM_NO_ERROR);
+ QuicRstStreamFrame rst1(stream_id, QUIC_STREAM_NO_ERROR);
visitor_->OnRstStream(rst1);
EXPECT_EQ(0u, session_->GetNumOpenStreams());
@@ -141,30 +147,40 @@ TEST_F(QuicServerSessionTest, CloseStreamDueToReset) {
EXPECT_EQ(0u, session_->GetNumOpenStreams());
}
-TEST_F(QuicServerSessionTest, NeverOpenStreamDueToReset) {
+TEST_P(QuicServerSessionTest, NeverOpenStreamDueToReset) {
+ QuicStreamId stream_id = GetParam() == QUIC_VERSION_12 ? 3 : 5;
// Send a reset.
- QuicRstStreamFrame rst1(3, QUIC_STREAM_NO_ERROR);
+ QuicRstStreamFrame rst1(stream_id, QUIC_STREAM_NO_ERROR);
visitor_->OnRstStream(rst1);
EXPECT_EQ(0u, session_->GetNumOpenStreams());
// Send two bytes of payload.
- QuicStreamFrame data1(3, false, 0, MakeIOVector("HT"));
+ QuicStreamFrame data1(stream_id, false, 0, MakeIOVector("HT"));
vector<QuicStreamFrame> frames;
frames.push_back(data1);
- // When we get data for the closed stream, it implies the far side has
- // compressed some headers. As a result we're going to bail due to
- // unrecoverable compression context state.
- EXPECT_CALL(*connection_, SendConnectionClose(
- QUIC_STREAM_RST_BEFORE_HEADERS_DECOMPRESSED));
- EXPECT_FALSE(visitor_->OnStreamFrames(frames));
+ if (connection_->version() > QUIC_VERSION_12) {
+ EXPECT_TRUE(visitor_->OnStreamFrames(frames));
+ } else {
+ // When we get data for the closed stream, it implies the far side has
+ // compressed some headers. As a result we're going to bail due to
+ // unrecoverable compression context state.
+ EXPECT_CALL(*connection_, SendConnectionClose(
+ QUIC_STREAM_RST_BEFORE_HEADERS_DECOMPRESSED));
+ EXPECT_FALSE(visitor_->OnStreamFrames(frames));
+ }
// The stream should never be opened, now that the reset is received.
EXPECT_EQ(0u, session_->GetNumOpenStreams());
}
-TEST_F(QuicServerSessionTest, GoOverPrematureClosedStreamLimit) {
- QuicStreamFrame data1(3, false, 0, MakeIOVector("H"));
+TEST_P(QuicServerSessionTest, GoOverPrematureClosedStreamLimit) {
+ QuicStreamId stream_id = GetParam() == QUIC_VERSION_12 ? 3 : 5;
+ if (connection_->version() > QUIC_VERSION_12) {
+ // The prematurely closed stream limit is v12 specific.
+ return;
+ }
+ QuicStreamFrame data1(stream_id, false, 0, MakeIOVector("H"));
vector<QuicStreamFrame> frames;
frames.push_back(data1);
@@ -177,58 +193,65 @@ TEST_F(QuicServerSessionTest, GoOverPrematureClosedStreamLimit) {
EXPECT_FALSE(visitor_->OnStreamFrames(frames));
}
-TEST_F(QuicServerSessionTest, AcceptClosedStream) {
+TEST_P(QuicServerSessionTest, AcceptClosedStream) {
+ QuicStreamId stream_id = GetParam() == QUIC_VERSION_12 ? 3 : 5;
vector<QuicStreamFrame> frames;
// Send (empty) compressed headers followed by two bytes of data.
- frames.push_back(
- QuicStreamFrame(3, false, 0, MakeIOVector("\1\0\0\0\0\0\0\0HT")));
- frames.push_back(
- QuicStreamFrame(5, false, 0, MakeIOVector("\2\0\0\0\0\0\0\0HT")));
+ frames.push_back(QuicStreamFrame(stream_id, false, 0,
+ MakeIOVector("\1\0\0\0\0\0\0\0HT")));
+ frames.push_back(QuicStreamFrame(stream_id + 2, false, 0,
+ MakeIOVector("\2\0\0\0\0\0\0\0HT")));
EXPECT_TRUE(visitor_->OnStreamFrames(frames));
// Pretend we got full headers, so we won't trigger the 'unercoverable
// compression context' state.
- MarkHeadersReadForStream(3);
+ MarkHeadersReadForStream(stream_id);
// Send a reset.
- QuicRstStreamFrame rst(3, QUIC_STREAM_NO_ERROR);
+ QuicRstStreamFrame rst(stream_id, QUIC_STREAM_NO_ERROR);
visitor_->OnRstStream(rst);
// If we were tracking, we'd probably want to reject this because it's data
// past the reset point of stream 3. As it's a closed stream we just drop the
// data on the floor, but accept the packet because it has data for stream 5.
frames.clear();
- frames.push_back(QuicStreamFrame(3, false, 2, MakeIOVector("TP")));
- frames.push_back(QuicStreamFrame(5, false, 2, MakeIOVector("TP")));
+ frames.push_back(QuicStreamFrame(stream_id, false, 2, MakeIOVector("TP")));
+ frames.push_back(QuicStreamFrame(stream_id + 2, false, 2,
+ MakeIOVector("TP")));
EXPECT_TRUE(visitor_->OnStreamFrames(frames));
}
-TEST_F(QuicServerSessionTest, MaxNumConnections) {
+TEST_P(QuicServerSessionTest, MaxNumConnections) {
+ QuicStreamId stream_id = GetParam() == QUIC_VERSION_12 ? 3 : 5;
EXPECT_EQ(0u, session_->GetNumOpenStreams());
- EXPECT_TRUE(
- QuicServerSessionPeer::GetIncomingReliableStream(session_.get(), 3));
- EXPECT_TRUE(
- QuicServerSessionPeer::GetIncomingReliableStream(session_.get(), 5));
- EXPECT_TRUE(
- QuicServerSessionPeer::GetIncomingReliableStream(session_.get(), 7));
- EXPECT_FALSE(
- QuicServerSessionPeer::GetIncomingReliableStream(session_.get(), 9));
+ EXPECT_TRUE(QuicServerSessionPeer::GetIncomingReliableStream(session_.get(),
+ stream_id));
+ EXPECT_TRUE(QuicServerSessionPeer::GetIncomingReliableStream(session_.get(),
+ stream_id + 2));
+ EXPECT_TRUE(QuicServerSessionPeer::GetIncomingReliableStream(session_.get(),
+ stream_id + 4));
+ EXPECT_CALL(*connection_, SendConnectionClose(QUIC_TOO_MANY_OPEN_STREAMS));
+ EXPECT_FALSE(QuicServerSessionPeer::GetIncomingReliableStream(session_.get(),
+ stream_id + 6));
}
-TEST_F(QuicServerSessionTest, MaxNumConnectionsImplicit) {
+TEST_P(QuicServerSessionTest, MaxNumConnectionsImplicit) {
+ QuicStreamId stream_id = GetParam() == QUIC_VERSION_12 ? 3 : 5;
EXPECT_EQ(0u, session_->GetNumOpenStreams());
- EXPECT_TRUE(
- QuicServerSessionPeer::GetIncomingReliableStream(session_.get(), 3));
- // Implicitly opens two more streams before 9.
- EXPECT_FALSE(
- QuicServerSessionPeer::GetIncomingReliableStream(session_.get(), 9));
+ EXPECT_TRUE(QuicServerSessionPeer::GetIncomingReliableStream(session_.get(),
+ stream_id));
+ // Implicitly opens two more streams.
+ EXPECT_CALL(*connection_, SendConnectionClose(QUIC_TOO_MANY_OPEN_STREAMS));
+ EXPECT_FALSE(QuicServerSessionPeer::GetIncomingReliableStream(session_.get(),
+ stream_id + 6));
}
-TEST_F(QuicServerSessionTest, GetEvenIncomingError) {
+TEST_P(QuicServerSessionTest, GetEvenIncomingError) {
// Incoming streams on the server session must be odd.
+ EXPECT_CALL(*connection_, SendConnectionClose(QUIC_INVALID_STREAM_ID));
EXPECT_EQ(NULL,
QuicServerSessionPeer::GetIncomingReliableStream(
- session_.get(), 2));
+ session_.get(), 4));
}
} // namespace
diff --git a/net/tools/quic/quic_spdy_client_stream.cc b/net/tools/quic/quic_spdy_client_stream.cc
index 1956c79..38f8a54 100644
--- a/net/tools/quic/quic_spdy_client_stream.cc
+++ b/net/tools/quic/quic_spdy_client_stream.cc
@@ -20,7 +20,9 @@ QuicSpdyClientStream::QuicSpdyClientStream(QuicStreamId id,
QuicClientSession* session)
: QuicDataStream(id, session),
read_buf_(new GrowableIOBuffer()),
- response_headers_received_(false) {
+ response_headers_received_(false),
+ header_bytes_read_(0),
+ header_bytes_written_(0) {
}
QuicSpdyClientStream::~QuicSpdyClientStream() {
@@ -35,22 +37,30 @@ bool QuicSpdyClientStream::OnStreamFrame(const QuicStreamFrame& frame) {
return QuicDataStream::OnStreamFrame(frame);
}
-uint32 QuicSpdyClientStream::ProcessData(const char* data, uint32 length) {
- uint32 total_bytes_processed = 0;
+void QuicSpdyClientStream::OnStreamHeadersComplete(bool fin,
+ size_t frame_len) {
+ header_bytes_read_ = frame_len;
+ QuicDataStream::OnStreamHeadersComplete(fin, frame_len);
+}
+
+uint32 QuicSpdyClientStream::ProcessData(const char* data,
+ uint32 data_len) {
+ int total_bytes_processed = 0;
// Are we still reading the response headers.
if (!response_headers_received_) {
// Grow the read buffer if necessary.
- if (read_buf_->RemainingCapacity() < (int)length) {
+ if (read_buf_->RemainingCapacity() < (int)data_len) {
read_buf_->SetCapacity(read_buf_->capacity() + kHeaderBufInitialSize);
}
- memcpy(read_buf_->data(), data, length);
- read_buf_->set_offset(read_buf_->offset() + length);
+ memcpy(read_buf_->data(), data, data_len);
+ read_buf_->set_offset(read_buf_->offset() + data_len);
ParseResponseHeaders();
} else {
- data_.append(data + total_bytes_processed, length - total_bytes_processed);
+ data_.append(data + total_bytes_processed,
+ data_len - total_bytes_processed);
}
- return length;
+ return data_len;
}
void QuicSpdyClientStream::OnFinRead() {
@@ -71,15 +81,23 @@ ssize_t QuicSpdyClientStream::SendRequest(const BalsaHeaders& headers,
SpdyUtils::RequestHeadersToSpdyHeaders(headers);
bool send_fin_with_headers = fin && body.empty();
- string headers_string = session()->compressor()->CompressHeadersWithPriority(
- priority(), header_block);
- WriteOrBufferData(headers_string, send_fin_with_headers);
+ size_t bytes_sent = body.size();
+ if (version() > QUIC_VERSION_12) {
+ header_bytes_written_ = WriteHeaders(header_block, send_fin_with_headers);
+ bytes_sent += header_bytes_written_;
+ } else {
+ string headers_string =
+ session()->compressor()->CompressHeadersWithPriority(
+ priority(), header_block);
+ WriteOrBufferData(headers_string, send_fin_with_headers);
+ bytes_sent += headers_string.length();
+ }
if (!body.empty()) {
WriteOrBufferData(body, fin);
}
- return headers_string.size() + body.size();
+ return bytes_sent;
}
int QuicSpdyClientStream::ParseResponseHeaders() {
diff --git a/net/tools/quic/quic_spdy_client_stream.h b/net/tools/quic/quic_spdy_client_stream.h
index 2b17205..1e60467 100644
--- a/net/tools/quic/quic_spdy_client_stream.h
+++ b/net/tools/quic/quic_spdy_client_stream.h
@@ -33,6 +33,9 @@ class QuicSpdyClientStream : public QuicDataStream {
// SPDY/HTTP does not support bidirectional streaming.
virtual bool OnStreamFrame(const QuicStreamFrame& frame) OVERRIDE;
+ // Override the base class to store the size of the headers.
+ virtual void OnStreamHeadersComplete(bool fin, size_t frame_len) OVERRIDE;
+
// ReliableQuicStream implementation called by the session when there's
// data for us.
virtual uint32 ProcessData(const char* data, uint32 data_len) OVERRIDE;
@@ -54,6 +57,10 @@ class QuicSpdyClientStream : public QuicDataStream {
// Returns whatever headers have been received for this stream.
const BalsaHeaders& headers() { return headers_; }
+ size_t header_bytes_read() const { return header_bytes_read_; }
+
+ size_t header_bytes_written() const { return header_bytes_written_; }
+
// While the server's set_priority shouldn't be called externally, the creator
// of client-side streams should be able to set the priority.
using QuicDataStream::set_priority;
@@ -66,6 +73,8 @@ class QuicSpdyClientStream : public QuicDataStream {
scoped_refptr<GrowableIOBuffer> read_buf_;
bool response_headers_received_;
+ size_t header_bytes_read_;
+ size_t header_bytes_written_;
};
} // namespace tools
diff --git a/net/tools/quic/quic_spdy_client_stream_test.cc b/net/tools/quic/quic_spdy_client_stream_test.cc
index 44c640c..000e94d 100644
--- a/net/tools/quic/quic_spdy_client_stream_test.cc
+++ b/net/tools/quic/quic_spdy_client_stream_test.cc
@@ -16,21 +16,23 @@
#include "testing/gtest/include/gtest/gtest.h"
using net::test::DefaultQuicConfig;
+using net::test::SupportedVersions;
using testing::TestWithParam;
+using testing::StrictMock;
namespace net {
namespace tools {
namespace test {
namespace {
-class QuicSpdyClientStreamTest : public ::testing::Test {
+class QuicSpdyClientStreamTest : public TestWithParam<QuicVersion> {
public:
QuicSpdyClientStreamTest()
- : session_("example.com", DefaultQuicConfig(),
- new MockConnection(false),
+ : connection_(new StrictMock<MockConnection>(
+ false, SupportedVersions(GetParam()))),
+ session_("example.com", DefaultQuicConfig(), connection_,
&crypto_config_),
body_("hello world") {
- session_.config()->SetDefaults();
crypto_config_.SetDefaults();
headers_.SetResponseFirstlineFromStringPieces("HTTP/1.1", "200", "Ok");
@@ -40,6 +42,7 @@ class QuicSpdyClientStreamTest : public ::testing::Test {
stream_.reset(new QuicSpdyClientStream(3, &session_));
}
+ StrictMock<MockConnection>* connection_;
QuicClientSession session_;
scoped_ptr<QuicSpdyClientStream> stream_;
BalsaHeaders headers_;
@@ -48,7 +51,10 @@ class QuicSpdyClientStreamTest : public ::testing::Test {
QuicCryptoClientConfig crypto_config_;
};
-TEST_F(QuicSpdyClientStreamTest, TestFraming) {
+INSTANTIATE_TEST_CASE_P(Tests, QuicSpdyClientStreamTest,
+ ::testing::ValuesIn(QuicSupportedVersions()));
+
+TEST_P(QuicSpdyClientStreamTest, TestFraming) {
EXPECT_EQ(headers_string_.size(), stream_->ProcessData(
headers_string_.c_str(), headers_string_.size()));
EXPECT_EQ(body_.size(),
@@ -57,7 +63,7 @@ TEST_F(QuicSpdyClientStreamTest, TestFraming) {
EXPECT_EQ(body_, stream_->data());
}
-TEST_F(QuicSpdyClientStreamTest, TestFramingOnePacket) {
+TEST_P(QuicSpdyClientStreamTest, TestFramingOnePacket) {
string message = headers_string_ + body_;
EXPECT_EQ(message.size(), stream_->ProcessData(
@@ -66,7 +72,7 @@ TEST_F(QuicSpdyClientStreamTest, TestFramingOnePacket) {
EXPECT_EQ(body_, stream_->data());
}
-TEST_F(QuicSpdyClientStreamTest, DISABLED_TestFramingExtraData) {
+TEST_P(QuicSpdyClientStreamTest, DISABLED_TestFramingExtraData) {
string large_body = "hello world!!!!!!";
EXPECT_EQ(headers_string_.size(), stream_->ProcessData(
@@ -75,12 +81,14 @@ TEST_F(QuicSpdyClientStreamTest, DISABLED_TestFramingExtraData) {
EXPECT_EQ(QUIC_STREAM_NO_ERROR, stream_->stream_error());
EXPECT_EQ(200u, stream_->headers().parsed_response_code());
+ EXPECT_CALL(*connection_,
+ SendRstStream(stream_->id(), QUIC_BAD_APPLICATION_PAYLOAD));
stream_->ProcessData(large_body.c_str(), large_body.size());
EXPECT_NE(QUIC_STREAM_NO_ERROR, stream_->stream_error());
}
-TEST_F(QuicSpdyClientStreamTest, TestNoBidirectionalStreaming) {
+TEST_P(QuicSpdyClientStreamTest, TestNoBidirectionalStreaming) {
QuicStreamFrame frame(3, false, 3, MakeIOVector("asd"));
EXPECT_FALSE(stream_->write_side_closed());
diff --git a/net/tools/quic/quic_spdy_server_stream.cc b/net/tools/quic/quic_spdy_server_stream.cc
index c1a9cf1..eba8139 100644
--- a/net/tools/quic/quic_spdy_server_stream.cc
+++ b/net/tools/quic/quic_spdy_server_stream.cc
@@ -121,9 +121,13 @@ void QuicSpdyServerStream:: SendHeadersAndBody(
SpdyHeaderBlock header_block =
SpdyUtils::ResponseHeadersToSpdyHeaders(response_headers);
- string headers_string =
- session()->compressor()->CompressHeaders(header_block);
+ if (version() > QUIC_VERSION_12) {
+ WriteHeaders(header_block, body.empty());
+ } else {
+ string headers_string =
+ session()->compressor()->CompressHeaders(header_block);
WriteOrBufferData(headers_string, body.empty());
+ }
if (!body.empty()) {
WriteOrBufferData(body, true);
diff --git a/net/tools/quic/quic_spdy_server_stream_test.cc b/net/tools/quic/quic_spdy_server_stream_test.cc
index c0ec944..3cacc15 100644
--- a/net/tools/quic/quic_spdy_server_stream_test.cc
+++ b/net/tools/quic/quic_spdy_server_stream_test.cc
@@ -20,8 +20,9 @@
#include "testing/gtest/include/gtest/gtest.h"
using base::StringPiece;
-using net::tools::test::MockConnection;
using net::test::MockSession;
+using net::test::SupportedVersions;
+using net::tools::test::MockConnection;
using std::string;
using testing::_;
using testing::AnyNumber;
@@ -57,14 +58,32 @@ class QuicSpdyServerStreamPeer : public QuicSpdyServerStream {
BalsaHeaders* mutable_headers() {
return &headers_;
}
+
+ static void SendResponse(QuicSpdyServerStream* stream) {
+ stream->SendResponse();
+ }
+
+ static void SendErrorResponse(QuicSpdyServerStream* stream) {
+ stream->SendResponse();
+ }
+
+ static const string& body(QuicSpdyServerStream* stream) {
+ return stream->body_;
+ }
+
+ static const BalsaHeaders& headers(QuicSpdyServerStream* stream) {
+ return stream->headers_;
+ }
};
namespace {
-class QuicSpdyServerStreamTest : public ::testing::Test {
+class QuicSpdyServerStreamTest : public ::testing::TestWithParam<QuicVersion> {
public:
QuicSpdyServerStreamTest()
- : session_(new MockConnection(true)),
+ : connection_(new StrictMock<MockConnection>(
+ true, SupportedVersions(GetParam()))),
+ session_(connection_),
body_("hello world") {
BalsaHeaders request_headers;
request_headers.SetRequestFirstlineFromStringPieces(
@@ -130,8 +149,17 @@ class QuicSpdyServerStreamTest : public ::testing::Test {
cache->AddResponse(request_headers, response_headers, body);
}
+ const string& StreamBody() {
+ return QuicSpdyServerStreamPeer::body(stream_.get());
+ }
+
+ const BalsaHeaders& StreamHeaders() {
+ return QuicSpdyServerStreamPeer::headers(stream_.get());
+ }
+
BalsaHeaders response_headers_;
EpollServer eps_;
+ StrictMock<MockConnection>* connection_;
StrictMock<MockSession> session_;
scoped_ptr<QuicSpdyServerStreamPeer> stream_;
string headers_string_;
@@ -152,20 +180,23 @@ QuicConsumedData ConsumeAllData(
return QuicConsumedData(consumed_length, fin);
}
-TEST_F(QuicSpdyServerStreamTest, TestFraming) {
+INSTANTIATE_TEST_CASE_P(Tests, QuicSpdyServerStreamTest,
+ ::testing::ValuesIn(QuicSupportedVersions()));
+
+TEST_P(QuicSpdyServerStreamTest, TestFraming) {
EXPECT_CALL(session_, WritevData(_, _, _, _, _, _)).Times(AnyNumber()).
WillRepeatedly(Invoke(ConsumeAllData));
EXPECT_EQ(headers_string_.size(), stream_->ProcessData(
headers_string_.c_str(), headers_string_.size()));
EXPECT_EQ(body_.size(), stream_->ProcessData(body_.c_str(), body_.size()));
- EXPECT_EQ(11u, stream_->headers().content_length());
- EXPECT_EQ("https://www.google.com/", stream_->headers().request_uri());
- EXPECT_EQ("POST", stream_->headers().request_method());
- EXPECT_EQ(body_, stream_->body());
+ EXPECT_EQ(11u, StreamHeaders().content_length());
+ EXPECT_EQ("https://www.google.com/", StreamHeaders().request_uri());
+ EXPECT_EQ("POST", StreamHeaders().request_method());
+ EXPECT_EQ(body_, StreamBody());
}
-TEST_F(QuicSpdyServerStreamTest, TestFramingOnePacket) {
+TEST_P(QuicSpdyServerStreamTest, TestFramingOnePacket) {
EXPECT_CALL(session_, WritevData(_, _, _, _, _, _)).Times(AnyNumber()).
WillRepeatedly(Invoke(ConsumeAllData));
@@ -173,14 +204,13 @@ TEST_F(QuicSpdyServerStreamTest, TestFramingOnePacket) {
EXPECT_EQ(message.size(), stream_->ProcessData(
message.c_str(), message.size()));
- EXPECT_EQ(11u, stream_->headers().content_length());
- EXPECT_EQ("https://www.google.com/",
- stream_->headers().request_uri());
- EXPECT_EQ("POST", stream_->headers().request_method());
- EXPECT_EQ(body_, stream_->body());
+ EXPECT_EQ(11u, StreamHeaders().content_length());
+ EXPECT_EQ("https://www.google.com/", StreamHeaders().request_uri());
+ EXPECT_EQ("POST", StreamHeaders().request_method());
+ EXPECT_EQ(body_, StreamBody());
}
-TEST_F(QuicSpdyServerStreamTest, TestFramingExtraData) {
+TEST_P(QuicSpdyServerStreamTest, TestFramingExtraData) {
string large_body = "hello world!!!!!!";
// We'll automatically write out an error (headers + body)
@@ -192,12 +222,12 @@ TEST_F(QuicSpdyServerStreamTest, TestFramingExtraData) {
// Content length is still 11. This will register as an error and we won't
// accept the bytes.
stream_->ProcessData(large_body.c_str(), large_body.size());
- EXPECT_EQ(11u, stream_->headers().content_length());
- EXPECT_EQ("https://www.google.com/", stream_->headers().request_uri());
- EXPECT_EQ("POST", stream_->headers().request_method());
+ EXPECT_EQ(11u, StreamHeaders().content_length());
+ EXPECT_EQ("https://www.google.com/", StreamHeaders().request_uri());
+ EXPECT_EQ("POST", StreamHeaders().request_method());
}
-TEST_F(QuicSpdyServerStreamTest, TestSendResponse) {
+TEST_P(QuicSpdyServerStreamTest, TestSendResponse) {
BalsaHeaders* request_headers = stream_->mutable_headers();
request_headers->SetRequestFirstlineFromStringPieces(
"GET",
@@ -209,40 +239,49 @@ TEST_F(QuicSpdyServerStreamTest, TestSendResponse) {
response_headers_.ReplaceOrAppendHeader("content-length", "3");
InSequence s;
- EXPECT_CALL(session_, WritevData(_, _, 1, _, _, _)).Times(1)
- .WillOnce(WithArgs<1>(Invoke(
- this, &QuicSpdyServerStreamTest::ValidateHeaders)));
+ if (GetParam() > QUIC_VERSION_12) {
+ EXPECT_CALL(session_,
+ WritevData(kHeadersStreamId, _, _, 0, false, NULL));
+ } else {
+ EXPECT_CALL(session_, WritevData(_, _, 1, _, _, _)).Times(1)
+ .WillOnce(WithArgs<1>(Invoke(
+ this, &QuicSpdyServerStreamTest::ValidateHeaders)));
+ }
EXPECT_CALL(session_, WritevData(_, _, 1, _, _, _)).Times(1).
WillOnce(Return(QuicConsumedData(3, true)));
- stream_->SendResponse();
+ QuicSpdyServerStreamPeer::SendResponse(stream_.get());
EXPECT_TRUE(stream_->read_side_closed());
EXPECT_TRUE(stream_->write_side_closed());
}
-TEST_F(QuicSpdyServerStreamTest, TestSendErrorResponse) {
+TEST_P(QuicSpdyServerStreamTest, TestSendErrorResponse) {
response_headers_.SetResponseFirstlineFromStringPieces(
"HTTP/1.1", "500", "Server Error");
response_headers_.ReplaceOrAppendHeader("content-length", "3");
InSequence s;
- EXPECT_CALL(session_, WritevData(_, _, 1, _, _, _)).Times(1)
- .WillOnce(WithArgs<1>(Invoke(
- this, &QuicSpdyServerStreamTest::ValidateHeaders)));
+ if (GetParam() > QUIC_VERSION_12) {
+ EXPECT_CALL(session_, WritevData(kHeadersStreamId, _, _, 0, false, NULL));
+ } else {
+ EXPECT_CALL(session_, WritevData(_, _, 1, _, _, _)).Times(1)
+ .WillOnce(WithArgs<1>(Invoke(
+ this, &QuicSpdyServerStreamTest::ValidateHeaders)));
+ }
EXPECT_CALL(session_, WritevData(_, _, 1, _, _, _)).Times(1).
WillOnce(Return(QuicConsumedData(3, true)));
- stream_->SendErrorResponse();
+ QuicSpdyServerStreamPeer::SendErrorResponse(stream_.get());
EXPECT_TRUE(stream_->read_side_closed());
EXPECT_TRUE(stream_->write_side_closed());
}
-TEST_F(QuicSpdyServerStreamTest, InvalidHeadersWithFin) {
+TEST_P(QuicSpdyServerStreamTest, InvalidHeadersWithFin) {
char arr[] = {
- 0x00, 0x00, 0x00, 0x05, // ....
- 0x00, 0x00, 0x00, 0x05, // ....
+ 0x05, 0x00, 0x00, 0x00, // ....
+ 0x05, 0x00, 0x00, 0x00, // ....
0x3a, 0x68, 0x6f, 0x73, // :hos
0x74, 0x00, 0x00, 0x00, // t...
0x00, 0x00, 0x00, 0x00, // ....
@@ -265,8 +304,9 @@ TEST_F(QuicSpdyServerStreamTest, InvalidHeadersWithFin) {
0x54, 0x54, 0x50, 0x2f, // TTP/
0x31, 0x2e, 0x31, // 1.1
};
- QuicStreamFrame frame(
- stream_->id(), true, 0, MakeIOVector(StringPiece(arr, arraysize(arr))));
+ size_t start = GetParam() > QUIC_VERSION_12 ? 8 : 0;
+ StringPiece data(arr + start, arraysize(arr) - start);
+ QuicStreamFrame frame(stream_->id(), true, 0, MakeIOVector(data));
// Verify that we don't crash when we get a invalid headers in stream frame.
stream_->OnStreamFrame(frame);
}
diff --git a/net/tools/quic/quic_time_wait_list_manager.cc b/net/tools/quic/quic_time_wait_list_manager.cc
index df9e378..31c386c 100644
--- a/net/tools/quic/quic_time_wait_list_manager.cc
+++ b/net/tools/quic/quic_time_wait_list_manager.cc
@@ -175,8 +175,13 @@ void QuicTimeWaitListManager::OnError(QuicFramer* framer) {
bool QuicTimeWaitListManager::OnProtocolVersionMismatch(
QuicVersion received_version) {
- // Drop such packets whose version don't match.
- return false;
+ if (!framer_.IsSupportedVersion(received_version)) {
+ // Drop such packets whose version don't match.
+ return false;
+ }
+ // Allow the framer to continue processing this packet.
+ framer_.set_version(received_version);
+ return true;
}
bool QuicTimeWaitListManager::OnUnauthenticatedHeader(
diff --git a/net/tools/quic/test_tools/quic_test_client.cc b/net/tools/quic/test_tools/quic_test_client.cc
index 57edade..c7c64de 100644
--- a/net/tools/quic/test_tools/quic_test_client.cc
+++ b/net/tools/quic/test_tools/quic_test_client.cc
@@ -413,8 +413,9 @@ void QuicTestClient::OnClose(QuicDataStream* stream) {
response_headers_complete_ = stream_->headers_decompressed();
headers_.CopyFrom(stream_->headers());
stream_error_ = stream_->stream_error();
- bytes_read_ = stream_->stream_bytes_read();
- bytes_written_ = stream_->stream_bytes_written();
+ bytes_read_ = stream_->stream_bytes_read() + stream_->header_bytes_read();
+ bytes_written_ =
+ stream_->stream_bytes_written() + stream_->header_bytes_written();
response_header_size_ = headers_.GetSizeForWriteBuffer();
response_body_size_ = stream_->data().size();
stream_ = NULL;
diff --git a/net/tools/quic/test_tools/quic_test_utils.cc b/net/tools/quic/test_tools/quic_test_utils.cc
index 1aad327..2d013c3 100644
--- a/net/tools/quic/test_tools/quic_test_utils.cc
+++ b/net/tools/quic/test_tools/quic_test_utils.cc
@@ -48,6 +48,17 @@ MockConnection::MockConnection(QuicGuid guid,
helper_(helper()) {
}
+MockConnection::MockConnection(bool is_server,
+ const QuicVersionVector& supported_versions)
+ : QuicConnection(kTestGuid,
+ IPEndPoint(net::test::Loopback4(), kTestPort),
+ new testing::NiceMock<MockHelper>(),
+ new testing::NiceMock<MockPacketWriter>(),
+ is_server, supported_versions),
+ writer_(net::test::QuicConnectionPeer::GetWriter(this)),
+ helper_(helper()) {
+}
+
MockConnection::~MockConnection() {
}
diff --git a/net/tools/quic/test_tools/quic_test_utils.h b/net/tools/quic/test_tools/quic_test_utils.h
index 986665b..028b08c 100644
--- a/net/tools/quic/test_tools/quic_test_utils.h
+++ b/net/tools/quic/test_tools/quic_test_utils.h
@@ -50,12 +50,13 @@ class MockConnection : public QuicConnection {
explicit MockConnection(bool is_server);
// Uses a MockHelper, GUID of 42.
- MockConnection(IPEndPoint address,
- bool is_server);
+ MockConnection(IPEndPoint address, bool is_server);
// Uses a MockHelper, and 127.0.0.1:123
- MockConnection(QuicGuid guid,
- bool is_server);
+ MockConnection(QuicGuid guid, bool is_server);
+
+ // Uses a Mock helper, GUID of 42, and 127.0.0.1:123.
+ MockConnection(bool is_server, const QuicVersionVector& supported_versions);
virtual ~MockConnection();