| // 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 "quiche/quic/core/http/quic_server_session_base.h" |
| |
| #include <cstdint> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "absl/memory/memory.h" |
| #include "quiche/quic/core/crypto/null_encrypter.h" |
| #include "quiche/quic/core/crypto/quic_crypto_server_config.h" |
| #include "quiche/quic/core/crypto/quic_random.h" |
| #include "quiche/quic/core/proto/cached_network_parameters_proto.h" |
| #include "quiche/quic/core/quic_connection.h" |
| #include "quiche/quic/core/quic_crypto_server_stream.h" |
| #include "quiche/quic/core/quic_crypto_server_stream_base.h" |
| #include "quiche/quic/core/quic_types.h" |
| #include "quiche/quic/core/quic_utils.h" |
| #include "quiche/quic/core/tls_server_handshaker.h" |
| #include "quiche/quic/platform/api/quic_expect_bug.h" |
| #include "quiche/quic/platform/api/quic_flags.h" |
| #include "quiche/quic/platform/api/quic_socket_address.h" |
| #include "quiche/quic/platform/api/quic_test.h" |
| #include "quiche/quic/test_tools/crypto_test_utils.h" |
| #include "quiche/quic/test_tools/fake_proof_source.h" |
| #include "quiche/quic/test_tools/mock_quic_session_visitor.h" |
| #include "quiche/quic/test_tools/quic_config_peer.h" |
| #include "quiche/quic/test_tools/quic_connection_peer.h" |
| #include "quiche/quic/test_tools/quic_crypto_server_config_peer.h" |
| #include "quiche/quic/test_tools/quic_sent_packet_manager_peer.h" |
| #include "quiche/quic/test_tools/quic_server_session_base_peer.h" |
| #include "quiche/quic/test_tools/quic_session_peer.h" |
| #include "quiche/quic/test_tools/quic_spdy_session_peer.h" |
| #include "quiche/quic/test_tools/quic_stream_id_manager_peer.h" |
| #include "quiche/quic/test_tools/quic_stream_peer.h" |
| #include "quiche/quic/test_tools/quic_sustained_bandwidth_recorder_peer.h" |
| #include "quiche/quic/test_tools/quic_test_utils.h" |
| #include "quiche/quic/tools/quic_memory_cache_backend.h" |
| #include "quiche/quic/tools/quic_simple_server_stream.h" |
| |
| using testing::_; |
| using testing::StrictMock; |
| |
| using testing::AtLeast; |
| |
| 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"; |
| |
| class TestServerSession : public QuicServerSessionBase { |
| public: |
| TestServerSession(const QuicConfig& config, QuicConnection* connection, |
| QuicSession::Visitor* visitor, |
| QuicCryptoServerStreamBase::Helper* helper, |
| const QuicCryptoServerConfig* crypto_config, |
| QuicCompressedCertsCache* compressed_certs_cache, |
| QuicSimpleServerBackend* quic_simple_server_backend) |
| : QuicServerSessionBase(config, CurrentSupportedVersions(), connection, |
| visitor, helper, crypto_config, |
| compressed_certs_cache), |
| quic_simple_server_backend_(quic_simple_server_backend) {} |
| |
| ~TestServerSession() override { DeleteConnection(); } |
| |
| MOCK_METHOD(bool, WriteControlFrame, |
| (const QuicFrame& frame, TransmissionType type), (override)); |
| |
| protected: |
| QuicSpdyStream* CreateIncomingStream(QuicStreamId id) override { |
| if (!ShouldCreateIncomingStream(id)) { |
| return nullptr; |
| } |
| QuicSpdyStream* stream = new QuicSimpleServerStream( |
| id, this, BIDIRECTIONAL, quic_simple_server_backend_); |
| ActivateStream(absl::WrapUnique(stream)); |
| return stream; |
| } |
| |
| QuicSpdyStream* CreateIncomingStream(PendingStream* pending) override { |
| QuicSpdyStream* stream = |
| new QuicSimpleServerStream(pending, this, quic_simple_server_backend_); |
| ActivateStream(absl::WrapUnique(stream)); |
| return stream; |
| } |
| |
| QuicSpdyStream* CreateOutgoingBidirectionalStream() override { |
| QUICHE_DCHECK(false); |
| return nullptr; |
| } |
| |
| QuicSpdyStream* CreateOutgoingUnidirectionalStream() override { |
| if (!ShouldCreateOutgoingUnidirectionalStream()) { |
| return nullptr; |
| } |
| |
| QuicSpdyStream* stream = new QuicSimpleServerStream( |
| GetNextOutgoingUnidirectionalStreamId(), this, WRITE_UNIDIRECTIONAL, |
| quic_simple_server_backend_); |
| ActivateStream(absl::WrapUnique(stream)); |
| return stream; |
| } |
| |
| std::unique_ptr<QuicCryptoServerStreamBase> CreateQuicCryptoServerStream( |
| const QuicCryptoServerConfig* crypto_config, |
| QuicCompressedCertsCache* compressed_certs_cache) override { |
| return CreateCryptoServerStream(crypto_config, compressed_certs_cache, this, |
| stream_helper()); |
| } |
| |
| private: |
| QuicSimpleServerBackend* |
| quic_simple_server_backend_; // Owned by QuicServerSessionBaseTest |
| }; |
| |
| const size_t kMaxStreamsForTest = 10; |
| |
| class QuicServerSessionBaseTest : public QuicTestWithParam<ParsedQuicVersion> { |
| protected: |
| QuicServerSessionBaseTest() |
| : QuicServerSessionBaseTest(crypto_test_utils::ProofSourceForTesting()) {} |
| |
| explicit QuicServerSessionBaseTest(std::unique_ptr<ProofSource> proof_source) |
| : crypto_config_(QuicCryptoServerConfig::TESTING, |
| QuicRandom::GetInstance(), std::move(proof_source), |
| KeyExchangeSource::Default()), |
| compressed_certs_cache_( |
| QuicCompressedCertsCache::kQuicCompressedCertsCacheSize) { |
| config_.SetMaxBidirectionalStreamsToSend(kMaxStreamsForTest); |
| config_.SetMaxUnidirectionalStreamsToSend(kMaxStreamsForTest); |
| QuicConfigPeer::SetReceivedMaxBidirectionalStreams(&config_, |
| kMaxStreamsForTest); |
| QuicConfigPeer::SetReceivedMaxUnidirectionalStreams(&config_, |
| kMaxStreamsForTest); |
| config_.SetInitialStreamFlowControlWindowToSend( |
| kInitialStreamFlowControlWindowForTest); |
| config_.SetInitialSessionFlowControlWindowToSend( |
| kInitialSessionFlowControlWindowForTest); |
| |
| ParsedQuicVersionVector supported_versions = SupportedVersions(version()); |
| connection_ = new StrictMock<MockQuicConnection>( |
| &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<TestServerSession>( |
| 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(); |
| QuicConfigPeer::SetReceivedInitialSessionFlowControlWindow( |
| session_->config(), kMinimumFlowControlSendWindow); |
| session_->OnConfigNegotiated(); |
| if (version().SupportsAntiAmplificationLimit()) { |
| QuicConnectionPeer::SetAddressValidated(connection_); |
| } |
| } |
| |
| 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; |
| } |
| |
| // 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. This method can be used to inject a STOP_SENDING, which |
| // would cause a close in the opposite direction. This allows tests to do the |
| // extra work to get a two-way (full) close where desired. Also sets up |
| // expects needed to ensure that the STOP_SENDING worked as expected. |
| void InjectStopSendingFrame(QuicStreamId stream_id) { |
| if (!VersionHasIetfQuicFrames(transport_version())) { |
| // Only needed for version 99/IETF QUIC. Noop otherwise. |
| return; |
| } |
| QuicStopSendingFrame stop_sending(kInvalidControlFrameId, stream_id, |
| QUIC_ERROR_PROCESSING_STREAM); |
| EXPECT_CALL(owner_, OnStopSendingReceived(_)).Times(1); |
| // Expect the RESET_STREAM that is generated in response to receiving a |
| // STOP_SENDING. |
| EXPECT_CALL(*session_, WriteControlFrame(_, _)); |
| EXPECT_CALL(*connection_, |
| OnStreamReset(stream_id, QUIC_ERROR_PROCESSING_STREAM)); |
| session_->OnStopSendingFrame(stop_sending); |
| } |
| |
| StrictMock<MockQuicSessionVisitor> owner_; |
| StrictMock<MockQuicCryptoServerStreamHelper> stream_helper_; |
| MockQuicConnectionHelper helper_; |
| MockAlarmFactory alarm_factory_; |
| StrictMock<MockQuicConnection>* connection_; |
| QuicConfig config_; |
| QuicCryptoServerConfig crypto_config_; |
| QuicCompressedCertsCache compressed_certs_cache_; |
| QuicMemoryCacheBackend memory_cache_backend_; |
| std::unique_ptr<TestServerSession> session_; |
| std::unique_ptr<CryptoHandshakeMessage> handshake_message_; |
| }; |
| |
| // Compares CachedNetworkParameters. |
| MATCHER_P(EqualsProto, network_params, "") { |
| CachedNetworkParameters reference(network_params); |
| return (arg->bandwidth_estimate_bytes_per_second() == |
| reference.bandwidth_estimate_bytes_per_second() && |
| arg->bandwidth_estimate_bytes_per_second() == |
| reference.bandwidth_estimate_bytes_per_second() && |
| arg->max_bandwidth_estimate_bytes_per_second() == |
| reference.max_bandwidth_estimate_bytes_per_second() && |
| arg->max_bandwidth_timestamp_seconds() == |
| reference.max_bandwidth_timestamp_seconds() && |
| arg->min_rtt_ms() == reference.min_rtt_ms() && |
| arg->previous_connection_state() == |
| reference.previous_connection_state()); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(Tests, QuicServerSessionBaseTest, |
| ::testing::ValuesIn(AllSupportedVersions()), |
| ::testing::PrintToStringParamName()); |
| |
| TEST_P(QuicServerSessionBaseTest, 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())); |
| |
| // 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())) { |
| // For non-version 99, the RESET_STREAM will do the full close. |
| // Set up expects accordingly. |
| EXPECT_CALL(*session_, WriteControlFrame(_, _)); |
| EXPECT_CALL(*connection_, |
| OnStreamReset(GetNthClientInitiatedBidirectionalId(0), |
| QUIC_RST_ACKNOWLEDGEMENT)); |
| } |
| session_->OnRstStream(rst1); |
| |
| // For version-99 will create and receive a stop-sending, completing |
| // the full-close expected by this test. |
| InjectStopSendingFrame(GetNthClientInitiatedBidirectionalId(0)); |
| |
| 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(QuicServerSessionBaseTest, 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())) { |
| // For non-version 99, the RESET_STREAM will do the full close. |
| // Set up expects accordingly. |
| EXPECT_CALL(*session_, WriteControlFrame(_, _)); |
| EXPECT_CALL(*connection_, |
| OnStreamReset(GetNthClientInitiatedBidirectionalId(0), |
| QUIC_RST_ACKNOWLEDGEMENT)); |
| } |
| session_->OnRstStream(rst1); |
| |
| // For version-99 will create and receive a stop-sending, completing |
| // the full-close expected by this test. |
| InjectStopSendingFrame(GetNthClientInitiatedBidirectionalId(0)); |
| |
| 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(QuicServerSessionBaseTest, 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())) { |
| // For non-version 99, the RESET_STREAM will do the full close. |
| // Set up expects accordingly. |
| EXPECT_CALL(*session_, WriteControlFrame(_, _)); |
| EXPECT_CALL(*connection_, |
| OnStreamReset(GetNthClientInitiatedBidirectionalId(0), |
| QUIC_RST_ACKNOWLEDGEMENT)); |
| } |
| session_->OnRstStream(rst); |
| |
| // For version-99 will create and receive a stop-sending, completing |
| // the full-close expected by this test. |
| InjectStopSendingFrame(GetNthClientInitiatedBidirectionalId(0)); |
| |
| // 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(QuicServerSessionBaseTest, MaxOpenStreams) { |
| // Test that the server refuses if a client attempts to open too many data |
| // streams. For versions other than version 99, the server accepts slightly |
| // more than the negotiated stream limit to deal with rare cases where a |
| // client FIN/RST is lost. |
| connection_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE); |
| session_->OnConfigNegotiated(); |
| if (!VersionHasIetfQuicFrames(transport_version())) { |
| // The slightly increased stream limit is set during config negotiation. It |
| // is either an increase of 10 over negotiated limit, or a fixed percentage |
| // scaling, whichever is larger. Test both before continuing. |
| EXPECT_LT(kMaxStreamsMultiplier * kMaxStreamsForTest, |
| kMaxStreamsForTest + kMaxStreamsMinimumIncrement); |
| EXPECT_EQ(kMaxStreamsForTest + kMaxStreamsMinimumIncrement, |
| session_->max_open_incoming_bidirectional_streams()); |
| } |
| EXPECT_EQ(0u, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get())); |
| QuicStreamId stream_id = GetNthClientInitiatedBidirectionalId(0); |
| // Open the max configured number of streams, should be no problem. |
| for (size_t i = 0; i < kMaxStreamsForTest; ++i) { |
| EXPECT_TRUE(QuicServerSessionBasePeer::GetOrCreateStream(session_.get(), |
| stream_id)); |
| stream_id += QuicUtils::StreamIdDelta(transport_version()); |
| } |
| |
| if (!VersionHasIetfQuicFrames(transport_version())) { |
| // Open more streams: server should accept slightly more than the limit. |
| // Excess streams are for non-version-99 only. |
| for (size_t i = 0; i < kMaxStreamsMinimumIncrement; ++i) { |
| EXPECT_TRUE(QuicServerSessionBasePeer::GetOrCreateStream(session_.get(), |
| stream_id)); |
| stream_id += QuicUtils::StreamIdDelta(transport_version()); |
| } |
| } |
| // Now violate the server's internal stream limit. |
| stream_id += QuicUtils::StreamIdDelta(transport_version()); |
| |
| if (!VersionHasIetfQuicFrames(transport_version())) { |
| // For non-version 99, QUIC responds to an attempt to exceed the stream |
| // limit by resetting the stream. |
| EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0); |
| EXPECT_CALL(*session_, WriteControlFrame(_, _)); |
| EXPECT_CALL(*connection_, OnStreamReset(stream_id, QUIC_REFUSED_STREAM)); |
| } else { |
| // In version 99 QUIC responds to an attempt to exceed the stream limit by |
| // closing the connection. |
| EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(1); |
| } |
| // Even if the connection remains open, the stream creation should fail. |
| EXPECT_FALSE( |
| QuicServerSessionBasePeer::GetOrCreateStream(session_.get(), stream_id)); |
| } |
| |
| TEST_P(QuicServerSessionBaseTest, MaxAvailableBidirectionalStreams) { |
| // Test that the server closes the connection if a client makes too many data |
| // streams available. The server accepts slightly more than the negotiated |
| // stream limit to deal with rare cases where a client FIN/RST is lost. |
| connection_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE); |
| session_->OnConfigNegotiated(); |
| const size_t kAvailableStreamLimit = |
| session_->MaxAvailableBidirectionalStreams(); |
| |
| EXPECT_EQ(0u, QuicSessionPeer::GetNumOpenDynamicStreams(session_.get())); |
| EXPECT_TRUE(QuicServerSessionBasePeer::GetOrCreateStream( |
| session_.get(), GetNthClientInitiatedBidirectionalId(0))); |
| |
| // Establish available streams up to the server's limit. |
| QuicStreamId next_id = QuicUtils::StreamIdDelta(transport_version()); |
| const int kLimitingStreamId = |
| GetNthClientInitiatedBidirectionalId(kAvailableStreamLimit + 1); |
| if (!VersionHasIetfQuicFrames(transport_version())) { |
| // This exceeds the stream limit. In versions other than 99 |
| // this is allowed. Version 99 hews to the IETF spec and does |
| // not allow it. |
| EXPECT_TRUE(QuicServerSessionBasePeer::GetOrCreateStream( |
| session_.get(), kLimitingStreamId)); |
| // A further available stream will result in connection close. |
| EXPECT_CALL(*connection_, |
| CloseConnection(QUIC_TOO_MANY_AVAILABLE_STREAMS, _, _)); |
| } else { |
| // A further available stream will result in connection close. |
| EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_STREAM_ID, _, _)); |
| } |
| |
| // This forces stream kLimitingStreamId + 2 to become available, which |
| // violates the quota. |
| EXPECT_FALSE(QuicServerSessionBasePeer::GetOrCreateStream( |
| session_.get(), kLimitingStreamId + 2 * next_id)); |
| } |
| |
| TEST_P(QuicServerSessionBaseTest, GetEvenIncomingError) { |
| // Incoming streams on the server session must be odd. |
| const QuicErrorCode expected_error = |
| VersionHasIetfQuicFrames(transport_version()) |
| ? QUIC_HTTP_STREAM_WRONG_DIRECTION |
| : QUIC_INVALID_STREAM_ID; |
| EXPECT_CALL(*connection_, CloseConnection(expected_error, _, _)); |
| EXPECT_EQ(nullptr, QuicServerSessionBasePeer::GetOrCreateStream( |
| session_.get(), |
| session_->next_outgoing_unidirectional_stream_id())); |
| } |
| |
| TEST_P(QuicServerSessionBaseTest, GetStreamDisconnected) { |
| // EXPECT_QUIC_BUG tests are expensive so only run one instance of them. |
| if (version() != AllSupportedVersions()[0]) { |
| return; |
| } |
| |
| // Don't create new streams if the connection is disconnected. |
| QuicConnectionPeer::TearDownLocalConnectionState(connection_); |
| EXPECT_QUIC_BUG(QuicServerSessionBasePeer::GetOrCreateStream( |
| session_.get(), GetNthClientInitiatedBidirectionalId(0)), |
| "ShouldCreateIncomingStream called when disconnected"); |
| } |
| |
| class MockQuicCryptoServerStream : public QuicCryptoServerStream { |
| public: |
| explicit MockQuicCryptoServerStream( |
| const QuicCryptoServerConfig* crypto_config, |
| QuicCompressedCertsCache* compressed_certs_cache, |
| QuicServerSessionBase* 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)); |
| }; |
| |
| class MockTlsServerHandshaker : public TlsServerHandshaker { |
| public: |
| explicit MockTlsServerHandshaker(QuicServerSessionBase* 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)); |
| |
| MOCK_METHOD(std::string, GetAddressToken, (const CachedNetworkParameters*), |
| (const, override)); |
| }; |
| |
| TEST_P(QuicServerSessionBaseTest, BandwidthEstimates) { |
| if (version().UsesTls() && !version().HasIetfQuicFrames()) { |
| // Skip the Txxx versions. |
| return; |
| } |
| |
| // Test that bandwidth estimate updates are sent to the client, only when |
| // bandwidth resumption is enabled, the bandwidth estimate has changed |
| // sufficiently, enough time has passed, |
| // and we don't have any other data to write. |
| |
| // Client has sent kBWRE connection option to trigger bandwidth resumption. |
| QuicTagVector copt; |
| copt.push_back(kBWRE); |
| copt.push_back(kBWID); |
| QuicConfigPeer::SetReceivedConnectionOptions(session_->config(), copt); |
| connection_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE); |
| session_->OnConfigNegotiated(); |
| EXPECT_TRUE( |
| QuicServerSessionBasePeer::IsBandwidthResumptionEnabled(session_.get())); |
| |
| int32_t bandwidth_estimate_kbytes_per_second = 123; |
| int32_t max_bandwidth_estimate_kbytes_per_second = 134; |
| int32_t max_bandwidth_estimate_timestamp = 1122334455; |
| const std::string serving_region = "not a real region"; |
| session_->set_serving_region(serving_region); |
| |
| if (!VersionUsesHttp3(transport_version())) { |
| session_->UnregisterStreamPriority( |
| QuicUtils::GetHeadersStreamId(transport_version()), |
| /*is_static=*/true); |
| } |
| QuicServerSessionBasePeer::SetCryptoStream(session_.get(), nullptr); |
| MockQuicCryptoServerStream* quic_crypto_stream = nullptr; |
| MockTlsServerHandshaker* tls_server_stream = nullptr; |
| if (version().handshake_protocol == PROTOCOL_QUIC_CRYPTO) { |
| quic_crypto_stream = new MockQuicCryptoServerStream( |
| &crypto_config_, &compressed_certs_cache_, session_.get(), |
| &stream_helper_); |
| QuicServerSessionBasePeer::SetCryptoStream(session_.get(), |
| quic_crypto_stream); |
| } else { |
| tls_server_stream = |
| new MockTlsServerHandshaker(session_.get(), &crypto_config_); |
| QuicServerSessionBasePeer::SetCryptoStream(session_.get(), |
| tls_server_stream); |
| } |
| if (!VersionUsesHttp3(transport_version())) { |
| session_->RegisterStreamPriority( |
| QuicUtils::GetHeadersStreamId(transport_version()), |
| /*is_static=*/true, QuicStreamPriority()); |
| } |
| |
| // Set some initial bandwidth values. |
| QuicSentPacketManager* sent_packet_manager = |
| QuicConnectionPeer::GetSentPacketManager(session_->connection()); |
| QuicSustainedBandwidthRecorder& bandwidth_recorder = |
| QuicSentPacketManagerPeer::GetBandwidthRecorder(sent_packet_manager); |
| // Seed an rtt measurement equal to the initial default rtt. |
| RttStats* rtt_stats = |
| const_cast<RttStats*>(sent_packet_manager->GetRttStats()); |
| rtt_stats->UpdateRtt(rtt_stats->initial_rtt(), QuicTime::Delta::Zero(), |
| QuicTime::Zero()); |
| QuicSustainedBandwidthRecorderPeer::SetBandwidthEstimate( |
| &bandwidth_recorder, bandwidth_estimate_kbytes_per_second); |
| QuicSustainedBandwidthRecorderPeer::SetMaxBandwidthEstimate( |
| &bandwidth_recorder, max_bandwidth_estimate_kbytes_per_second, |
| max_bandwidth_estimate_timestamp); |
| // Queue up some pending data. |
| if (!VersionUsesHttp3(transport_version())) { |
| session_->MarkConnectionLevelWriteBlocked( |
| QuicUtils::GetHeadersStreamId(transport_version())); |
| } else { |
| session_->MarkConnectionLevelWriteBlocked( |
| QuicUtils::GetFirstUnidirectionalStreamId(transport_version(), |
| Perspective::IS_SERVER)); |
| } |
| EXPECT_TRUE(session_->HasDataToWrite()); |
| |
| // There will be no update sent yet - not enough time has passed. |
| QuicTime now = QuicTime::Zero(); |
| session_->OnCongestionWindowChange(now); |
| |
| // Bandwidth estimate has now changed sufficiently but not enough time has |
| // passed to send a Server Config Update. |
| bandwidth_estimate_kbytes_per_second = |
| bandwidth_estimate_kbytes_per_second * 1.6; |
| session_->OnCongestionWindowChange(now); |
| |
| // Bandwidth estimate has now changed sufficiently and enough time has passed, |
| // but not enough packets have been sent. |
| int64_t srtt_ms = |
| sent_packet_manager->GetRttStats()->smoothed_rtt().ToMilliseconds(); |
| now = now + QuicTime::Delta::FromMilliseconds( |
| kMinIntervalBetweenServerConfigUpdatesRTTs * srtt_ms); |
| session_->OnCongestionWindowChange(now); |
| |
| // The connection no longer has pending data to be written. |
| session_->OnCanWrite(); |
| EXPECT_FALSE(session_->HasDataToWrite()); |
| session_->OnCongestionWindowChange(now); |
| |
| // Bandwidth estimate has now changed sufficiently, enough time has passed, |
| // and enough packets have been sent. |
| SerializedPacket packet( |
| QuicPacketNumber(1) + kMinPacketsBetweenServerConfigUpdates, |
| PACKET_4BYTE_PACKET_NUMBER, nullptr, 1000, false, false); |
| sent_packet_manager->OnPacketSent(&packet, now, NOT_RETRANSMISSION, |
| HAS_RETRANSMITTABLE_DATA, true); |
| |
| if (GetQuicRestartFlag( |
| quic_enable_sending_bandwidth_estimate_when_network_idle_v2)) { |
| EXPECT_CALL(*connection_, OnSendConnectionState(_)).Times(0); |
| } else { |
| // Verify that the proto has exactly the values we expect. |
| CachedNetworkParameters expected_network_params; |
| expected_network_params.set_bandwidth_estimate_bytes_per_second( |
| bandwidth_recorder.BandwidthEstimate().ToBytesPerSecond()); |
| expected_network_params.set_max_bandwidth_estimate_bytes_per_second( |
| bandwidth_recorder.MaxBandwidthEstimate().ToBytesPerSecond()); |
| expected_network_params.set_max_bandwidth_timestamp_seconds( |
| bandwidth_recorder.MaxBandwidthTimestamp()); |
| expected_network_params.set_min_rtt_ms(session_->connection() |
| ->sent_packet_manager() |
| .GetRttStats() |
| ->min_rtt() |
| .ToMilliseconds()); |
| expected_network_params.set_previous_connection_state( |
| CachedNetworkParameters::CONGESTION_AVOIDANCE); |
| expected_network_params.set_timestamp( |
| session_->connection()->clock()->WallNow().ToUNIXSeconds()); |
| expected_network_params.set_serving_region(serving_region); |
| |
| if (quic_crypto_stream) { |
| EXPECT_CALL(*quic_crypto_stream, |
| SendServerConfigUpdate(EqualsProto(expected_network_params))) |
| .Times(1); |
| } else { |
| EXPECT_CALL(*tls_server_stream, |
| GetAddressToken(EqualsProto(expected_network_params))) |
| .WillOnce(testing::Return("Test address token")); |
| } |
| EXPECT_CALL(*connection_, OnSendConnectionState(_)).Times(1); |
| } |
| session_->OnCongestionWindowChange(now); |
| } |
| |
| TEST_P(QuicServerSessionBaseTest, BandwidthResumptionExperiment) { |
| if (version().UsesTls()) { |
| if (!version().HasIetfQuicFrames()) { |
| // Skip the Txxx versions. |
| return; |
| } |
| // Avoid a QUIC_BUG in QuicSession::OnConfigNegotiated. |
| connection_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE); |
| } |
| |
| // Test that if a client provides a CachedNetworkParameters with the same |
| // serving region as the current server, and which was made within an hour of |
| // now, that this data is passed down to the send algorithm. |
| |
| // Client has sent kBWRE connection option to trigger bandwidth resumption. |
| QuicTagVector copt; |
| copt.push_back(kBWRE); |
| QuicConfigPeer::SetReceivedConnectionOptions(session_->config(), copt); |
| |
| const std::string kTestServingRegion = "a serving region"; |
| session_->set_serving_region(kTestServingRegion); |
| |
| // Set the time to be one hour + one second from the 0 baseline. |
| connection_->AdvanceTime( |
| QuicTime::Delta::FromSeconds(kNumSecondsPerHour + 1)); |
| |
| QuicCryptoServerStreamBase* crypto_stream = |
| static_cast<QuicCryptoServerStreamBase*>( |
| QuicSessionPeer::GetMutableCryptoStream(session_.get())); |
| |
| // No effect if no CachedNetworkParameters provided. |
| EXPECT_CALL(*connection_, ResumeConnectionState(_, _)).Times(0); |
| session_->OnConfigNegotiated(); |
| |
| // No effect if CachedNetworkParameters provided, but different serving |
| // regions. |
| CachedNetworkParameters cached_network_params; |
| cached_network_params.set_bandwidth_estimate_bytes_per_second(1); |
| cached_network_params.set_serving_region("different serving region"); |
| crypto_stream->SetPreviousCachedNetworkParams(cached_network_params); |
| EXPECT_CALL(*connection_, ResumeConnectionState(_, _)).Times(0); |
| session_->OnConfigNegotiated(); |
| |
| // Same serving region, but timestamp is too old, should have no effect. |
| cached_network_params.set_serving_region(kTestServingRegion); |
| cached_network_params.set_timestamp(0); |
| crypto_stream->SetPreviousCachedNetworkParams(cached_network_params); |
| EXPECT_CALL(*connection_, ResumeConnectionState(_, _)).Times(0); |
| session_->OnConfigNegotiated(); |
| |
| // Same serving region, and timestamp is recent: estimate is stored. |
| cached_network_params.set_timestamp( |
| connection_->clock()->WallNow().ToUNIXSeconds()); |
| crypto_stream->SetPreviousCachedNetworkParams(cached_network_params); |
| EXPECT_CALL(*connection_, ResumeConnectionState(_, _)).Times(1); |
| session_->OnConfigNegotiated(); |
| } |
| |
| TEST_P(QuicServerSessionBaseTest, BandwidthMaxEnablesResumption) { |
| EXPECT_FALSE( |
| QuicServerSessionBasePeer::IsBandwidthResumptionEnabled(session_.get())); |
| |
| // Client has sent kBWMX connection option to trigger bandwidth resumption. |
| QuicTagVector copt; |
| copt.push_back(kBWMX); |
| QuicConfigPeer::SetReceivedConnectionOptions(session_->config(), copt); |
| connection_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE); |
| session_->OnConfigNegotiated(); |
| EXPECT_TRUE( |
| QuicServerSessionBasePeer::IsBandwidthResumptionEnabled(session_.get())); |
| } |
| |
| TEST_P(QuicServerSessionBaseTest, NoBandwidthResumptionByDefault) { |
| EXPECT_FALSE( |
| QuicServerSessionBasePeer::IsBandwidthResumptionEnabled(session_.get())); |
| connection_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE); |
| session_->OnConfigNegotiated(); |
| EXPECT_FALSE( |
| QuicServerSessionBasePeer::IsBandwidthResumptionEnabled(session_.get())); |
| } |
| |
| // Tests which check the lifetime management of data members of |
| // QuicCryptoServerStream objects when async GetProof is in use. |
| class StreamMemberLifetimeTest : public QuicServerSessionBaseTest { |
| public: |
| StreamMemberLifetimeTest() |
| : QuicServerSessionBaseTest( |
| std::unique_ptr<FakeProofSource>(new FakeProofSource())), |
| crypto_config_peer_(&crypto_config_) { |
| GetFakeProofSource()->Activate(); |
| } |
| |
| FakeProofSource* GetFakeProofSource() const { |
| return static_cast<FakeProofSource*>(crypto_config_peer_.GetProofSource()); |
| } |
| |
| private: |
| QuicCryptoServerConfigPeer crypto_config_peer_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(StreamMemberLifetimeTests, StreamMemberLifetimeTest, |
| ::testing::ValuesIn(AllSupportedVersions()), |
| ::testing::PrintToStringParamName()); |
| |
| // Trigger an operation which causes an async invocation of |
| // ProofSource::GetProof. Delay the completion of the operation until after the |
| // stream has been destroyed, and verify that there are no memory bugs. |
| TEST_P(StreamMemberLifetimeTest, Basic) { |
| if (version().handshake_protocol == PROTOCOL_TLS1_3) { |
| // This test depends on the QUIC crypto protocol, so it is disabled for the |
| // TLS handshake. |
| // TODO(nharper): Fix this test so it doesn't rely on QUIC crypto. |
| return; |
| } |
| |
| const QuicClock* clock = helper_.GetClock(); |
| CryptoHandshakeMessage chlo = crypto_test_utils::GenerateDefaultInchoateCHLO( |
| clock, transport_version(), &crypto_config_); |
| chlo.SetVector(kCOPT, QuicTagVector{kREJ}); |
| std::vector<ParsedQuicVersion> packet_version_list = {version()}; |
| std::unique_ptr<QuicEncryptedPacket> packet(ConstructEncryptedPacket( |
| TestConnectionId(1), EmptyQuicConnectionId(), true, false, 1, |
| std::string(chlo.GetSerialized().AsStringPiece()), CONNECTION_ID_PRESENT, |
| CONNECTION_ID_ABSENT, PACKET_4BYTE_PACKET_NUMBER, &packet_version_list)); |
| |
| EXPECT_CALL(stream_helper_, CanAcceptClientHello(_, _, _, _, _)) |
| .WillOnce(testing::Return(true)); |
| |
| // Set the current packet |
| QuicConnectionPeer::SetCurrentPacket(session_->connection(), |
| packet->AsStringPiece()); |
| |
| // Yes, this is horrible. But it's the easiest way to trigger the behavior we |
| // need to exercise. |
| QuicCryptoServerStreamBase* crypto_stream = |
| const_cast<QuicCryptoServerStreamBase*>(session_->crypto_stream()); |
| |
| // Feed the CHLO into the crypto stream, which will trigger a call to |
| // ProofSource::GetProof |
| crypto_test_utils::SendHandshakeMessageToStream(crypto_stream, chlo, |
| Perspective::IS_CLIENT); |
| ASSERT_EQ(GetFakeProofSource()->NumPendingCallbacks(), 1); |
| |
| // Destroy the stream |
| session_.reset(); |
| |
| // Allow the async ProofSource::GetProof call to complete. Verify (under |
| // memory access checkers) that this does not result in accesses to any |
| // freed memory from the session or its subobjects. |
| GetFakeProofSource()->InvokePendingCallback(0); |
| } |
| |
| } // namespace |
| } // namespace test |
| } // namespace quic |