diff options
author | rtenneti@chromium.org <rtenneti@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-12-18 22:21:08 +0000 |
---|---|---|
committer | rtenneti@chromium.org <rtenneti@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-12-18 22:21:08 +0000 |
commit | 4d640797720f35d3fab791863c2d2cd9e1ce1e68 (patch) | |
tree | b8b9c9b76afda5479a2f0a6fd2562ff7c8965aa0 | |
parent | 91162f3da2ef9c114f10d5cbb175704573145ab3 (diff) | |
download | chromium_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
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(); |