| // 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 "quic/tools/quic_simple_server_session.h" | |
| #include <algorithm> | |
| #include <memory> | |
| #include <utility> | |
| #include "absl/strings/str_cat.h" | |
| #include "quic/core/crypto/null_encrypter.h" | |
| #include "quic/core/crypto/quic_crypto_server_config.h" | |
| #include "quic/core/crypto/quic_random.h" | |
| #include "quic/core/http/http_encoder.h" | |
| #include "quic/core/proto/cached_network_parameters_proto.h" | |
| #include "quic/core/quic_connection.h" | |
| #include "quic/core/quic_crypto_server_stream.h" | |
| #include "quic/core/quic_utils.h" | |
| #include "quic/core/quic_versions.h" | |
| #include "quic/core/tls_server_handshaker.h" | |
| #include "quic/platform/api/quic_containers.h" | |
| #include "quic/platform/api/quic_expect_bug.h" | |
| #include "quic/platform/api/quic_flags.h" | |
| #include "quic/platform/api/quic_socket_address.h" | |
| #include "quic/platform/api/quic_test.h" | |
| #include "quic/test_tools/crypto_test_utils.h" | |
| #include "quic/test_tools/mock_quic_session_visitor.h" | |
| #include "quic/test_tools/quic_config_peer.h" | |
| #include "quic/test_tools/quic_connection_peer.h" | |
| #include "quic/test_tools/quic_sent_packet_manager_peer.h" | |
| #include "quic/test_tools/quic_session_peer.h" | |
| #include "quic/test_tools/quic_spdy_session_peer.h" | |
| #include "quic/test_tools/quic_stream_peer.h" | |
| #include "quic/test_tools/quic_sustained_bandwidth_recorder_peer.h" | |
| #include "quic/test_tools/quic_test_utils.h" | |
| #include "quic/tools/quic_backend_response.h" | |
| #include "quic/tools/quic_memory_cache_backend.h" | |
| #include "quic/tools/quic_simple_server_stream.h" | |
| using testing::_; | |
| using testing::AnyNumber; | |
| using testing::AtLeast; | |
| using testing::InSequence; | |
| using testing::Invoke; | |
| using testing::Return; | |
| using testing::StrictMock; | |
| namespace quic { | |
| namespace test { | |
| namespace { | |
| // Data to be sent on a request stream. In Google QUIC, this is interpreted as | |
| // DATA payload (there is no framing on request streams). In IETF QUIC, this is | |
| // interpreted as HEADERS frame (type 0x1) with payload length 122 ('z'). Since | |
| // no payload is included, QPACK decoder will not be invoked. | |
| const char* const kStreamData = "\1z"; | |
| } // namespace | |
| class QuicSimpleServerSessionPeer { | |
| public: | |
| static void SetCryptoStream(QuicSimpleServerSession* s, | |
| QuicCryptoServerStreamBase* crypto_stream) { | |
| s->crypto_stream_.reset(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, | |
| QuicSession* session, | |
| QuicCryptoServerStreamBase::Helper* helper) | |
| : QuicCryptoServerStream(crypto_config, | |
| compressed_certs_cache, | |
| session, | |
| helper) {} | |
| MockQuicCryptoServerStream(const MockQuicCryptoServerStream&) = delete; | |
| MockQuicCryptoServerStream& operator=(const MockQuicCryptoServerStream&) = | |
| delete; | |
| ~MockQuicCryptoServerStream() override {} | |
| MOCK_METHOD(void, | |
| SendServerConfigUpdate, | |
| (const CachedNetworkParameters*), | |
| (override)); | |
| bool encryption_established() const override { return true; } | |
| }; | |
| class MockTlsServerHandshaker : public TlsServerHandshaker { | |
| public: | |
| explicit MockTlsServerHandshaker(QuicSession* session, | |
| const QuicCryptoServerConfig* crypto_config) | |
| : TlsServerHandshaker(session, crypto_config) {} | |
| MockTlsServerHandshaker(const MockTlsServerHandshaker&) = delete; | |
| MockTlsServerHandshaker& operator=(const MockTlsServerHandshaker&) = delete; | |
| ~MockTlsServerHandshaker() override {} | |
| MOCK_METHOD(void, | |
| SendServerConfigUpdate, | |
| (const CachedNetworkParameters*), | |
| (override)); | |
| bool encryption_established() const override { return true; } | |
| }; | |
| QuicCryptoServerStreamBase* CreateMockCryptoServerStream( | |
| const QuicCryptoServerConfig* crypto_config, | |
| QuicCompressedCertsCache* compressed_certs_cache, | |
| QuicSession* session, | |
| QuicCryptoServerStreamBase::Helper* helper) { | |
| switch (session->connection()->version().handshake_protocol) { | |
| case PROTOCOL_QUIC_CRYPTO: | |
| return new MockQuicCryptoServerStream( | |
| crypto_config, compressed_certs_cache, session, helper); | |
| case PROTOCOL_TLS1_3: | |
| return new MockTlsServerHandshaker(session, crypto_config); | |
| case PROTOCOL_UNSUPPORTED: | |
| break; | |
| } | |
| QUIC_BUG(quic_bug_10933_1) | |
| << "Unknown handshake protocol: " | |
| << static_cast<int>(session->connection()->version().handshake_protocol); | |
| return nullptr; | |
| } | |
| 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_METHOD(QuicConsumedData, | |
| SendStreamData, | |
| (QuicStreamId id, | |
| size_t write_length, | |
| QuicStreamOffset offset, | |
| StreamSendingState state), | |
| (override)); | |
| }; | |
| class MockQuicSimpleServerSession : public QuicSimpleServerSession { | |
| public: | |
| MockQuicSimpleServerSession( | |
| const QuicConfig& config, | |
| QuicConnection* connection, | |
| QuicSession::Visitor* visitor, | |
| QuicCryptoServerStreamBase::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 Http2HeaderBlock by value cannot be | |
| // mocked directly. | |
| void WritePushPromise(QuicStreamId original_stream_id, | |
| QuicStreamId promised_stream_id, | |
| spdy::Http2HeaderBlock headers) override { | |
| return WritePushPromiseMock(original_stream_id, promised_stream_id, | |
| headers); | |
| } | |
| MOCK_METHOD(void, | |
| WritePushPromiseMock, | |
| (QuicStreamId original_stream_id, | |
| QuicStreamId promised_stream_id, | |
| const spdy::Http2HeaderBlock& headers), | |
| ()); | |
| MOCK_METHOD(void, SendBlocked, (QuicStreamId), (override)); | |
| MOCK_METHOD(bool, | |
| WriteControlFrame, | |
| (const QuicFrame& frame, TransmissionType type), | |
| (override)); | |
| }; | |
| class QuicSimpleServerSessionTest | |
| : public QuicTestWithParam<ParsedQuicVersion> { | |
| public: | |
| // The function ensures that A) the MAX_STREAMS 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 ClearMaxStreamsControlFrame(const QuicFrame& frame) { | |
| if (frame.type == MAX_STREAMS_FRAME) { | |
| DeleteFrame(&const_cast<QuicFrame&>(frame)); | |
| return true; | |
| } | |
| return false; | |
| } | |
| protected: | |
| QuicSimpleServerSessionTest() | |
| : crypto_config_(QuicCryptoServerConfig::TESTING, | |
| QuicRandom::GetInstance(), | |
| crypto_test_utils::ProofSourceForTesting(), | |
| KeyExchangeSource::Default()), | |
| compressed_certs_cache_( | |
| QuicCompressedCertsCache::kQuicCompressedCertsCacheSize) { | |
| config_.SetMaxBidirectionalStreamsToSend(kMaxStreamsForTest); | |
| QuicConfigPeer::SetReceivedMaxBidirectionalStreams(&config_, | |
| kMaxStreamsForTest); | |
| config_.SetMaxUnidirectionalStreamsToSend(kMaxStreamsForTest); | |
| config_.SetInitialStreamFlowControlWindowToSend( | |
| kInitialStreamFlowControlWindowForTest); | |
| config_.SetInitialMaxStreamDataBytesIncomingBidirectionalToSend( | |
| kInitialStreamFlowControlWindowForTest); | |
| config_.SetInitialMaxStreamDataBytesOutgoingBidirectionalToSend( | |
| kInitialStreamFlowControlWindowForTest); | |
| config_.SetInitialMaxStreamDataBytesUnidirectionalToSend( | |
| kInitialStreamFlowControlWindowForTest); | |
| config_.SetInitialSessionFlowControlWindowToSend( | |
| kInitialSessionFlowControlWindowForTest); | |
| if (VersionUsesHttp3(transport_version())) { | |
| QuicConfigPeer::SetReceivedMaxUnidirectionalStreams( | |
| &config_, kMaxStreamsForTest + 3); | |
| } else { | |
| QuicConfigPeer::SetReceivedMaxUnidirectionalStreams(&config_, | |
| kMaxStreamsForTest); | |
| } | |
| ParsedQuicVersionVector supported_versions = SupportedVersions(version()); | |
| connection_ = new StrictMock<MockQuicConnectionWithSendStreamData>( | |
| &helper_, &alarm_factory_, Perspective::IS_SERVER, supported_versions); | |
| connection_->AdvanceTime(QuicTime::Delta::FromSeconds(1)); | |
| connection_->SetEncrypter( | |
| ENCRYPTION_FORWARD_SECURE, | |
| std::make_unique<NullEncrypter>(connection_->perspective())); | |
| session_ = std::make_unique<MockQuicSimpleServerSession>( | |
| config_, connection_, &owner_, &stream_helper_, &crypto_config_, | |
| &compressed_certs_cache_, &memory_cache_backend_); | |
| MockClock clock; | |
| handshake_message_ = crypto_config_.AddDefaultConfig( | |
| QuicRandom::GetInstance(), &clock, | |
| QuicCryptoServerConfig::ConfigOptions()); | |
| session_->Initialize(); | |
| if (VersionHasIetfQuicFrames(transport_version())) { | |
| EXPECT_CALL(*session_, WriteControlFrame(_, _)) | |
| .WillRepeatedly(Invoke(&ClearControlFrameWithTransmissionType)); | |
| } | |
| session_->OnConfigNegotiated(); | |
| } | |
| QuicStreamId GetNthClientInitiatedBidirectionalId(int n) { | |
| return GetNthClientInitiatedBidirectionalStreamId(transport_version(), n); | |
| } | |
| QuicStreamId GetNthServerInitiatedUnidirectionalId(int n) { | |
| return quic::test::GetNthServerInitiatedUnidirectionalStreamId( | |
| transport_version(), n); | |
| } | |
| ParsedQuicVersion version() const { return GetParam(); } | |
| QuicTransportVersion transport_version() const { | |
| return version().transport_version; | |
| } | |
| 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 (!VersionHasIetfQuicFrames(transport_version())) { | |
| // Only needed for version 99/IETF QUIC. | |
| return; | |
| } | |
| EXPECT_CALL(owner_, OnStopSendingReceived(_)).Times(1); | |
| QuicStopSendingFrame stop_sending(kInvalidControlFrameId, stream_id, | |
| 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_; | |
| }; | |
| INSTANTIATE_TEST_SUITE_P(Tests, | |
| QuicSimpleServerSessionTest, | |
| ::testing::ValuesIn(AllSupportedVersions()), | |
| ::testing::PrintToStringParamName()); | |
| TEST_P(QuicSimpleServerSessionTest, CloseStreamDueToReset) { | |
| // Send some data open a stream, then reset it. | |
| QuicStreamFrame data1(GetNthClientInitiatedBidirectionalId(0), false, 0, | |
| kStreamData); | |
| session_->OnStreamFrame(data1); | |
| EXPECT_EQ(1u, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get())); | |
| // 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(*session_, WriteControlFrame(_, _)); | |
| if (!VersionHasIetfQuicFrames(transport_version())) { | |
| // For version 99, this is covered in InjectStopSending() | |
| EXPECT_CALL(*connection_, | |
| OnStreamReset(GetNthClientInitiatedBidirectionalId(0), | |
| QUIC_RST_ACKNOWLEDGEMENT)); | |
| } | |
| session_->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, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get())); | |
| // Send the same two bytes of payload in a new packet. | |
| session_->OnStreamFrame(data1); | |
| // The stream should not be re-opened. | |
| EXPECT_EQ(0u, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get())); | |
| 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 (!VersionHasIetfQuicFrames(transport_version())) { | |
| EXPECT_CALL(*session_, WriteControlFrame(_, _)); | |
| // For version 99, this is covered in InjectStopSending() | |
| EXPECT_CALL(*connection_, | |
| OnStreamReset(GetNthClientInitiatedBidirectionalId(0), | |
| QUIC_RST_ACKNOWLEDGEMENT)); | |
| } | |
| session_->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, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get())); | |
| QuicStreamFrame data1(GetNthClientInitiatedBidirectionalId(0), false, 0, | |
| kStreamData); | |
| session_->OnStreamFrame(data1); | |
| // The stream should never be opened, now that the reset is received. | |
| EXPECT_EQ(0u, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get())); | |
| EXPECT_TRUE(connection_->connected()); | |
| } | |
| TEST_P(QuicSimpleServerSessionTest, AcceptClosedStream) { | |
| // Send some data to open two streams. | |
| QuicStreamFrame frame1(GetNthClientInitiatedBidirectionalId(0), false, 0, | |
| kStreamData); | |
| QuicStreamFrame frame2(GetNthClientInitiatedBidirectionalId(1), false, 0, | |
| kStreamData); | |
| session_->OnStreamFrame(frame1); | |
| session_->OnStreamFrame(frame2); | |
| EXPECT_EQ(2u, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get())); | |
| // 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 (!VersionHasIetfQuicFrames(transport_version())) { | |
| EXPECT_CALL(*session_, WriteControlFrame(_, _)); | |
| // For version 99, this is covered in InjectStopSending() | |
| EXPECT_CALL(*connection_, | |
| OnStreamReset(GetNthClientInitiatedBidirectionalId(0), | |
| QUIC_RST_ACKNOWLEDGEMENT)); | |
| } | |
| session_->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, | |
| kStreamData); | |
| QuicStreamFrame frame4(GetNthClientInitiatedBidirectionalId(1), false, 2, | |
| kStreamData); | |
| session_->OnStreamFrame(frame3); | |
| session_->OnStreamFrame(frame4); | |
| // The stream should never be opened, now that the reset is received. | |
| EXPECT_EQ(1u, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get())); | |
| EXPECT_TRUE(connection_->connected()); | |
| } | |
| TEST_P(QuicSimpleServerSessionTest, CreateIncomingStreamDisconnected) { | |
| // EXPECT_QUIC_BUG tests are expensive so only run one instance of them. | |
| if (version() != AllSupportedVersions()[0]) { | |
| return; | |
| } | |
| // Tests that incoming stream creation fails when connection is not connected. | |
| size_t initial_num_open_stream = | |
| QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()); | |
| QuicConnectionPeer::TearDownLocalConnectionState(connection_); | |
| EXPECT_QUIC_BUG(QuicSimpleServerSessionPeer::CreateIncomingStream( | |
| session_.get(), GetNthClientInitiatedBidirectionalId(0)), | |
| "ShouldCreateIncomingStream called when disconnected"); | |
| EXPECT_EQ(initial_num_open_stream, | |
| QuicSessionPeer::GetNumOpenDynamicStreams(session_.get())); | |
| } | |
| 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 (version() != AllSupportedVersions()[0]) { | |
| return; | |
| } | |
| // Tests that outgoing stream creation fails when connection is not connected. | |
| size_t initial_num_open_stream = | |
| QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()); | |
| QuicConnectionPeer::TearDownLocalConnectionState(connection_); | |
| EXPECT_QUIC_BUG( | |
| QuicSimpleServerSessionPeer::CreateOutgoingUnidirectionalStream( | |
| session_.get()), | |
| "ShouldCreateOutgoingUnidirectionalStream called when disconnected"); | |
| EXPECT_EQ(initial_num_open_stream, | |
| QuicSessionPeer::GetNumOpenDynamicStreams(session_.get())); | |
| } | |
| TEST_P(QuicSimpleServerSessionTest, CreateOutgoingDynamicStreamUnencrypted) { | |
| // EXPECT_QUIC_BUG tests are expensive so only run one instance of them. | |
| if (version() != AllSupportedVersions()[0]) { | |
| return; | |
| } | |
| // Tests that outgoing stream creation fails when encryption has not yet been | |
| // established. | |
| size_t initial_num_open_stream = | |
| QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()); | |
| EXPECT_QUIC_BUG( | |
| QuicSimpleServerSessionPeer::CreateOutgoingUnidirectionalStream( | |
| session_.get()), | |
| "Encryption not established so no outgoing stream created."); | |
| EXPECT_EQ(initial_num_open_stream, | |
| QuicSessionPeer::GetNumOpenDynamicStreams(session_.get())); | |
| } | |
| 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, | |
| kStreamData); | |
| session_->OnStreamFrame(data1); | |
| EXPECT_EQ(1u, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get())); | |
| EXPECT_EQ(0u, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()) - | |
| /*incoming=*/1); | |
| if (!VersionUsesHttp3(transport_version())) { | |
| session_->UnregisterStreamPriority( | |
| QuicUtils::GetHeadersStreamId(transport_version()), | |
| /*is_static=*/true); | |
| } | |
| // Assume encryption already established. | |
| QuicSimpleServerSessionPeer::SetCryptoStream(session_.get(), nullptr); | |
| QuicCryptoServerStreamBase* crypto_stream = | |
| CreateMockCryptoServerStream(&crypto_config_, &compressed_certs_cache_, | |
| session_.get(), &stream_helper_); | |
| QuicSimpleServerSessionPeer::SetCryptoStream(session_.get(), crypto_stream); | |
| if (!VersionUsesHttp3(transport_version())) { | |
| session_->RegisterStreamPriority( | |
| QuicUtils::GetHeadersStreamId(transport_version()), | |
| /*is_static=*/true, | |
| spdy::SpdyStreamPrecedence(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()); | |
| if (VersionUsesHttp3(transport_version())) { | |
| EXPECT_EQ(GetNthServerInitiatedUnidirectionalId(i + 3), | |
| created_stream->id()); | |
| } else { | |
| EXPECT_EQ(GetNthServerInitiatedUnidirectionalId(i), created_stream->id()); | |
| } | |
| EXPECT_EQ(i + 1, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()) - | |
| /*incoming=*/1); | |
| } | |
| // Continuing creating push stream would fail. | |
| EXPECT_EQ(nullptr, | |
| QuicSimpleServerSessionPeer::CreateOutgoingUnidirectionalStream( | |
| session_.get())); | |
| EXPECT_EQ(kMaxStreamsForTest, | |
| QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()) - | |
| /*incoming=*/1); | |
| // Create peer initiated stream should have no problem. | |
| QuicStreamFrame data2(GetNthClientInitiatedBidirectionalId(1), false, 0, | |
| kStreamData); | |
| session_->OnStreamFrame(data2); | |
| EXPECT_EQ(2u, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()) - | |
| /*outcoming=*/kMaxStreamsForTest); | |
| } | |
| TEST_P(QuicSimpleServerSessionTest, OnStreamFrameWithEvenStreamId) { | |
| QuicStreamFrame frame(GetNthServerInitiatedUnidirectionalId(0), false, 0, | |
| kStreamData); | |
| EXPECT_CALL(*connection_, | |
| CloseConnection(QUIC_INVALID_STREAM_ID, | |
| "Client sent data on server push stream", _)); | |
| session_->OnStreamFrame(frame); | |
| } | |
| // Tests that calling GetOrCreateStream() on an outgoing stream not promised yet | |
| // should result close connection. | |
| TEST_P(QuicSimpleServerSessionTest, GetEvenIncomingError) { | |
| const size_t initial_num_open_stream = | |
| QuicSessionPeer::GetNumOpenDynamicStreams(session_.get()); | |
| const QuicErrorCode expected_error = VersionUsesHttp3(transport_version()) | |
| ? QUIC_HTTP_STREAM_WRONG_DIRECTION | |
| : QUIC_INVALID_STREAM_ID; | |
| EXPECT_CALL(*connection_, CloseConnection(expected_error, | |
| "Data for nonexistent stream", _)); | |
| EXPECT_EQ(nullptr, | |
| QuicSessionPeer::GetOrCreateStream( | |
| session_.get(), GetNthServerInitiatedUnidirectionalId(3))); | |
| EXPECT_EQ(initial_num_open_stream, | |
| QuicSessionPeer::GetNumOpenDynamicStreams(session_.get())); | |
| } | |
| } // namespace | |
| } // namespace test | |
| } // namespace quic |