Project import generated by Copybara.
PiperOrigin-RevId: 237361882
Change-Id: I109a68f44db867b20f8c6a7732b0ce657133e52a
diff --git a/quic/tools/quic_simple_server_session_test.cc b/quic/tools/quic_simple_server_session_test.cc
new file mode 100644
index 0000000..b2ef667
--- /dev/null
+++ b/quic/tools/quic_simple_server_session_test.cc
@@ -0,0 +1,838 @@
+// 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/third_party/quiche/src/quic/tools/quic_simple_server_session.h"
+
+#include <algorithm>
+#include <memory>
+
+#include "base/macros.h"
+#include "net/third_party/quiche/src/quic/core/crypto/quic_crypto_server_config.h"
+#include "net/third_party/quiche/src/quic/core/crypto/quic_random.h"
+#include "net/third_party/quiche/src/quic/core/proto/cached_network_parameters.pb.h"
+#include "net/third_party/quiche/src/quic/core/quic_connection.h"
+#include "net/third_party/quiche/src/quic/core/quic_crypto_server_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/core/tls_server_handshaker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_containers.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_expect_bug.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
+#include "net/third_party/quiche/src/quic/test_tools/crypto_test_utils.h"
+#include "net/third_party/quiche/src/quic/test_tools/mock_quic_session_visitor.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_config_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_connection_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_sent_packet_manager_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_session_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_spdy_session_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_stream_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_sustained_bandwidth_recorder_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
+#include "net/third_party/quiche/src/quic/tools/quic_backend_response.h"
+#include "net/third_party/quiche/src/quic/tools/quic_memory_cache_backend.h"
+#include "net/third_party/quiche/src/quic/tools/quic_simple_server_stream.h"
+
+using testing::_;
+using testing::AtLeast;
+using testing::InSequence;
+using testing::Invoke;
+using testing::Return;
+using testing::StrictMock;
+
+namespace quic {
+namespace test {
+namespace {
+typedef QuicSimpleServerSession::PromisedStreamInfo PromisedStreamInfo;
+} // namespace
+
+class QuicSimpleServerSessionPeer {
+ public:
+ static void SetCryptoStream(QuicSimpleServerSession* s,
+ QuicCryptoServerStream* crypto_stream) {
+ s->crypto_stream_.reset(crypto_stream);
+ s->RegisterStaticStream(
+ QuicUtils::GetCryptoStreamId(s->connection()->transport_version()),
+ crypto_stream);
+ }
+
+ static QuicSpdyStream* CreateIncomingStream(QuicSimpleServerSession* s,
+ QuicStreamId id) {
+ return s->CreateIncomingStream(id);
+ }
+
+ static QuicSimpleServerStream* CreateOutgoingUnidirectionalStream(
+ QuicSimpleServerSession* s) {
+ return s->CreateOutgoingUnidirectionalStream();
+ }
+};
+
+namespace {
+
+const size_t kMaxStreamsForTest = 10;
+
+class MockQuicCryptoServerStream : public QuicCryptoServerStream {
+ public:
+ explicit MockQuicCryptoServerStream(
+ const QuicCryptoServerConfig* crypto_config,
+ QuicCompressedCertsCache* compressed_certs_cache,
+ QuicServerSessionBase* session,
+ QuicCryptoServerStream::Helper* helper)
+ : QuicCryptoServerStream(
+ crypto_config,
+ compressed_certs_cache,
+ GetQuicReloadableFlag(
+ enable_quic_stateless_reject_support), // NOLINT
+ session,
+ helper) {}
+ MockQuicCryptoServerStream(const MockQuicCryptoServerStream&) = delete;
+ MockQuicCryptoServerStream& operator=(const MockQuicCryptoServerStream&) =
+ delete;
+ ~MockQuicCryptoServerStream() override {}
+
+ MOCK_METHOD1(SendServerConfigUpdate,
+ void(const CachedNetworkParameters* cached_network_parameters));
+
+ void set_encryption_established(bool has_established) {
+ encryption_established_override_ = has_established;
+ }
+
+ bool encryption_established() const override {
+ return QuicCryptoServerStream::encryption_established() ||
+ encryption_established_override_;
+ }
+
+ private:
+ bool encryption_established_override_ = false;
+};
+
+class MockQuicConnectionWithSendStreamData : public MockQuicConnection {
+ public:
+ MockQuicConnectionWithSendStreamData(
+ MockQuicConnectionHelper* helper,
+ MockAlarmFactory* alarm_factory,
+ Perspective perspective,
+ const ParsedQuicVersionVector& supported_versions)
+ : MockQuicConnection(helper,
+ alarm_factory,
+ perspective,
+ supported_versions) {
+ auto consume_all_data = [](QuicStreamId id, size_t write_length,
+ QuicStreamOffset offset,
+ StreamSendingState state) {
+ return QuicConsumedData(write_length, state != NO_FIN);
+ };
+ ON_CALL(*this, SendStreamData(_, _, _, _))
+ .WillByDefault(Invoke(consume_all_data));
+ }
+
+ MOCK_METHOD4(SendStreamData,
+ QuicConsumedData(QuicStreamId id,
+ size_t write_length,
+ QuicStreamOffset offset,
+ StreamSendingState state));
+};
+
+class MockQuicSimpleServerSession : public QuicSimpleServerSession {
+ public:
+ MockQuicSimpleServerSession(
+ const QuicConfig& config,
+ QuicConnection* connection,
+ QuicSession::Visitor* visitor,
+ QuicCryptoServerStream::Helper* helper,
+ const QuicCryptoServerConfig* crypto_config,
+ QuicCompressedCertsCache* compressed_certs_cache,
+ QuicSimpleServerBackend* quic_simple_server_backend)
+ : QuicSimpleServerSession(config,
+ CurrentSupportedVersions(),
+ connection,
+ visitor,
+ helper,
+ crypto_config,
+ compressed_certs_cache,
+ quic_simple_server_backend) {}
+ // Methods taking non-copyable types like SpdyHeaderBlock by value cannot be
+ // mocked directly.
+ size_t WritePushPromise(QuicStreamId original_stream_id,
+ QuicStreamId promised_stream_id,
+ spdy::SpdyHeaderBlock headers) override {
+ return WritePushPromiseMock(original_stream_id, promised_stream_id,
+ headers);
+ }
+ MOCK_METHOD3(WritePushPromiseMock,
+ size_t(QuicStreamId original_stream_id,
+ QuicStreamId promised_stream_id,
+ const spdy::SpdyHeaderBlock& headers));
+
+ MOCK_METHOD1(SendBlocked, void(QuicStreamId));
+};
+
+class QuicSimpleServerSessionTest
+ : public QuicTestWithParam<ParsedQuicVersion> {
+ public:
+ bool ClearControlFrame(const QuicFrame& frame) {
+ DeleteFrame(&const_cast<QuicFrame&>(frame));
+ return true;
+ }
+
+ // The function ensures that A) the max stream id frames get properly deleted
+ // (since the test uses a 'did we leak memory' check ... if we just lose the
+ // frame, the test fails) and B) returns true (instead of the default, false)
+ // which ensures that the rest of the system thinks that the frame actually
+ // was transmitted.
+ bool ClearMaxStreamIdControlFrame(const QuicFrame& frame) {
+ if (frame.type == MAX_STREAM_ID_FRAME) {
+ DeleteFrame(&const_cast<QuicFrame&>(frame));
+ return true;
+ }
+ return false;
+ }
+
+ protected:
+ QuicSimpleServerSessionTest()
+ : crypto_config_(QuicCryptoServerConfig::TESTING,
+ QuicRandom::GetInstance(),
+ crypto_test_utils::ProofSourceForTesting(),
+ KeyExchangeSource::Default(),
+ TlsServerHandshaker::CreateSslCtx()),
+ compressed_certs_cache_(
+ QuicCompressedCertsCache::kQuicCompressedCertsCacheSize) {
+ config_.SetMaxIncomingDynamicStreamsToSend(kMaxStreamsForTest);
+ QuicConfigPeer::SetReceivedMaxIncomingDynamicStreams(&config_,
+ kMaxStreamsForTest);
+ config_.SetInitialStreamFlowControlWindowToSend(
+ kInitialStreamFlowControlWindowForTest);
+ config_.SetInitialSessionFlowControlWindowToSend(
+ kInitialSessionFlowControlWindowForTest);
+
+ ParsedQuicVersionVector supported_versions = SupportedVersions(GetParam());
+ connection_ = new StrictMock<MockQuicConnectionWithSendStreamData>(
+ &helper_, &alarm_factory_, Perspective::IS_SERVER, supported_versions);
+ connection_->AdvanceTime(QuicTime::Delta::FromSeconds(1));
+ session_ = QuicMakeUnique<MockQuicSimpleServerSession>(
+ config_, connection_, &owner_, &stream_helper_, &crypto_config_,
+ &compressed_certs_cache_, &memory_cache_backend_);
+ MockClock clock;
+ handshake_message_.reset(crypto_config_.AddDefaultConfig(
+ QuicRandom::GetInstance(), &clock,
+ QuicCryptoServerConfig::ConfigOptions()));
+ session_->Initialize();
+ QuicSessionPeer::GetMutableCryptoStream(session_.get())
+ ->OnSuccessfulVersionNegotiation(supported_versions.front());
+ visitor_ = QuicConnectionPeer::GetVisitor(connection_);
+
+ if (IsVersion99()) {
+ EXPECT_CALL(*connection_, SendControlFrame(_))
+ .WillRepeatedly(Invoke(
+ this,
+ &QuicSimpleServerSessionTest::ClearMaxStreamIdControlFrame));
+ }
+ session_->OnConfigNegotiated();
+ }
+
+ QuicStreamId GetNthClientInitiatedBidirectionalId(int n) {
+ return GetNthClientInitiatedBidirectionalStreamId(
+ connection_->transport_version(), n);
+ }
+
+ QuicStreamId GetNthServerInitiatedUnidirectionalId(int n) {
+ return quic::test::GetNthServerInitiatedUnidirectionalStreamId(
+ connection_->transport_version(), n);
+ }
+
+ bool IsVersion99() const {
+ return connection_->transport_version() == QUIC_VERSION_99;
+ }
+
+ void InjectStopSending(QuicStreamId stream_id,
+ QuicRstStreamErrorCode rst_stream_code) {
+ // Create and inject a STOP_SENDING frame. In GOOGLE QUIC, receiving a
+ // RST_STREAM frame causes a two-way close. For IETF QUIC, RST_STREAM causes
+ // a one-way close.
+ if (!IsVersion99()) {
+ // Only needed for version 99/IETF QUIC.
+ return;
+ }
+ EXPECT_CALL(owner_, OnStopSendingReceived(_)).Times(1);
+ QuicStopSendingFrame stop_sending(
+ kInvalidControlFrameId, stream_id,
+ static_cast<QuicApplicationErrorCode>(rst_stream_code));
+ // Expect the RESET_STREAM that is generated in response to receiving a
+ // STOP_SENDING.
+ EXPECT_CALL(*connection_, OnStreamReset(stream_id, rst_stream_code));
+ session_->OnStopSendingFrame(stop_sending);
+ }
+
+ StrictMock<MockQuicSessionVisitor> owner_;
+ StrictMock<MockQuicCryptoServerStreamHelper> stream_helper_;
+ MockQuicConnectionHelper helper_;
+ MockAlarmFactory alarm_factory_;
+ StrictMock<MockQuicConnectionWithSendStreamData>* connection_;
+ QuicConfig config_;
+ QuicCryptoServerConfig crypto_config_;
+ QuicCompressedCertsCache compressed_certs_cache_;
+ QuicMemoryCacheBackend memory_cache_backend_;
+ std::unique_ptr<MockQuicSimpleServerSession> session_;
+ std::unique_ptr<CryptoHandshakeMessage> handshake_message_;
+ QuicConnectionVisitorInterface* visitor_;
+};
+
+INSTANTIATE_TEST_SUITE_P(Tests,
+ QuicSimpleServerSessionTest,
+ ::testing::ValuesIn(AllSupportedVersions()));
+
+TEST_P(QuicSimpleServerSessionTest, CloseStreamDueToReset) {
+ // Open a stream, then reset it.
+ // Send two bytes of payload to open it.
+ QuicStreamFrame data1(GetNthClientInitiatedBidirectionalId(0), false, 0,
+ QuicStringPiece("HT"));
+ session_->OnStreamFrame(data1);
+ EXPECT_EQ(1u, session_->GetNumOpenIncomingStreams());
+
+ // Receive a reset (and send a RST in response).
+ QuicRstStreamFrame rst1(kInvalidControlFrameId,
+ GetNthClientInitiatedBidirectionalId(0),
+ QUIC_ERROR_PROCESSING_STREAM, 0);
+ EXPECT_CALL(owner_, OnRstStreamReceived(_)).Times(1);
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ if (!IsVersion99()) {
+ // For version 99, this is covered in InjectStopSending()
+ EXPECT_CALL(*connection_,
+ OnStreamReset(GetNthClientInitiatedBidirectionalId(0),
+ QUIC_RST_ACKNOWLEDGEMENT));
+ }
+ visitor_->OnRstStream(rst1);
+ // Create and inject a STOP_SENDING frame. In GOOGLE QUIC, receiving a
+ // RST_STREAM frame causes a two-way close. For IETF QUIC, RST_STREAM causes
+ // a one-way close.
+ InjectStopSending(GetNthClientInitiatedBidirectionalId(0),
+ QUIC_ERROR_PROCESSING_STREAM);
+ EXPECT_EQ(0u, session_->GetNumOpenIncomingStreams());
+
+ // Send the same two bytes of payload in a new packet.
+ visitor_->OnStreamFrame(data1);
+
+ // The stream should not be re-opened.
+ EXPECT_EQ(0u, session_->GetNumOpenIncomingStreams());
+ EXPECT_TRUE(connection_->connected());
+}
+
+TEST_P(QuicSimpleServerSessionTest, NeverOpenStreamDueToReset) {
+ // Send a reset (and expect the peer to send a RST in response).
+ QuicRstStreamFrame rst1(kInvalidControlFrameId,
+ GetNthClientInitiatedBidirectionalId(0),
+ QUIC_ERROR_PROCESSING_STREAM, 0);
+ EXPECT_CALL(owner_, OnRstStreamReceived(_)).Times(1);
+ if (!IsVersion99()) {
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ // For version 99, this is covered in InjectStopSending()
+ EXPECT_CALL(*connection_,
+ OnStreamReset(GetNthClientInitiatedBidirectionalId(0),
+ QUIC_RST_ACKNOWLEDGEMENT));
+ }
+ visitor_->OnRstStream(rst1);
+ // Create and inject a STOP_SENDING frame. In GOOGLE QUIC, receiving a
+ // RST_STREAM frame causes a two-way close. For IETF QUIC, RST_STREAM causes
+ // a one-way close.
+ InjectStopSending(GetNthClientInitiatedBidirectionalId(0),
+ QUIC_ERROR_PROCESSING_STREAM);
+
+ EXPECT_EQ(0u, session_->GetNumOpenIncomingStreams());
+
+ // Send two bytes of payload.
+ QuicStreamFrame data1(GetNthClientInitiatedBidirectionalId(0), false, 0,
+ QuicStringPiece("HT"));
+ visitor_->OnStreamFrame(data1);
+
+ // The stream should never be opened, now that the reset is received.
+ EXPECT_EQ(0u, session_->GetNumOpenIncomingStreams());
+ EXPECT_TRUE(connection_->connected());
+}
+
+TEST_P(QuicSimpleServerSessionTest, AcceptClosedStream) {
+ // Send (empty) compressed headers followed by two bytes of data.
+ QuicStreamFrame frame1(GetNthClientInitiatedBidirectionalId(0), false, 0,
+ QuicStringPiece("\1\0\0\0\0\0\0\0HT"));
+ QuicStreamFrame frame2(GetNthClientInitiatedBidirectionalId(1), false, 0,
+ QuicStringPiece("\2\0\0\0\0\0\0\0HT"));
+ visitor_->OnStreamFrame(frame1);
+ visitor_->OnStreamFrame(frame2);
+ EXPECT_EQ(2u, session_->GetNumOpenIncomingStreams());
+
+ // Send a reset (and expect the peer to send a RST in response).
+ QuicRstStreamFrame rst(kInvalidControlFrameId,
+ GetNthClientInitiatedBidirectionalId(0),
+ QUIC_ERROR_PROCESSING_STREAM, 0);
+ EXPECT_CALL(owner_, OnRstStreamReceived(_)).Times(1);
+ if (!IsVersion99()) {
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ // For version 99, this is covered in InjectStopSending()
+ EXPECT_CALL(*connection_,
+ OnStreamReset(GetNthClientInitiatedBidirectionalId(0),
+ QUIC_RST_ACKNOWLEDGEMENT));
+ }
+ visitor_->OnRstStream(rst);
+ // Create and inject a STOP_SENDING frame. In GOOGLE QUIC, receiving a
+ // RST_STREAM frame causes a two-way close. For IETF QUIC, RST_STREAM causes
+ // a one-way close.
+ InjectStopSending(GetNthClientInitiatedBidirectionalId(0),
+ QUIC_ERROR_PROCESSING_STREAM);
+
+ // 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.
+ QuicStreamFrame frame3(GetNthClientInitiatedBidirectionalId(0), false, 2,
+ QuicStringPiece("TP"));
+ QuicStreamFrame frame4(GetNthClientInitiatedBidirectionalId(1), false, 2,
+ QuicStringPiece("TP"));
+ visitor_->OnStreamFrame(frame3);
+ visitor_->OnStreamFrame(frame4);
+ // The stream should never be opened, now that the reset is received.
+ EXPECT_EQ(1u, session_->GetNumOpenIncomingStreams());
+ EXPECT_TRUE(connection_->connected());
+}
+
+TEST_P(QuicSimpleServerSessionTest, CreateIncomingStreamDisconnected) {
+ // EXPECT_QUIC_BUG tests are expensive so only run one instance of them.
+ if (GetParam() != AllSupportedVersions()[0]) {
+ return;
+ }
+
+ // Tests that incoming stream creation fails when connection is not connected.
+ size_t initial_num_open_stream = session_->GetNumOpenIncomingStreams();
+ QuicConnectionPeer::TearDownLocalConnectionState(connection_);
+ EXPECT_QUIC_BUG(QuicSimpleServerSessionPeer::CreateIncomingStream(
+ session_.get(), GetNthClientInitiatedBidirectionalId(0)),
+ "ShouldCreateIncomingStream called when disconnected");
+ EXPECT_EQ(initial_num_open_stream, session_->GetNumOpenIncomingStreams());
+}
+
+TEST_P(QuicSimpleServerSessionTest, CreateEvenIncomingDynamicStream) {
+ // Tests that incoming stream creation fails when given stream id is even.
+ size_t initial_num_open_stream = session_->GetNumOpenIncomingStreams();
+ EXPECT_CALL(*connection_,
+ CloseConnection(QUIC_INVALID_STREAM_ID,
+ "Client created even numbered stream", _));
+ QuicSimpleServerSessionPeer::CreateIncomingStream(
+ session_.get(), GetNthServerInitiatedUnidirectionalId(0));
+ EXPECT_EQ(initial_num_open_stream, session_->GetNumOpenIncomingStreams());
+}
+
+TEST_P(QuicSimpleServerSessionTest, CreateIncomingStream) {
+ QuicSpdyStream* stream = QuicSimpleServerSessionPeer::CreateIncomingStream(
+ session_.get(), GetNthClientInitiatedBidirectionalId(0));
+ EXPECT_NE(nullptr, stream);
+ EXPECT_EQ(GetNthClientInitiatedBidirectionalId(0), stream->id());
+}
+
+TEST_P(QuicSimpleServerSessionTest, CreateOutgoingDynamicStreamDisconnected) {
+ // EXPECT_QUIC_BUG tests are expensive so only run one instance of them.
+ if (GetParam() != AllSupportedVersions()[0]) {
+ return;
+ }
+
+ // Tests that outgoing stream creation fails when connection is not connected.
+ size_t initial_num_open_stream = session_->GetNumOpenOutgoingStreams();
+ QuicConnectionPeer::TearDownLocalConnectionState(connection_);
+ EXPECT_QUIC_BUG(
+ QuicSimpleServerSessionPeer::CreateOutgoingUnidirectionalStream(
+ session_.get()),
+ "ShouldCreateOutgoingUnidirectionalStream called when disconnected");
+
+ EXPECT_EQ(initial_num_open_stream, session_->GetNumOpenOutgoingStreams());
+}
+
+TEST_P(QuicSimpleServerSessionTest, CreateOutgoingDynamicStreamUnencrypted) {
+ // EXPECT_QUIC_BUG tests are expensive so only run one instance of them.
+ if (GetParam() != AllSupportedVersions()[0]) {
+ return;
+ }
+
+ // Tests that outgoing stream creation fails when encryption has not yet been
+ // established.
+ size_t initial_num_open_stream = session_->GetNumOpenOutgoingStreams();
+ EXPECT_QUIC_BUG(
+ QuicSimpleServerSessionPeer::CreateOutgoingUnidirectionalStream(
+ session_.get()),
+ "Encryption not established so no outgoing stream created.");
+ EXPECT_EQ(initial_num_open_stream, session_->GetNumOpenOutgoingStreams());
+}
+
+TEST_P(QuicSimpleServerSessionTest, CreateOutgoingDynamicStreamUptoLimit) {
+ // Tests that outgoing stream creation should not be affected by existing
+ // incoming stream and vice-versa. But when reaching the limit of max outgoing
+ // stream allowed, creation should fail.
+
+ // Receive some data to initiate a incoming stream which should not effect
+ // creating outgoing streams.
+ QuicStreamFrame data1(GetNthClientInitiatedBidirectionalId(0), false, 0,
+ QuicStringPiece("HT"));
+ session_->OnStreamFrame(data1);
+ EXPECT_EQ(1u, session_->GetNumOpenIncomingStreams());
+ EXPECT_EQ(0u, session_->GetNumOpenOutgoingStreams());
+
+ session_->UnregisterStreamPriority(
+ QuicUtils::GetHeadersStreamId(connection_->transport_version()),
+ /*is_static=*/true);
+ // Assume encryption already established.
+ QuicSimpleServerSessionPeer::SetCryptoStream(session_.get(), nullptr);
+ MockQuicCryptoServerStream* crypto_stream =
+ new MockQuicCryptoServerStream(&crypto_config_, &compressed_certs_cache_,
+ session_.get(), &stream_helper_);
+ crypto_stream->set_encryption_established(true);
+ QuicSimpleServerSessionPeer::SetCryptoStream(session_.get(), crypto_stream);
+ session_->RegisterStreamPriority(
+ QuicUtils::GetHeadersStreamId(connection_->transport_version()),
+ /*is_static=*/true, QuicStream::kDefaultPriority);
+
+ // Create push streams till reaching the upper limit of allowed open streams.
+ for (size_t i = 0; i < kMaxStreamsForTest; ++i) {
+ QuicSpdyStream* created_stream =
+ QuicSimpleServerSessionPeer::CreateOutgoingUnidirectionalStream(
+ session_.get());
+ EXPECT_EQ(GetNthServerInitiatedUnidirectionalId(i), created_stream->id());
+ EXPECT_EQ(i + 1, session_->GetNumOpenOutgoingStreams());
+ }
+
+ // Continuing creating push stream would fail.
+ EXPECT_EQ(nullptr,
+ QuicSimpleServerSessionPeer::CreateOutgoingUnidirectionalStream(
+ session_.get()));
+ EXPECT_EQ(kMaxStreamsForTest, session_->GetNumOpenOutgoingStreams());
+
+ // Create peer initiated stream should have no problem.
+ QuicStreamFrame data2(GetNthClientInitiatedBidirectionalId(1), false, 0,
+ QuicStringPiece("HT"));
+ session_->OnStreamFrame(data2);
+ EXPECT_EQ(2u, session_->GetNumOpenIncomingStreams());
+}
+
+TEST_P(QuicSimpleServerSessionTest, OnStreamFrameWithEvenStreamId) {
+ QuicStreamFrame frame(GetNthServerInitiatedUnidirectionalId(0), false, 0,
+ QuicStringPiece());
+ EXPECT_CALL(*connection_,
+ CloseConnection(QUIC_INVALID_STREAM_ID,
+ "Client sent data on server push stream", _));
+ session_->OnStreamFrame(frame);
+}
+
+TEST_P(QuicSimpleServerSessionTest, GetEvenIncomingError) {
+ // Tests that calling GetOrCreateDynamicStream() on an outgoing stream not
+ // promised yet should result close connection.
+ EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_STREAM_ID,
+ "Data for nonexistent stream", _));
+ EXPECT_EQ(nullptr,
+ QuicSessionPeer::GetOrCreateDynamicStream(
+ session_.get(), GetNthServerInitiatedUnidirectionalId(1)));
+}
+
+// In order to test the case where server push stream creation goes beyond
+// limit, server push streams need to be hanging there instead of
+// immediately closing after sending back response.
+// To achieve this goal, this class resets flow control windows so that large
+// responses will not be sent fully in order to prevent push streams from being
+// closed immediately.
+// Also adjust connection-level flow control window to ensure a large response
+// can cause stream-level flow control blocked but not connection-level.
+class QuicSimpleServerSessionServerPushTest
+ : public QuicSimpleServerSessionTest {
+ protected:
+ const size_t kStreamFlowControlWindowSize = 32 * 1024; // 32KB.
+
+ QuicSimpleServerSessionServerPushTest() {
+ // Reset stream level flow control window to be 32KB.
+ QuicConfigPeer::SetReceivedInitialStreamFlowControlWindow(
+ &config_, kStreamFlowControlWindowSize);
+ // Reset connection level flow control window to be 1.5 MB which is large
+ // enough that it won't block any stream to write before stream level flow
+ // control blocks it.
+ QuicConfigPeer::SetReceivedInitialSessionFlowControlWindow(
+ &config_, kInitialSessionFlowControlWindowForTest);
+
+ ParsedQuicVersionVector supported_versions = SupportedVersions(GetParam());
+ connection_ = new StrictMock<MockQuicConnectionWithSendStreamData>(
+ &helper_, &alarm_factory_, Perspective::IS_SERVER, supported_versions);
+ session_ = QuicMakeUnique<MockQuicSimpleServerSession>(
+ config_, connection_, &owner_, &stream_helper_, &crypto_config_,
+ &compressed_certs_cache_, &memory_cache_backend_);
+ session_->Initialize();
+ QuicSessionPeer::GetMutableCryptoStream(session_.get())
+ ->OnSuccessfulVersionNegotiation(supported_versions.front());
+ // Needed to make new session flow control window and server push work.
+
+ if (IsVersion99()) {
+ EXPECT_CALL(*connection_, SendControlFrame(_))
+ .WillRepeatedly(Invoke(this, &QuicSimpleServerSessionServerPushTest::
+ ClearMaxStreamIdControlFrame));
+ }
+ session_->OnConfigNegotiated();
+
+ visitor_ = QuicConnectionPeer::GetVisitor(connection_);
+
+ session_->UnregisterStreamPriority(
+ QuicUtils::GetHeadersStreamId(connection_->transport_version()),
+ /*is_static=*/true);
+ QuicSimpleServerSessionPeer::SetCryptoStream(session_.get(), nullptr);
+ // Assume encryption already established.
+ MockQuicCryptoServerStream* crypto_stream = new MockQuicCryptoServerStream(
+ &crypto_config_, &compressed_certs_cache_, session_.get(),
+ &stream_helper_);
+
+ crypto_stream->set_encryption_established(true);
+ QuicSimpleServerSessionPeer::SetCryptoStream(session_.get(), crypto_stream);
+ session_->RegisterStreamPriority(
+ QuicUtils::GetHeadersStreamId(connection_->transport_version()),
+ /*is_static=*/true, QuicStream::kDefaultPriority);
+ }
+
+ // Given |num_resources|, create this number of fake push resources and push
+ // them by sending PUSH_PROMISE for all and sending push responses for as much
+ // as possible(limited by kMaxStreamsForTest).
+ // If |num_resources| > kMaxStreamsForTest, the left over will be queued.
+ // Returns the length of the data frame header, 0 if the version doesn't
+ // require header.
+ QuicByteCount PromisePushResources(size_t num_resources) {
+ // testing::InSequence seq;
+ // To prevent push streams from being closed the response need to be larger
+ // than stream flow control window so stream won't send the full body.
+ size_t body_size = 2 * kStreamFlowControlWindowSize; // 64KB.
+
+ QuicString request_url = "mail.google.com/";
+ spdy::SpdyHeaderBlock request_headers;
+ QuicString resource_host = "www.google.com";
+ QuicString partial_push_resource_path = "/server_push_src";
+ std::list<QuicBackendResponse::ServerPushInfo> push_resources;
+ QuicString scheme = "http";
+ QuicByteCount header_length = 0;
+ for (unsigned int i = 1; i <= num_resources; ++i) {
+ QuicStreamId stream_id = GetNthServerInitiatedUnidirectionalId(i - 1);
+ QuicString path =
+ partial_push_resource_path + QuicTextUtils::Uint64ToString(i);
+ QuicString url = scheme + "://" + resource_host + path;
+ QuicUrl resource_url = QuicUrl(url);
+ QuicString body(body_size, 'a');
+ QuicString data;
+ header_length = 0;
+ if (VersionHasDataFrameHeader(connection_->transport_version())) {
+ HttpEncoder encoder;
+ std::unique_ptr<char[]> buffer;
+ header_length =
+ encoder.SerializeDataFrameHeader(body.length(), &buffer);
+ QuicString header = QuicString(buffer.get(), header_length);
+ data = header + body;
+ } else {
+ data = body;
+ }
+
+ memory_cache_backend_.AddSimpleResponse(resource_host, path, 200, data);
+ push_resources.push_back(QuicBackendResponse::ServerPushInfo(
+ resource_url, spdy::SpdyHeaderBlock(), QuicStream::kDefaultPriority,
+ body));
+ // PUSH_PROMISED are sent for all the resources.
+ EXPECT_CALL(*session_,
+ WritePushPromiseMock(GetNthClientInitiatedBidirectionalId(0),
+ stream_id, _));
+ if (i <= kMaxStreamsForTest) {
+ // |kMaxStreamsForTest| promised responses should be sent.
+ // Since flow control window is smaller than response body, not the
+ // whole body will be sent.
+ if (VersionHasDataFrameHeader(connection_->transport_version())) {
+ EXPECT_CALL(*connection_,
+ SendStreamData(stream_id, header_length, 0, NO_FIN));
+ }
+ EXPECT_CALL(*connection_,
+ SendStreamData(stream_id, _, header_length, NO_FIN))
+ .WillOnce(Return(QuicConsumedData(
+ kStreamFlowControlWindowSize - header_length, false)));
+ EXPECT_CALL(*session_, SendBlocked(stream_id));
+ }
+ }
+ session_->PromisePushResources(request_url, push_resources,
+ GetNthClientInitiatedBidirectionalId(0),
+ request_headers);
+ return header_length;
+ }
+
+ void ConsumeHeadersStreamData() {
+ QuicStreamId headers_stream_id =
+ QuicUtils::GetHeadersStreamId(connection_->transport_version());
+ EXPECT_CALL(*connection_, SendStreamData(headers_stream_id, _, _, _))
+ .Times(AtLeast(1));
+ }
+};
+
+INSTANTIATE_TEST_SUITE_P(Tests,
+ QuicSimpleServerSessionServerPushTest,
+ ::testing::ValuesIn(AllSupportedVersions()));
+
+// Tests that given more than kMaxStreamsForTest resources, all their
+// PUSH_PROMISE's will be sent out and only kMaxStreamsForTest streams will be
+// opened and send push response.
+TEST_P(QuicSimpleServerSessionServerPushTest, TestPromisePushResources) {
+ ConsumeHeadersStreamData();
+ size_t num_resources = kMaxStreamsForTest + 5;
+ PromisePushResources(num_resources);
+ EXPECT_EQ(kMaxStreamsForTest, session_->GetNumOpenOutgoingStreams());
+}
+
+// Tests that after promised stream queued up, when an opened stream is marked
+// draining, a queued promised stream will become open and send push response.
+TEST_P(QuicSimpleServerSessionServerPushTest,
+ HandlePromisedPushRequestsAfterStreamDraining) {
+ ConsumeHeadersStreamData();
+ size_t num_resources = kMaxStreamsForTest + 1;
+ QuicByteCount header_length = PromisePushResources(num_resources);
+ QuicStreamId next_out_going_stream_id =
+ GetNthServerInitiatedUnidirectionalId(kMaxStreamsForTest);
+
+ // After an open stream is marked draining, a new stream is expected to be
+ // created and a response sent on the stream.
+ if (VersionHasDataFrameHeader(connection_->transport_version())) {
+ EXPECT_CALL(*connection_, SendStreamData(next_out_going_stream_id,
+ header_length, 0, NO_FIN));
+ }
+ EXPECT_CALL(*connection_, SendStreamData(next_out_going_stream_id, _,
+ header_length, NO_FIN))
+ .WillOnce(Return(QuicConsumedData(
+ kStreamFlowControlWindowSize - header_length, false)));
+ EXPECT_CALL(*session_, SendBlocked(next_out_going_stream_id));
+
+ if (IsVersion99()) {
+ // The PromisePushedResources call, above, will have used all available
+ // stream ids. For version 99, stream ids are not made available until
+ // a MAX_STREAM_ID frame is received. This emulates the reception of one.
+ // For pre-v-99, the node monitors its own stream usage and makes streams
+ // available as it closes/etc them.
+ session_->OnMaxStreamIdFrame(
+ QuicMaxStreamIdFrame(0, GetNthServerInitiatedUnidirectionalId(10)));
+ }
+ session_->StreamDraining(GetNthServerInitiatedUnidirectionalId(0));
+ // Number of open outgoing streams should still be the same, because a new
+ // stream is opened. And the queue should be empty.
+ EXPECT_EQ(kMaxStreamsForTest, session_->GetNumOpenOutgoingStreams());
+}
+
+// Tests that after all resources are promised, a RST frame from client can
+// prevent a promised resource to be send out.
+TEST_P(QuicSimpleServerSessionServerPushTest,
+ ResetPromisedStreamToCancelServerPush) {
+ ConsumeHeadersStreamData();
+
+ // Having two extra resources to be send later. One of them will be reset, so
+ // when opened stream become close, only one will become open.
+ size_t num_resources = kMaxStreamsForTest + 2;
+ if (IsVersion99()) {
+ // V99 will send out a stream-id-blocked frame when the we desired to exceed
+ // the limit. This will clear the frames so that they do not block the later
+ // rst-stream frame.
+ EXPECT_CALL(*connection_, SendControlFrame(_))
+ .WillOnce(Invoke(
+ this, &QuicSimpleServerSessionServerPushTest::ClearControlFrame));
+ }
+ QuicByteCount header_length = PromisePushResources(num_resources);
+
+ // Reset the last stream in the queue. It should be marked cancelled.
+ QuicStreamId stream_got_reset =
+ GetNthServerInitiatedUnidirectionalId(kMaxStreamsForTest + 1);
+ QuicRstStreamFrame rst(kInvalidControlFrameId, stream_got_reset,
+ QUIC_STREAM_CANCELLED, 0);
+ EXPECT_CALL(owner_, OnRstStreamReceived(_)).Times(1);
+ EXPECT_CALL(*connection_, SendControlFrame(_))
+ .WillOnce(Invoke(
+ this, &QuicSimpleServerSessionServerPushTest::ClearControlFrame));
+ EXPECT_CALL(*connection_,
+ OnStreamReset(stream_got_reset, QUIC_RST_ACKNOWLEDGEMENT));
+ visitor_->OnRstStream(rst);
+
+ // When the first 2 streams becomes draining, the two queued up stream could
+ // be created. But since one of them was marked cancelled due to RST frame,
+ // only one queued resource will be sent out.
+ QuicStreamId stream_not_reset =
+ GetNthServerInitiatedUnidirectionalId(kMaxStreamsForTest);
+ InSequence s;
+ if (VersionHasDataFrameHeader(connection_->transport_version())) {
+ EXPECT_CALL(*connection_,
+ SendStreamData(stream_not_reset, header_length, 0, NO_FIN));
+ }
+ EXPECT_CALL(*connection_,
+ SendStreamData(stream_not_reset, _, header_length, NO_FIN))
+ .WillOnce(Return(QuicConsumedData(
+ kStreamFlowControlWindowSize - header_length, false)));
+ EXPECT_CALL(*session_, SendBlocked(stream_not_reset));
+
+ if (IsVersion99()) {
+ // The PromisePushedResources call, above, will have used all available
+ // stream ids. For version 99, stream ids are not made available until
+ // a MAX_STREAM_ID frame is received. This emulates the reception of one.
+ // For pre-v-99, the node monitors its own stream usage and makes streams
+ // available as it closes/etc them.
+ session_->OnMaxStreamIdFrame(
+ QuicMaxStreamIdFrame(0, GetNthServerInitiatedUnidirectionalId(11)));
+ }
+ session_->StreamDraining(GetNthServerInitiatedUnidirectionalId(0));
+ session_->StreamDraining(GetNthServerInitiatedUnidirectionalId(1));
+}
+
+// Tests that closing a open outgoing stream can trigger a promised resource in
+// the queue to be send out.
+TEST_P(QuicSimpleServerSessionServerPushTest,
+ CloseStreamToHandleMorePromisedStream) {
+ ConsumeHeadersStreamData();
+ size_t num_resources = kMaxStreamsForTest + 1;
+ if (IsVersion99()) {
+ // V99 will send out a stream-id-blocked frame when the we desired to exceed
+ // the limit. This will clear the frames so that they do not block the later
+ // rst-stream frame.
+ EXPECT_CALL(*connection_, SendControlFrame(_))
+ .WillOnce(Invoke(
+ this, &QuicSimpleServerSessionServerPushTest::ClearControlFrame));
+ }
+ QuicByteCount header_length = PromisePushResources(num_resources);
+ QuicStreamId stream_to_open =
+ GetNthServerInitiatedUnidirectionalId(kMaxStreamsForTest);
+
+ // Resetting 1st open stream will close the stream and give space for extra
+ // stream to be opened.
+ QuicStreamId stream_got_reset = GetNthServerInitiatedUnidirectionalId(0);
+ EXPECT_CALL(owner_, OnRstStreamReceived(_)).Times(1);
+ EXPECT_CALL(*connection_, SendControlFrame(_));
+ if (!IsVersion99()) {
+ // For version 99, this is covered in InjectStopSending()
+ EXPECT_CALL(*connection_,
+ OnStreamReset(stream_got_reset, QUIC_RST_ACKNOWLEDGEMENT));
+ }
+ if (VersionHasDataFrameHeader(connection_->transport_version())) {
+ EXPECT_CALL(*connection_,
+ SendStreamData(stream_to_open, header_length, 0, NO_FIN));
+ }
+ EXPECT_CALL(*connection_,
+ SendStreamData(stream_to_open, _, header_length, NO_FIN))
+ .WillOnce(Return(QuicConsumedData(
+ kStreamFlowControlWindowSize - header_length, false)));
+
+ EXPECT_CALL(*session_, SendBlocked(stream_to_open));
+ QuicRstStreamFrame rst(kInvalidControlFrameId, stream_got_reset,
+ QUIC_STREAM_CANCELLED, 0);
+ if (IsVersion99()) {
+ // The PromisePushedResources call, above, will have used all available
+ // stream ids. For version 99, stream ids are not made available until
+ // a MAX_STREAM_ID frame is received. This emulates the reception of one.
+ // For pre-v-99, the node monitors its own stream usage and makes streams
+ // available as it closes/etc them.
+ session_->OnMaxStreamIdFrame(
+ QuicMaxStreamIdFrame(0, GetNthServerInitiatedUnidirectionalId(10)));
+ }
+ visitor_->OnRstStream(rst);
+ // Create and inject a STOP_SENDING frame. In GOOGLE QUIC, receiving a
+ // RST_STREAM frame causes a two-way close. For IETF QUIC, RST_STREAM causes
+ // a one-way close.
+ InjectStopSending(stream_got_reset, QUIC_STREAM_CANCELLED);
+}
+
+} // namespace
+} // namespace test
+} // namespace quic