| // 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/tools/quic/quic_server_session.h" |
| |
| #include "net/quic/crypto/cached_network_parameters.h" |
| #include "net/quic/crypto/quic_crypto_server_config.h" |
| #include "net/quic/crypto/quic_random.h" |
| #include "net/quic/quic_connection.h" |
| #include "net/quic/quic_crypto_server_stream.h" |
| #include "net/quic/quic_flags.h" |
| #include "net/quic/quic_utils.h" |
| #include "net/quic/test_tools/quic_config_peer.h" |
| #include "net/quic/test_tools/quic_connection_peer.h" |
| #include "net/quic/test_tools/quic_data_stream_peer.h" |
| #include "net/quic/test_tools/quic_sent_packet_manager_peer.h" |
| #include "net/quic/test_tools/quic_session_peer.h" |
| #include "net/quic/test_tools/quic_sustained_bandwidth_recorder_peer.h" |
| #include "net/quic/test_tools/quic_test_utils.h" |
| #include "net/tools/quic/quic_spdy_server_stream.h" |
| #include "net/tools/quic/test_tools/quic_test_utils.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using __gnu_cxx::vector; |
| using net::test::MockConnection; |
| using net::test::QuicConfigPeer; |
| using net::test::QuicConnectionPeer; |
| using net::test::QuicDataStreamPeer; |
| using net::test::QuicSentPacketManagerPeer; |
| using net::test::QuicSessionPeer; |
| using net::test::QuicSustainedBandwidthRecorderPeer; |
| using net::test::SupportedVersions; |
| using net::test::ValueRestore; |
| using net::test::kClientDataStreamId1; |
| using net::test::kClientDataStreamId2; |
| using net::test::kClientDataStreamId3; |
| using net::test::kClientDataStreamId4; |
| using testing::StrictMock; |
| using testing::_; |
| |
| namespace net { |
| namespace tools { |
| namespace test { |
| |
| class QuicServerSessionPeer { |
| public: |
| static QuicDataStream* GetIncomingDataStream( |
| QuicServerSession* s, QuicStreamId id) { |
| return s->GetIncomingDataStream(id); |
| } |
| static QuicDataStream* GetDataStream(QuicServerSession* s, QuicStreamId id) { |
| return s->GetDataStream(id); |
| } |
| static void SetCryptoStream(QuicServerSession* s, |
| QuicCryptoServerStream* crypto_stream) { |
| s->crypto_stream_.reset(crypto_stream); |
| } |
| }; |
| |
| namespace { |
| |
| const size_t kMaxStreamsForTest = 10; |
| |
| class QuicServerSessionTest : public ::testing::TestWithParam<QuicVersion> { |
| protected: |
| QuicServerSessionTest() |
| : crypto_config_(QuicCryptoServerConfig::TESTING, |
| QuicRandom::GetInstance()) { |
| config_.SetMaxStreamsPerConnection(kMaxStreamsForTest, |
| kMaxStreamsForTest); |
| config_.SetInitialFlowControlWindowToSend( |
| kInitialSessionFlowControlWindowForTest); |
| config_.SetInitialStreamFlowControlWindowToSend( |
| kInitialStreamFlowControlWindowForTest); |
| config_.SetInitialSessionFlowControlWindowToSend( |
| kInitialSessionFlowControlWindowForTest); |
| |
| connection_ = |
| new StrictMock<MockConnection>(true, SupportedVersions(GetParam())); |
| session_.reset(new QuicServerSession(config_, connection_, &owner_)); |
| MockClock clock; |
| handshake_message_.reset(crypto_config_.AddDefaultConfig( |
| QuicRandom::GetInstance(), &clock, |
| QuicCryptoServerConfig::ConfigOptions())); |
| session_->InitializeSession(crypto_config_); |
| visitor_ = QuicConnectionPeer::GetVisitor(connection_); |
| } |
| |
| QuicVersion version() const { return connection_->version(); } |
| |
| StrictMock<MockQuicServerSessionVisitor> owner_; |
| StrictMock<MockConnection>* connection_; |
| QuicConfig config_; |
| QuicCryptoServerConfig crypto_config_; |
| scoped_ptr<QuicServerSession> session_; |
| scoped_ptr<CryptoHandshakeMessage> handshake_message_; |
| QuicConnectionVisitorInterface* visitor_; |
| }; |
| |
| // 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_CASE_P(Tests, QuicServerSessionTest, |
| ::testing::ValuesIn(QuicSupportedVersions())); |
| |
| TEST_P(QuicServerSessionTest, CloseStreamDueToReset) { |
| // Open a stream, then reset it. |
| // Send two bytes of payload to open it. |
| QuicStreamFrame data1(kClientDataStreamId1, false, 0, MakeIOVector("HT")); |
| vector<QuicStreamFrame> frames; |
| frames.push_back(data1); |
| session_->OnStreamFrames(frames); |
| EXPECT_EQ(1u, session_->GetNumOpenStreams()); |
| |
| // Send a reset (and expect the peer to send a RST in response). |
| QuicRstStreamFrame rst1(kClientDataStreamId1, QUIC_STREAM_NO_ERROR, 0); |
| EXPECT_CALL(*connection_, |
| SendRstStream(kClientDataStreamId1, QUIC_RST_ACKNOWLEDGEMENT, 0)); |
| visitor_->OnRstStream(rst1); |
| EXPECT_EQ(0u, session_->GetNumOpenStreams()); |
| |
| // Send the same two bytes of payload in a new packet. |
| visitor_->OnStreamFrames(frames); |
| |
| // The stream should not be re-opened. |
| EXPECT_EQ(0u, session_->GetNumOpenStreams()); |
| EXPECT_TRUE(connection_->connected()); |
| } |
| |
| TEST_P(QuicServerSessionTest, NeverOpenStreamDueToReset) { |
| // Send a reset (and expect the peer to send a RST in response). |
| QuicRstStreamFrame rst1(kClientDataStreamId1, QUIC_STREAM_NO_ERROR, 0); |
| EXPECT_CALL(*connection_, |
| SendRstStream(kClientDataStreamId1, QUIC_RST_ACKNOWLEDGEMENT, 0)); |
| visitor_->OnRstStream(rst1); |
| EXPECT_EQ(0u, session_->GetNumOpenStreams()); |
| |
| // Send two bytes of payload. |
| QuicStreamFrame data1(kClientDataStreamId1, false, 0, MakeIOVector("HT")); |
| vector<QuicStreamFrame> frames; |
| frames.push_back(data1); |
| visitor_->OnStreamFrames(frames); |
| |
| // The stream should never be opened, now that the reset is received. |
| EXPECT_EQ(0u, session_->GetNumOpenStreams()); |
| EXPECT_TRUE(connection_->connected()); |
| } |
| |
| TEST_P(QuicServerSessionTest, AcceptClosedStream) { |
| vector<QuicStreamFrame> frames; |
| // Send (empty) compressed headers followed by two bytes of data. |
| frames.push_back(QuicStreamFrame(kClientDataStreamId1, false, 0, |
| MakeIOVector("\1\0\0\0\0\0\0\0HT"))); |
| frames.push_back(QuicStreamFrame(kClientDataStreamId2, false, 0, |
| MakeIOVector("\2\0\0\0\0\0\0\0HT"))); |
| visitor_->OnStreamFrames(frames); |
| EXPECT_EQ(2u, session_->GetNumOpenStreams()); |
| |
| // Send a reset (and expect the peer to send a RST in response). |
| QuicRstStreamFrame rst(kClientDataStreamId1, QUIC_STREAM_NO_ERROR, 0); |
| EXPECT_CALL(*connection_, |
| SendRstStream(kClientDataStreamId1, QUIC_RST_ACKNOWLEDGEMENT, 0)); |
| visitor_->OnRstStream(rst); |
| |
| // If we were tracking, we'd probably want to reject this because it's data |
| // past the reset point of stream 3. As it's a closed stream we just drop the |
| // data on the floor, but accept the packet because it has data for stream 5. |
| frames.clear(); |
| frames.push_back( |
| QuicStreamFrame(kClientDataStreamId1, false, 2, MakeIOVector("TP"))); |
| frames.push_back( |
| QuicStreamFrame(kClientDataStreamId2, false, 2, MakeIOVector("TP"))); |
| visitor_->OnStreamFrames(frames); |
| // The stream should never be opened, now that the reset is received. |
| EXPECT_EQ(1u, session_->GetNumOpenStreams()); |
| EXPECT_TRUE(connection_->connected()); |
| } |
| |
| TEST_P(QuicServerSessionTest, MaxOpenStreams) { |
| // Test that the server closes the connection if a client attempts to open too |
| // many data streams. The server accepts slightly more than the negotiated |
| // stream limit to deal with rare cases where a client FIN/RST is lost. |
| |
| // The slightly increased stream limit is set during config negotiation. It |
| // should be either an increase of 10 over negotiated limit, or a fixed |
| // percentage scaling, whichever is larger. Test both before continuing. |
| EXPECT_EQ(kMaxStreamsForTest, session_->get_max_open_streams()); |
| session_->OnConfigNegotiated(); |
| EXPECT_LT(kMaxStreamsMultiplier * kMaxStreamsForTest, |
| kMaxStreamsForTest + kMaxStreamsMinimumIncrement); |
| EXPECT_EQ(kMaxStreamsForTest + kMaxStreamsMinimumIncrement, |
| session_->get_max_open_streams()); |
| EXPECT_EQ(0u, session_->GetNumOpenStreams()); |
| QuicStreamId stream_id = kClientDataStreamId1; |
| // Open the max configured number of streams, should be no problem. |
| for (size_t i = 0; i < kMaxStreamsForTest; ++i) { |
| EXPECT_TRUE(QuicServerSessionPeer::GetIncomingDataStream(session_.get(), |
| stream_id)); |
| stream_id += 2; |
| } |
| |
| // Open more streams: server should accept slightly more than the limit. |
| for (size_t i = 0; i < kMaxStreamsMinimumIncrement; ++i) { |
| EXPECT_TRUE(QuicServerSessionPeer::GetIncomingDataStream(session_.get(), |
| stream_id)); |
| stream_id += 2; |
| } |
| |
| // Now violate the server's internal stream limit. |
| EXPECT_CALL(*connection_, SendConnectionClose(QUIC_TOO_MANY_OPEN_STREAMS)); |
| stream_id += 2; |
| EXPECT_FALSE( |
| QuicServerSessionPeer::GetIncomingDataStream(session_.get(), stream_id)); |
| } |
| |
| TEST_P(QuicServerSessionTest, MaxOpenStreamsImplicit) { |
| // Test that the server closes the connection if a client attempts to open too |
| // many data streams implicitly. The server accepts slightly more than the |
| // negotiated stream limit to deal with rare cases where a client FIN/RST is |
| // lost. |
| |
| // The slightly increased stream limit is set during config negotiation. |
| EXPECT_EQ(kMaxStreamsForTest, session_->get_max_open_streams()); |
| session_->OnConfigNegotiated(); |
| EXPECT_LT(kMaxStreamsMultiplier * kMaxStreamsForTest, |
| kMaxStreamsForTest + kMaxStreamsMinimumIncrement); |
| EXPECT_EQ(kMaxStreamsForTest + kMaxStreamsMinimumIncrement, |
| session_->get_max_open_streams()); |
| |
| EXPECT_EQ(0u, session_->GetNumOpenStreams()); |
| EXPECT_TRUE(QuicServerSessionPeer::GetIncomingDataStream( |
| session_.get(), kClientDataStreamId1)); |
| // Implicitly open streams up to the server's limit. |
| const int kActualMaxStreams = |
| kMaxStreamsForTest + kMaxStreamsMinimumIncrement; |
| const int kMaxValidStreamId = |
| kClientDataStreamId1 + (kActualMaxStreams - 1) * 2; |
| EXPECT_TRUE(QuicServerSessionPeer::GetIncomingDataStream( |
| session_.get(), kMaxValidStreamId)); |
| |
| // Opening a further stream will result in connection close. |
| EXPECT_CALL(*connection_, SendConnectionClose(QUIC_TOO_MANY_OPEN_STREAMS)); |
| EXPECT_FALSE(QuicServerSessionPeer::GetIncomingDataStream( |
| session_.get(), kMaxValidStreamId + 2)); |
| } |
| |
| TEST_P(QuicServerSessionTest, GetEvenIncomingError) { |
| // Incoming streams on the server session must be odd. |
| EXPECT_CALL(*connection_, SendConnectionClose(QUIC_INVALID_STREAM_ID)); |
| EXPECT_EQ(nullptr, |
| QuicServerSessionPeer::GetIncomingDataStream(session_.get(), 4)); |
| } |
| |
| TEST_P(QuicServerSessionTest, SetFecProtectionFromConfig) { |
| ValueRestore<bool> old_flag(&FLAGS_enable_quic_fec, true); |
| |
| // Set received config to have FEC connection option. |
| QuicTagVector copt; |
| copt.push_back(kFHDR); |
| QuicConfigPeer::SetReceivedConnectionOptions(session_->config(), copt); |
| session_->OnConfigNegotiated(); |
| |
| // Verify that headers stream is always protected and data streams are |
| // optionally protected. |
| EXPECT_EQ(FEC_PROTECT_ALWAYS, |
| QuicSessionPeer::GetHeadersStream(session_.get())->fec_policy()); |
| QuicDataStream* stream = QuicServerSessionPeer::GetIncomingDataStream( |
| session_.get(), kClientDataStreamId1); |
| ASSERT_TRUE(stream); |
| EXPECT_EQ(FEC_PROTECT_OPTIONAL, stream->fec_policy()); |
| } |
| |
| class MockQuicCryptoServerStream : public QuicCryptoServerStream { |
| public: |
| explicit MockQuicCryptoServerStream( |
| const QuicCryptoServerConfig& crypto_config, QuicSession* session) |
| : QuicCryptoServerStream(crypto_config, session) {} |
| ~MockQuicCryptoServerStream() override {} |
| |
| MOCK_METHOD1(SendServerConfigUpdate, |
| void(const CachedNetworkParameters* cached_network_parameters)); |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(MockQuicCryptoServerStream); |
| }; |
| |
| TEST_P(QuicServerSessionTest, BandwidthEstimates) { |
| if (version() <= QUIC_VERSION_21) { |
| return; |
| } |
| |
| // Test that bandwidth estimate updates are sent to the client, only after the |
| // bandwidth estimate has changes sufficiently, and enough time has passed. |
| |
| int32 bandwidth_estimate_kbytes_per_second = 123; |
| int32 max_bandwidth_estimate_kbytes_per_second = 134; |
| int32 max_bandwidth_estimate_timestamp = 1122334455; |
| const string serving_region = "not a real region"; |
| session_->set_serving_region(serving_region); |
| |
| MockQuicCryptoServerStream* crypto_stream = |
| new MockQuicCryptoServerStream(crypto_config_, session_.get()); |
| QuicServerSessionPeer::SetCryptoStream(session_.get(), crypto_stream); |
| |
| // 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 = |
| QuicSentPacketManagerPeer::GetRttStats(sent_packet_manager); |
| rtt_stats->UpdateRtt(QuicTime::Delta::FromMicroseconds( |
| rtt_stats->initial_rtt_us()), 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); |
| |
| // 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 srtt_ms = |
| sent_packet_manager->GetRttStats()->smoothed_rtt().ToMilliseconds(); |
| now = now.Add(QuicTime::Delta::FromMilliseconds( |
| kMinIntervalBetweenServerConfigUpdatesRTTs * srtt_ms)); |
| session_->OnCongestionWindowChange(now); |
| |
| // Bandwidth estimate has now changed sufficiently, enough time has passed, |
| // and enough packets have been sent. |
| QuicConnectionPeer::SetSequenceNumberOfLastSentPacket( |
| session_->connection(), kMinPacketsBetweenServerConfigUpdates); |
| |
| // 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); |
| |
| EXPECT_CALL(*crypto_stream, |
| SendServerConfigUpdate(EqualsProto(expected_network_params))) |
| .Times(1); |
| session_->OnCongestionWindowChange(now); |
| } |
| |
| TEST_P(QuicServerSessionTest, BandwidthResumptionExperiment) { |
| ValueRestore<bool> old_flag( |
| &FLAGS_quic_enable_bandwidth_resumption_experiment, true); |
| |
| // Test that if a client provides a CachedNetworkParameters with the same |
| // serving region as the current server, 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 string kTestServingRegion = "a serving region"; |
| session_->set_serving_region(kTestServingRegion); |
| |
| QuicCryptoServerStream* crypto_stream = |
| static_cast<QuicCryptoServerStream*>( |
| QuicSessionPeer::GetCryptoStream(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->set_previous_cached_network_params(cached_network_params); |
| EXPECT_CALL(*connection_, ResumeConnectionState(_)).Times(0); |
| session_->OnConfigNegotiated(); |
| |
| // Same serving region results in CachedNetworkParameters being stored. |
| cached_network_params.set_serving_region(kTestServingRegion); |
| crypto_stream->set_previous_cached_network_params(cached_network_params); |
| EXPECT_CALL(*connection_, ResumeConnectionState(_)).Times(1); |
| session_->OnConfigNegotiated(); |
| } |
| |
| } // namespace |
| } // namespace test |
| } // namespace tools |
| } // namespace net |