| // Copyright (c) 2012 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 <cstddef> |
| #include <cstdint> |
| #include <list> |
| #include <memory> |
| #include <ostream> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/time/clock.h" |
| #include "absl/time/time.h" |
| #include "absl/types/span.h" |
| #include "quiche/quic/core/crypto/null_encrypter.h" |
| #include "quiche/quic/core/crypto/quic_client_session_cache.h" |
| #include "quiche/quic/core/frames/quic_blocked_frame.h" |
| #include "quiche/quic/core/http/http_constants.h" |
| #include "quiche/quic/core/http/quic_spdy_client_stream.h" |
| #include "quiche/quic/core/http/web_transport_http3.h" |
| #include "quiche/quic/core/io/quic_default_event_loop.h" |
| #include "quiche/quic/core/io/quic_event_loop.h" |
| #include "quiche/quic/core/quic_connection.h" |
| #include "quiche/quic/core/quic_constants.h" |
| #include "quiche/quic/core/quic_data_writer.h" |
| #include "quiche/quic/core/quic_default_clock.h" |
| #include "quiche/quic/core/quic_error_codes.h" |
| #include "quiche/quic/core/quic_framer.h" |
| #include "quiche/quic/core/quic_packet_creator.h" |
| #include "quiche/quic/core/quic_packet_writer_wrapper.h" |
| #include "quiche/quic/core/quic_packets.h" |
| #include "quiche/quic/core/quic_session.h" |
| #include "quiche/quic/core/quic_types.h" |
| #include "quiche/quic/core/quic_utils.h" |
| #include "quiche/quic/platform/api/quic_expect_bug.h" |
| #include "quiche/quic/platform/api/quic_flags.h" |
| #include "quiche/quic/platform/api/quic_logging.h" |
| #include "quiche/quic/platform/api/quic_socket_address.h" |
| #include "quiche/quic/platform/api/quic_test.h" |
| #include "quiche/quic/platform/api/quic_test_loopback.h" |
| #include "quiche/quic/test_tools/bad_packet_writer.h" |
| #include "quiche/quic/test_tools/crypto_test_utils.h" |
| #include "quiche/quic/test_tools/packet_dropping_test_writer.h" |
| #include "quiche/quic/test_tools/packet_reordering_writer.h" |
| #include "quiche/quic/test_tools/qpack/qpack_encoder_peer.h" |
| #include "quiche/quic/test_tools/qpack/qpack_test_utils.h" |
| #include "quiche/quic/test_tools/quic_client_session_cache_peer.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_dispatcher_peer.h" |
| #include "quiche/quic/test_tools/quic_flow_controller_peer.h" |
| #include "quiche/quic/test_tools/quic_sent_packet_manager_peer.h" |
| #include "quiche/quic/test_tools/quic_server_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_spdy_stream_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_stream_sequencer_peer.h" |
| #include "quiche/quic/test_tools/quic_test_backend.h" |
| #include "quiche/quic/test_tools/quic_test_client.h" |
| #include "quiche/quic/test_tools/quic_test_server.h" |
| #include "quiche/quic/test_tools/quic_test_utils.h" |
| #include "quiche/quic/test_tools/server_thread.h" |
| #include "quiche/quic/test_tools/web_transport_test_tools.h" |
| #include "quiche/quic/tools/quic_backend_response.h" |
| #include "quiche/quic/tools/quic_memory_cache_backend.h" |
| #include "quiche/quic/tools/quic_server.h" |
| #include "quiche/quic/tools/quic_simple_client_stream.h" |
| #include "quiche/quic/tools/quic_simple_server_stream.h" |
| #include "quiche/common/platform/api/quiche_test.h" |
| #include "quiche/common/quiche_stream.h" |
| #include "quiche/common/test_tools/quiche_test_utils.h" |
| #include "quiche/spdy/core/http2_header_block.h" |
| |
| using spdy::Http2HeaderBlock; |
| using spdy::kV3LowestPriority; |
| using spdy::SpdyFramer; |
| using spdy::SpdySerializedFrame; |
| using spdy::SpdySettingsIR; |
| using ::testing::_; |
| using ::testing::Assign; |
| using ::testing::Invoke; |
| using ::testing::NiceMock; |
| using ::testing::UnorderedElementsAreArray; |
| |
| namespace quic { |
| namespace test { |
| namespace { |
| |
| const char kFooResponseBody[] = "Artichoke hearts make me happy."; |
| const char kBarResponseBody[] = "Palm hearts are pretty delicious, also."; |
| const char kTestUserAgentId[] = "quic/core/http/end_to_end_test.cc"; |
| const float kSessionToStreamRatio = 1.5; |
| const int kLongConnectionIdLength = 16; |
| |
| // Run all tests with the cross products of all versions. |
| struct TestParams { |
| TestParams(const ParsedQuicVersion& version, QuicTag congestion_control_tag, |
| QuicEventLoopFactory* event_loop, |
| int override_server_connection_id_length) |
| : version(version), |
| congestion_control_tag(congestion_control_tag), |
| event_loop(event_loop), |
| override_server_connection_id_length( |
| override_server_connection_id_length) {} |
| |
| friend std::ostream& operator<<(std::ostream& os, const TestParams& p) { |
| os << "{ version: " << ParsedQuicVersionToString(p.version); |
| os << " congestion_control_tag: " |
| << QuicTagToString(p.congestion_control_tag) |
| << " event loop: " << p.event_loop->GetName() |
| << " connection ID length: " << p.override_server_connection_id_length |
| << " }"; |
| return os; |
| } |
| |
| ParsedQuicVersion version; |
| QuicTag congestion_control_tag; |
| QuicEventLoopFactory* event_loop; |
| int override_server_connection_id_length; |
| }; |
| |
| // Used by ::testing::PrintToStringParamName(). |
| std::string PrintToString(const TestParams& p) { |
| std::string rv = absl::StrCat( |
| ParsedQuicVersionToString(p.version), "_", |
| QuicTagToString(p.congestion_control_tag), "_", p.event_loop->GetName(), |
| "_", |
| std::to_string((p.override_server_connection_id_length == -1) |
| ? static_cast<int>(kQuicDefaultConnectionIdLength) |
| : p.override_server_connection_id_length)); |
| return EscapeTestParamName(rv); |
| } |
| |
| // Constructs various test permutations. |
| std::vector<TestParams> GetTestParams() { |
| std::vector<TestParams> params; |
| std::vector<int> connection_id_lengths{-1, kLongConnectionIdLength}; |
| for (auto connection_id_length : connection_id_lengths) { |
| for (const QuicTag congestion_control_tag : {kTBBR, kQBIC, kB2ON}) { |
| if (!GetQuicReloadableFlag(quic_allow_client_enabled_bbr_v2) && |
| congestion_control_tag == kB2ON) { |
| continue; |
| } |
| for (const ParsedQuicVersion& version : CurrentSupportedVersions()) { |
| // TODO(b/232269029): Q050 should be able to handle 0-RTT when the |
| // initial connection ID is > 8 bytes, but it cannot. This is an |
| // invasive fix that has no impact as long as gQUIC clients always use |
| // 8B server connection IDs. If this bug is fixed, we can change |
| // 'UsesTls' to 'AllowsVariableLengthConnectionIds()' below to test |
| // qQUIC as well. |
| if (connection_id_length == -1 || version.UsesTls()) { |
| params.push_back(TestParams(version, congestion_control_tag, |
| GetDefaultEventLoop(), |
| connection_id_length)); |
| } |
| } // End of outer version loop. |
| } // End of congestion_control_tag loop. |
| } // End of connection_id_length loop. |
| |
| // Only run every event loop implementation for one fixed configuration. |
| for (QuicEventLoopFactory* event_loop : GetAllSupportedEventLoops()) { |
| if (event_loop == GetDefaultEventLoop()) { |
| continue; |
| } |
| params.push_back( |
| TestParams(ParsedQuicVersion::RFCv1(), kTBBR, event_loop, -1)); |
| } |
| |
| return params; |
| } |
| |
| void WriteHeadersOnStream(QuicSpdyStream* stream) { |
| // Since QuicSpdyStream uses QuicHeaderList::empty() to detect too large |
| // headers, it also fails when receiving empty headers. |
| Http2HeaderBlock headers; |
| headers[":authority"] = "test.example.com:443"; |
| headers[":path"] = "/path"; |
| headers[":method"] = "GET"; |
| headers[":scheme"] = "https"; |
| stream->WriteHeaders(std::move(headers), /* fin = */ false, nullptr); |
| } |
| |
| class ServerDelegate : public PacketDroppingTestWriter::Delegate { |
| public: |
| explicit ServerDelegate(QuicDispatcher* dispatcher) |
| : dispatcher_(dispatcher) {} |
| ~ServerDelegate() override = default; |
| void OnCanWrite() override { dispatcher_->OnCanWrite(); } |
| |
| private: |
| QuicDispatcher* dispatcher_; |
| }; |
| |
| class ClientDelegate : public PacketDroppingTestWriter::Delegate { |
| public: |
| explicit ClientDelegate(QuicDefaultClient* client) : client_(client) {} |
| ~ClientDelegate() override = default; |
| void OnCanWrite() override { |
| client_->default_network_helper()->OnSocketEvent( |
| nullptr, client_->GetLatestFD(), kSocketEventWritable); |
| } |
| |
| private: |
| QuicDefaultClient* client_; |
| }; |
| |
| class EndToEndTest : public QuicTestWithParam<TestParams> { |
| protected: |
| EndToEndTest() |
| : initialized_(false), |
| connect_to_server_on_initialize_(true), |
| server_address_(QuicSocketAddress(TestLoopback(), 0)), |
| server_hostname_("test.example.com"), |
| fd_(kQuicInvalidSocketFd), |
| client_writer_(nullptr), |
| server_writer_(nullptr), |
| version_(GetParam().version), |
| client_supported_versions_({version_}), |
| server_supported_versions_(CurrentSupportedVersions()), |
| chlo_multiplier_(0), |
| stream_factory_(nullptr), |
| override_server_connection_id_length_( |
| GetParam().override_server_connection_id_length), |
| expected_server_connection_id_length_(kQuicDefaultConnectionIdLength) { |
| QUIC_LOG(INFO) << "Using Configuration: " << GetParam(); |
| |
| // Use different flow control windows for client/server. |
| client_config_.SetInitialStreamFlowControlWindowToSend( |
| 2 * kInitialStreamFlowControlWindowForTest); |
| client_config_.SetInitialSessionFlowControlWindowToSend( |
| 2 * kInitialSessionFlowControlWindowForTest); |
| server_config_.SetInitialStreamFlowControlWindowToSend( |
| 3 * kInitialStreamFlowControlWindowForTest); |
| server_config_.SetInitialSessionFlowControlWindowToSend( |
| 3 * kInitialSessionFlowControlWindowForTest); |
| |
| // The default idle timeouts can be too strict when running on a busy |
| // machine. |
| const QuicTime::Delta timeout = QuicTime::Delta::FromSeconds(30); |
| client_config_.set_max_time_before_crypto_handshake(timeout); |
| client_config_.set_max_idle_time_before_crypto_handshake(timeout); |
| server_config_.set_max_time_before_crypto_handshake(timeout); |
| server_config_.set_max_idle_time_before_crypto_handshake(timeout); |
| |
| AddToCache("/foo", 200, kFooResponseBody); |
| AddToCache("/bar", 200, kBarResponseBody); |
| // Enable fixes for bugs found in tests and prod. |
| } |
| |
| virtual void CreateClientWithWriter() { |
| client_.reset(CreateQuicClient(client_writer_)); |
| } |
| |
| QuicTestClient* CreateQuicClient(QuicPacketWriterWrapper* writer) { |
| QuicTestClient* client = new QuicTestClient( |
| server_address_, server_hostname_, client_config_, |
| client_supported_versions_, |
| crypto_test_utils::ProofVerifierForTesting(), |
| std::make_unique<QuicClientSessionCache>(), |
| GetParam().event_loop->Create(QuicDefaultClock::Get())); |
| client->SetUserAgentID(kTestUserAgentId); |
| client->UseWriter(writer); |
| if (!pre_shared_key_client_.empty()) { |
| client->client()->SetPreSharedKey(pre_shared_key_client_); |
| } |
| if (override_server_connection_id_length_ >= 0) { |
| client->UseConnectionIdLength(override_server_connection_id_length_); |
| } |
| if (override_client_connection_id_length_ >= 0) { |
| client->UseClientConnectionIdLength( |
| override_client_connection_id_length_); |
| } |
| client->client()->set_connection_debug_visitor(connection_debug_visitor_); |
| client->client()->set_enable_web_transport(enable_web_transport_); |
| client->Connect(); |
| return client; |
| } |
| |
| void set_smaller_flow_control_receive_window() { |
| const uint32_t kClientIFCW = 64 * 1024; |
| const uint32_t kServerIFCW = 1024 * 1024; |
| set_client_initial_stream_flow_control_receive_window(kClientIFCW); |
| set_client_initial_session_flow_control_receive_window( |
| kSessionToStreamRatio * kClientIFCW); |
| set_server_initial_stream_flow_control_receive_window(kServerIFCW); |
| set_server_initial_session_flow_control_receive_window( |
| kSessionToStreamRatio * kServerIFCW); |
| } |
| |
| void set_client_initial_stream_flow_control_receive_window(uint32_t window) { |
| ASSERT_TRUE(client_ == nullptr); |
| QUIC_DLOG(INFO) << "Setting client initial stream flow control window: " |
| << window; |
| client_config_.SetInitialStreamFlowControlWindowToSend(window); |
| } |
| |
| void set_client_initial_session_flow_control_receive_window(uint32_t window) { |
| ASSERT_TRUE(client_ == nullptr); |
| QUIC_DLOG(INFO) << "Setting client initial session flow control window: " |
| << window; |
| client_config_.SetInitialSessionFlowControlWindowToSend(window); |
| } |
| |
| void set_client_initial_max_stream_data_incoming_bidirectional( |
| uint32_t window) { |
| ASSERT_TRUE(client_ == nullptr); |
| QUIC_DLOG(INFO) |
| << "Setting client initial max stream data incoming bidirectional: " |
| << window; |
| client_config_.SetInitialMaxStreamDataBytesIncomingBidirectionalToSend( |
| window); |
| } |
| |
| void set_server_initial_max_stream_data_outgoing_bidirectional( |
| uint32_t window) { |
| ASSERT_TRUE(client_ == nullptr); |
| QUIC_DLOG(INFO) |
| << "Setting server initial max stream data outgoing bidirectional: " |
| << window; |
| server_config_.SetInitialMaxStreamDataBytesOutgoingBidirectionalToSend( |
| window); |
| } |
| |
| void set_server_initial_stream_flow_control_receive_window(uint32_t window) { |
| ASSERT_TRUE(server_thread_ == nullptr); |
| QUIC_DLOG(INFO) << "Setting server initial stream flow control window: " |
| << window; |
| server_config_.SetInitialStreamFlowControlWindowToSend(window); |
| } |
| |
| void set_server_initial_session_flow_control_receive_window(uint32_t window) { |
| ASSERT_TRUE(server_thread_ == nullptr); |
| QUIC_DLOG(INFO) << "Setting server initial session flow control window: " |
| << window; |
| server_config_.SetInitialSessionFlowControlWindowToSend(window); |
| } |
| |
| const QuicSentPacketManager* GetSentPacketManagerFromFirstServerSession() { |
| QuicConnection* server_connection = GetServerConnection(); |
| if (server_connection == nullptr) { |
| ADD_FAILURE() << "Missing server connection"; |
| return nullptr; |
| } |
| return &server_connection->sent_packet_manager(); |
| } |
| |
| const QuicSentPacketManager* GetSentPacketManagerFromClientSession() { |
| QuicConnection* client_connection = GetClientConnection(); |
| if (client_connection == nullptr) { |
| ADD_FAILURE() << "Missing client connection"; |
| return nullptr; |
| } |
| return &client_connection->sent_packet_manager(); |
| } |
| |
| QuicSpdyClientSession* GetClientSession() { |
| if (!client_) { |
| ADD_FAILURE() << "Missing QuicTestClient"; |
| return nullptr; |
| } |
| if (client_->client() == nullptr) { |
| ADD_FAILURE() << "Missing MockableQuicClient"; |
| return nullptr; |
| } |
| return client_->client()->client_session(); |
| } |
| |
| QuicConnection* GetClientConnection() { |
| QuicSpdyClientSession* client_session = GetClientSession(); |
| if (client_session == nullptr) { |
| ADD_FAILURE() << "Missing client session"; |
| return nullptr; |
| } |
| return client_session->connection(); |
| } |
| |
| QuicConnection* GetServerConnection() { |
| QuicSpdySession* server_session = GetServerSession(); |
| if (server_session == nullptr) { |
| ADD_FAILURE() << "Missing server session"; |
| return nullptr; |
| } |
| return server_session->connection(); |
| } |
| |
| QuicSpdySession* GetServerSession() { |
| if (!server_thread_) { |
| ADD_FAILURE() << "Missing server thread"; |
| return nullptr; |
| } |
| QuicServer* quic_server = server_thread_->server(); |
| if (quic_server == nullptr) { |
| ADD_FAILURE() << "Missing server"; |
| return nullptr; |
| } |
| QuicDispatcher* dispatcher = QuicServerPeer::GetDispatcher(quic_server); |
| if (dispatcher == nullptr) { |
| ADD_FAILURE() << "Missing dispatcher"; |
| return nullptr; |
| } |
| if (dispatcher->NumSessions() == 0) { |
| ADD_FAILURE() << "Empty dispatcher session map"; |
| return nullptr; |
| } |
| EXPECT_EQ(1u, dispatcher->NumSessions()); |
| return static_cast<QuicSpdySession*>( |
| QuicDispatcherPeer::GetFirstSessionIfAny(dispatcher)); |
| } |
| |
| bool Initialize() { |
| if (enable_web_transport_) { |
| memory_cache_backend_.set_enable_webtransport(true); |
| } |
| |
| QuicTagVector copt; |
| server_config_.SetConnectionOptionsToSend(copt); |
| copt = client_extra_copts_; |
| |
| // TODO(nimia): Consider setting the congestion control algorithm for the |
| // client as well according to the test parameter. |
| copt.push_back(GetParam().congestion_control_tag); |
| copt.push_back(k2PTO); |
| if (version_.HasIetfQuicFrames()) { |
| copt.push_back(kILD0); |
| } |
| copt.push_back(kPLE1); |
| if (!GetQuicReloadableFlag( |
| quic_remove_connection_migration_connection_option_v2)) { |
| copt.push_back(kRVCM); |
| } |
| client_config_.SetConnectionOptionsToSend(copt); |
| |
| // Start the server first, because CreateQuicClient() attempts |
| // to connect to the server. |
| StartServer(); |
| |
| if (!connect_to_server_on_initialize_) { |
| initialized_ = true; |
| return true; |
| } |
| |
| CreateClientWithWriter(); |
| if (!client_) { |
| ADD_FAILURE() << "Missing QuicTestClient"; |
| return false; |
| } |
| MockableQuicClient* client = client_->client(); |
| if (client == nullptr) { |
| ADD_FAILURE() << "Missing MockableQuicClient"; |
| return false; |
| } |
| if (client_writer_ != nullptr) { |
| QuicConnection* client_connection = GetClientConnection(); |
| if (client_connection == nullptr) { |
| ADD_FAILURE() << "Missing client connection"; |
| return false; |
| } |
| client_writer_->Initialize( |
| QuicConnectionPeer::GetHelper(client_connection), |
| QuicConnectionPeer::GetAlarmFactory(client_connection), |
| std::make_unique<ClientDelegate>(client)); |
| } |
| initialized_ = true; |
| return client->connected(); |
| } |
| |
| void SetUp() override { |
| // The ownership of these gets transferred to the QuicPacketWriterWrapper |
| // when Initialize() is executed. |
| client_writer_ = new PacketDroppingTestWriter(); |
| server_writer_ = new PacketDroppingTestWriter(); |
| } |
| |
| void TearDown() override { |
| EXPECT_TRUE(initialized_) << "You must call Initialize() in every test " |
| << "case. Otherwise, your test will leak memory."; |
| QuicConnection* client_connection = GetClientConnection(); |
| if (client_connection != nullptr) { |
| client_connection->set_debug_visitor(nullptr); |
| } else { |
| ADD_FAILURE() << "Missing client connection"; |
| } |
| StopServer(/*will_restart=*/false); |
| if (fd_ != kQuicInvalidSocketFd) { |
| // Every test should follow StopServer(true) with StartServer(), so we |
| // should never get here. |
| QuicUdpSocketApi socket_api; |
| socket_api.Destroy(fd_); |
| fd_ = kQuicInvalidSocketFd; |
| } |
| } |
| |
| void StartServer() { |
| if (fd_ != kQuicInvalidSocketFd) { |
| // We previously called StopServer to reserve the ephemeral port. Close |
| // the socket so that it's available below. |
| QuicUdpSocketApi socket_api; |
| socket_api.Destroy(fd_); |
| fd_ = kQuicInvalidSocketFd; |
| } |
| auto test_server = std::make_unique<QuicTestServer>( |
| crypto_test_utils::ProofSourceForTesting(), server_config_, |
| server_supported_versions_, &memory_cache_backend_, |
| expected_server_connection_id_length_); |
| test_server->SetEventLoopFactory(GetParam().event_loop); |
| const QuicSocketAddress server_listening_address = |
| server_listening_address_.has_value() ? *server_listening_address_ |
| : server_address_; |
| server_thread_ = std::make_unique<ServerThread>(std::move(test_server), |
| server_listening_address); |
| if (chlo_multiplier_ != 0) { |
| server_thread_->server()->SetChloMultiplier(chlo_multiplier_); |
| } |
| if (!pre_shared_key_server_.empty()) { |
| server_thread_->server()->SetPreSharedKey(pre_shared_key_server_); |
| } |
| server_thread_->Initialize(); |
| server_address_ = |
| QuicSocketAddress(server_address_.host(), server_thread_->GetPort()); |
| QuicDispatcher* dispatcher = |
| QuicServerPeer::GetDispatcher(server_thread_->server()); |
| ASSERT_TRUE(dispatcher != nullptr); |
| QuicDispatcherPeer::UseWriter(dispatcher, server_writer_); |
| |
| server_writer_->Initialize(QuicDispatcherPeer::GetHelper(dispatcher), |
| QuicDispatcherPeer::GetAlarmFactory(dispatcher), |
| std::make_unique<ServerDelegate>(dispatcher)); |
| if (stream_factory_ != nullptr) { |
| static_cast<QuicTestServer*>(server_thread_->server()) |
| ->SetSpdyStreamFactory(stream_factory_); |
| } |
| |
| server_thread_->Start(); |
| } |
| |
| void StopServer(bool will_restart = true) { |
| if (server_thread_) { |
| server_thread_->Quit(); |
| server_thread_->Join(); |
| } |
| if (will_restart) { |
| // server_address_ now contains the random listening port. Since many |
| // tests will attempt to re-bind the socket, claim it so that the kernel |
| // doesn't give away the ephemeral port. |
| QuicUdpSocketApi socket_api; |
| fd_ = socket_api.Create( |
| server_address_.host().AddressFamilyToInt(), |
| /*receive_buffer_size =*/kDefaultSocketReceiveBuffer, |
| /*send_buffer_size =*/kDefaultSocketReceiveBuffer); |
| if (fd_ == kQuicInvalidSocketFd) { |
| QUIC_LOG(ERROR) << "CreateSocket() failed: " << strerror(errno); |
| return; |
| } |
| int rc = socket_api.Bind(fd_, server_address_); |
| if (rc < 0) { |
| QUIC_LOG(ERROR) << "Bind failed: " << strerror(errno); |
| return; |
| } |
| } |
| } |
| |
| void AddToCache(absl::string_view path, int response_code, |
| absl::string_view body) { |
| memory_cache_backend_.AddSimpleResponse(server_hostname_, path, |
| response_code, body); |
| } |
| |
| void SetPacketLossPercentage(int32_t loss) { |
| client_writer_->set_fake_packet_loss_percentage(loss); |
| server_writer_->set_fake_packet_loss_percentage(loss); |
| } |
| |
| void SetPacketSendDelay(QuicTime::Delta delay) { |
| client_writer_->set_fake_packet_delay(delay); |
| server_writer_->set_fake_packet_delay(delay); |
| } |
| |
| void SetReorderPercentage(int32_t reorder) { |
| client_writer_->set_fake_reorder_percentage(reorder); |
| server_writer_->set_fake_reorder_percentage(reorder); |
| } |
| |
| // Verifies that the client and server connections were both free of packets |
| // being discarded, based on connection stats. |
| // Calls server_thread_ Pause() and Resume(), which may only be called once |
| // per test. |
| void VerifyCleanConnection(bool had_packet_loss) { |
| QuicConnection* client_connection = GetClientConnection(); |
| if (client_connection == nullptr) { |
| ADD_FAILURE() << "Missing client connection"; |
| return; |
| } |
| QuicConnectionStats client_stats = client_connection->GetStats(); |
| // TODO(ianswett): Determine why this becomes even more flaky with BBR |
| // enabled. b/62141144 |
| if (!had_packet_loss && !GetQuicReloadableFlag(quic_default_to_bbr)) { |
| EXPECT_EQ(0u, client_stats.packets_lost); |
| } |
| EXPECT_EQ(0u, client_stats.packets_discarded); |
| // When client starts with an unsupported version, the version negotiation |
| // packet sent by server for the old connection (respond for the connection |
| // close packet) will be dropped by the client. |
| if (!ServerSendsVersionNegotiation()) { |
| EXPECT_EQ(0u, client_stats.packets_dropped); |
| } |
| if (!version_.UsesTls()) { |
| // Only enforce this for QUIC crypto because accounting of number of |
| // packets received, processed gets complicated with packets coalescing |
| // and key dropping. For example, a received undecryptable coalesced |
| // packet can be processed later and each sub-packet increases |
| // packets_processed. |
| EXPECT_EQ(client_stats.packets_received, client_stats.packets_processed); |
| } |
| |
| if (!server_thread_) { |
| ADD_FAILURE() << "Missing server thread"; |
| return; |
| } |
| server_thread_->Pause(); |
| QuicSpdySession* server_session = GetServerSession(); |
| if (server_session != nullptr) { |
| QuicConnection* server_connection = server_session->connection(); |
| if (server_connection != nullptr) { |
| QuicConnectionStats server_stats = server_connection->GetStats(); |
| if (!had_packet_loss) { |
| EXPECT_EQ(0u, server_stats.packets_lost); |
| } |
| EXPECT_EQ(0u, server_stats.packets_discarded); |
| } else { |
| ADD_FAILURE() << "Missing server connection"; |
| } |
| } else { |
| ADD_FAILURE() << "Missing server session"; |
| } |
| // TODO(ianswett): Restore the check for packets_dropped equals 0. |
| // The expect for packets received is equal to packets processed fails |
| // due to version negotiation packets. |
| server_thread_->Resume(); |
| } |
| |
| // Returns true when client starts with an unsupported version, and client |
| // closes connection when version negotiation is received. |
| bool ServerSendsVersionNegotiation() { |
| return client_supported_versions_[0] != version_; |
| } |
| |
| bool SupportsIetfQuicWithTls(ParsedQuicVersion version) { |
| return version.HasIetfInvariantHeader() && |
| version.handshake_protocol == PROTOCOL_TLS1_3; |
| } |
| |
| static void ExpectFlowControlsSynced(QuicSession* client, |
| QuicSession* server) { |
| EXPECT_EQ( |
| QuicFlowControllerPeer::SendWindowSize(client->flow_controller()), |
| QuicFlowControllerPeer::ReceiveWindowSize(server->flow_controller())); |
| EXPECT_EQ( |
| QuicFlowControllerPeer::ReceiveWindowSize(client->flow_controller()), |
| QuicFlowControllerPeer::SendWindowSize(server->flow_controller())); |
| } |
| |
| static void ExpectFlowControlsSynced(QuicStream* client, QuicStream* server) { |
| EXPECT_EQ(QuicStreamPeer::SendWindowSize(client), |
| QuicStreamPeer::ReceiveWindowSize(server)); |
| EXPECT_EQ(QuicStreamPeer::ReceiveWindowSize(client), |
| QuicStreamPeer::SendWindowSize(server)); |
| } |
| |
| // Must be called before Initialize to have effect. |
| void SetSpdyStreamFactory(QuicTestServer::StreamFactory* factory) { |
| stream_factory_ = factory; |
| } |
| |
| QuicStreamId GetNthClientInitiatedBidirectionalId(int n) { |
| return GetNthClientInitiatedBidirectionalStreamId( |
| version_.transport_version, n); |
| } |
| |
| QuicStreamId GetNthServerInitiatedBidirectionalId(int n) { |
| return GetNthServerInitiatedBidirectionalStreamId( |
| version_.transport_version, n); |
| } |
| |
| bool CheckResponseHeaders(QuicTestClient* client, |
| const std::string& expected_status) { |
| const spdy::Http2HeaderBlock* response_headers = client->response_headers(); |
| auto it = response_headers->find(":status"); |
| if (it == response_headers->end()) { |
| ADD_FAILURE() << "Did not find :status header in response"; |
| return false; |
| } |
| if (it->second != expected_status) { |
| ADD_FAILURE() << "Got bad :status response: \"" << it->second << "\""; |
| return false; |
| } |
| return true; |
| } |
| |
| bool CheckResponseHeaders(QuicTestClient* client) { |
| return CheckResponseHeaders(client, "200"); |
| } |
| |
| bool CheckResponseHeaders(const std::string& expected_status) { |
| return CheckResponseHeaders(client_.get(), expected_status); |
| } |
| |
| bool CheckResponseHeaders() { return CheckResponseHeaders(client_.get()); } |
| |
| bool CheckResponse(QuicTestClient* client, |
| const std::string& received_response, |
| const std::string& expected_response) { |
| EXPECT_THAT(client_->stream_error(), IsQuicStreamNoError()); |
| EXPECT_THAT(client_->connection_error(), IsQuicNoError()); |
| |
| if (received_response.empty() && !expected_response.empty()) { |
| ADD_FAILURE() << "Failed to get any response for request"; |
| return false; |
| } |
| if (received_response != expected_response) { |
| ADD_FAILURE() << "Got wrong response: \"" << received_response << "\""; |
| return false; |
| } |
| return CheckResponseHeaders(client); |
| } |
| |
| bool SendSynchronousRequestAndCheckResponse( |
| QuicTestClient* client, const std::string& request, |
| const std::string& expected_response) { |
| std::string received_response = client->SendSynchronousRequest(request); |
| return CheckResponse(client, received_response, expected_response); |
| } |
| |
| bool SendSynchronousRequestAndCheckResponse( |
| const std::string& request, const std::string& expected_response) { |
| return SendSynchronousRequestAndCheckResponse(client_.get(), request, |
| expected_response); |
| } |
| |
| bool SendSynchronousFooRequestAndCheckResponse(QuicTestClient* client) { |
| return SendSynchronousRequestAndCheckResponse(client, "/foo", |
| kFooResponseBody); |
| } |
| |
| bool SendSynchronousFooRequestAndCheckResponse() { |
| return SendSynchronousFooRequestAndCheckResponse(client_.get()); |
| } |
| |
| bool SendSynchronousBarRequestAndCheckResponse() { |
| std::string received_response = client_->SendSynchronousRequest("/bar"); |
| return CheckResponse(client_.get(), received_response, kBarResponseBody); |
| } |
| |
| bool WaitForFooResponseAndCheckIt(QuicTestClient* client) { |
| client->WaitForResponse(); |
| std::string received_response = client->response_body(); |
| return CheckResponse(client_.get(), received_response, kFooResponseBody); |
| } |
| |
| bool WaitForFooResponseAndCheckIt() { |
| return WaitForFooResponseAndCheckIt(client_.get()); |
| } |
| |
| WebTransportHttp3* CreateWebTransportSession( |
| const std::string& path, bool wait_for_server_response, |
| QuicSpdyStream** connect_stream_out = nullptr) { |
| // Wait until we receive the settings from the server indicating |
| // WebTransport support. |
| client_->WaitUntil( |
| 2000, [this]() { return GetClientSession()->SupportsWebTransport(); }); |
| if (!GetClientSession()->SupportsWebTransport()) { |
| return nullptr; |
| } |
| |
| spdy::Http2HeaderBlock headers; |
| headers[":scheme"] = "https"; |
| headers[":authority"] = "localhost"; |
| headers[":path"] = path; |
| headers[":method"] = "CONNECT"; |
| headers[":protocol"] = "webtransport"; |
| |
| client_->SendMessage(headers, "", /*fin=*/false); |
| QuicSpdyStream* stream = client_->latest_created_stream(); |
| if (stream->web_transport() == nullptr) { |
| return nullptr; |
| } |
| WebTransportSessionId id = client_->latest_created_stream()->id(); |
| QuicSpdySession* client_session = GetClientSession(); |
| if (client_session->GetWebTransportSession(id) == nullptr) { |
| return nullptr; |
| } |
| WebTransportHttp3* session = client_session->GetWebTransportSession(id); |
| if (wait_for_server_response) { |
| client_->WaitUntil(-1, |
| [stream]() { return stream->headers_decompressed(); }); |
| EXPECT_TRUE(session->ready()); |
| } |
| if (connect_stream_out != nullptr) { |
| *connect_stream_out = stream; |
| } |
| return session; |
| } |
| |
| NiceMock<MockWebTransportSessionVisitor>& SetupWebTransportVisitor( |
| WebTransportHttp3* session) { |
| auto visitor_owned = |
| std::make_unique<NiceMock<MockWebTransportSessionVisitor>>(); |
| NiceMock<MockWebTransportSessionVisitor>& visitor = *visitor_owned; |
| session->SetVisitor(std::move(visitor_owned)); |
| return visitor; |
| } |
| |
| std::string ReadDataFromWebTransportStreamUntilFin( |
| WebTransportStream* stream, |
| MockWebTransportStreamVisitor* visitor = nullptr) { |
| QuicStreamId id = stream->GetStreamId(); |
| std::string buffer; |
| |
| // Try reading data if immediately available. |
| WebTransportStream::ReadResult result = stream->Read(&buffer); |
| if (result.fin) { |
| return buffer; |
| } |
| |
| while (true) { |
| bool can_read = false; |
| if (visitor == nullptr) { |
| auto visitor_owned = std::make_unique<MockWebTransportStreamVisitor>(); |
| visitor = visitor_owned.get(); |
| stream->SetVisitor(std::move(visitor_owned)); |
| } |
| EXPECT_CALL(*visitor, OnCanRead()) |
| .WillRepeatedly(Assign(&can_read, true)); |
| client_->WaitUntil(5000 /*ms*/, [&can_read]() { return can_read; }); |
| if (!can_read) { |
| ADD_FAILURE() << "Waiting for readable data on stream " << id |
| << " timed out"; |
| return buffer; |
| } |
| if (GetClientSession()->GetOrCreateSpdyDataStream(id) == nullptr) { |
| ADD_FAILURE() << "Stream " << id |
| << " was deleted while waiting for incoming data"; |
| return buffer; |
| } |
| |
| result = stream->Read(&buffer); |
| if (result.fin) { |
| return buffer; |
| } |
| if (result.bytes_read == 0) { |
| ADD_FAILURE() << "No progress made while reading from stream " |
| << stream->GetStreamId(); |
| return buffer; |
| } |
| } |
| } |
| |
| void ReadAllIncomingWebTransportUnidirectionalStreams( |
| WebTransportSession* session) { |
| while (true) { |
| WebTransportStream* received_stream = |
| session->AcceptIncomingUnidirectionalStream(); |
| if (received_stream == nullptr) { |
| break; |
| } |
| received_webtransport_unidirectional_streams_.push_back( |
| ReadDataFromWebTransportStreamUntilFin(received_stream)); |
| } |
| } |
| |
| void WaitForNewConnectionIds() { |
| // Wait until a new server CID is available for another migration. |
| const auto* client_connection = GetClientConnection(); |
| while (!QuicConnectionPeer::HasUnusedPeerIssuedConnectionId( |
| client_connection) || |
| (!client_connection->client_connection_id().IsEmpty() && |
| !QuicConnectionPeer::HasSelfIssuedConnectionIdToConsume( |
| client_connection))) { |
| client_->client()->WaitForEvents(); |
| } |
| } |
| |
| quiche::test::ScopedEnvironmentForThreads environment_; |
| bool initialized_; |
| // If true, the Initialize() function will create |client_| and starts to |
| // connect to the server. |
| // Default is true. |
| bool connect_to_server_on_initialize_; |
| QuicSocketAddress server_address_; |
| absl::optional<QuicSocketAddress> server_listening_address_; |
| std::string server_hostname_; |
| QuicTestBackend memory_cache_backend_; |
| std::unique_ptr<ServerThread> server_thread_; |
| // This socket keeps the ephemeral port reserved so that the kernel doesn't |
| // give it away while the server is shut down. |
| QuicUdpSocketFd fd_; |
| std::unique_ptr<QuicTestClient> client_; |
| QuicConnectionDebugVisitor* connection_debug_visitor_ = nullptr; |
| PacketDroppingTestWriter* client_writer_; |
| PacketDroppingTestWriter* server_writer_; |
| QuicConfig client_config_; |
| QuicConfig server_config_; |
| ParsedQuicVersion version_; |
| ParsedQuicVersionVector client_supported_versions_; |
| ParsedQuicVersionVector server_supported_versions_; |
| QuicTagVector client_extra_copts_; |
| size_t chlo_multiplier_; |
| QuicTestServer::StreamFactory* stream_factory_; |
| std::string pre_shared_key_client_; |
| std::string pre_shared_key_server_; |
| int override_server_connection_id_length_; |
| int override_client_connection_id_length_ = -1; |
| uint8_t expected_server_connection_id_length_; |
| bool enable_web_transport_ = false; |
| std::vector<std::string> received_webtransport_unidirectional_streams_; |
| }; |
| |
| // Run all end to end tests with all supported versions. |
| INSTANTIATE_TEST_SUITE_P(EndToEndTests, EndToEndTest, |
| ::testing::ValuesIn(GetTestParams()), |
| ::testing::PrintToStringParamName()); |
| |
| TEST_P(EndToEndTest, HandshakeSuccessful) { |
| ASSERT_TRUE(Initialize()); |
| EXPECT_TRUE(client_->client()->WaitForOneRttKeysAvailable()); |
| ASSERT_TRUE(server_thread_); |
| server_thread_->WaitForCryptoHandshakeConfirmed(); |
| QuicSpdyClientSession* client_session = GetClientSession(); |
| ASSERT_TRUE(client_session); |
| QuicCryptoStream* client_crypto_stream = |
| QuicSessionPeer::GetMutableCryptoStream(client_session); |
| ASSERT_TRUE(client_crypto_stream); |
| QuicStreamSequencer* client_sequencer = |
| QuicStreamPeer::sequencer(client_crypto_stream); |
| ASSERT_TRUE(client_sequencer); |
| EXPECT_FALSE( |
| QuicStreamSequencerPeer::IsUnderlyingBufferAllocated(client_sequencer)); |
| |
| // We've had bugs in the past where the connections could end up on the wrong |
| // version. This was never diagnosed but could have been due to in-connection |
| // version negotiation back when that existed. At this point in time, our test |
| // setup ensures that connections here always use |version_|, but we add this |
| // sanity check out of paranoia to catch a regression of this type. |
| QuicConnection* client_connection = GetClientConnection(); |
| ASSERT_TRUE(client_connection); |
| EXPECT_EQ(client_connection->version(), version_); |
| |
| server_thread_->Pause(); |
| QuicSpdySession* server_session = GetServerSession(); |
| QuicConnection* server_connection = nullptr; |
| QuicCryptoStream* server_crypto_stream = nullptr; |
| QuicStreamSequencer* server_sequencer = nullptr; |
| if (server_session != nullptr) { |
| server_connection = server_session->connection(); |
| server_crypto_stream = |
| QuicSessionPeer::GetMutableCryptoStream(server_session); |
| } else { |
| ADD_FAILURE() << "Missing server session"; |
| } |
| if (server_crypto_stream != nullptr) { |
| server_sequencer = QuicStreamPeer::sequencer(server_crypto_stream); |
| } else { |
| ADD_FAILURE() << "Missing server crypto stream"; |
| } |
| if (server_sequencer != nullptr) { |
| EXPECT_FALSE( |
| QuicStreamSequencerPeer::IsUnderlyingBufferAllocated(server_sequencer)); |
| } else { |
| ADD_FAILURE() << "Missing server sequencer"; |
| } |
| if (server_connection != nullptr) { |
| EXPECT_EQ(server_connection->version(), version_); |
| } else { |
| ADD_FAILURE() << "Missing server connection"; |
| } |
| server_thread_->Resume(); |
| } |
| |
| TEST_P(EndToEndTest, ExportKeyingMaterial) { |
| ASSERT_TRUE(Initialize()); |
| if (!version_.UsesTls()) { |
| return; |
| } |
| const char* kExportLabel = "label"; |
| const int kExportLen = 30; |
| std::string client_keying_material_export, server_keying_material_export; |
| |
| EXPECT_TRUE(client_->client()->WaitForOneRttKeysAvailable()); |
| ASSERT_TRUE(server_thread_); |
| server_thread_->WaitForCryptoHandshakeConfirmed(); |
| |
| server_thread_->Pause(); |
| QuicSpdySession* server_session = GetServerSession(); |
| QuicCryptoStream* server_crypto_stream = nullptr; |
| if (server_session != nullptr) { |
| server_crypto_stream = |
| QuicSessionPeer::GetMutableCryptoStream(server_session); |
| } else { |
| ADD_FAILURE() << "Missing server session"; |
| } |
| if (server_crypto_stream != nullptr) { |
| ASSERT_TRUE(server_crypto_stream->ExportKeyingMaterial( |
| kExportLabel, /*context=*/"", kExportLen, |
| &server_keying_material_export)); |
| |
| } else { |
| ADD_FAILURE() << "Missing server crypto stream"; |
| } |
| server_thread_->Resume(); |
| |
| QuicSpdyClientSession* client_session = GetClientSession(); |
| ASSERT_TRUE(client_session); |
| QuicCryptoStream* client_crypto_stream = |
| QuicSessionPeer::GetMutableCryptoStream(client_session); |
| ASSERT_TRUE(client_crypto_stream); |
| ASSERT_TRUE(client_crypto_stream->ExportKeyingMaterial( |
| kExportLabel, /*context=*/"", kExportLen, |
| &client_keying_material_export)); |
| ASSERT_EQ(client_keying_material_export.size(), |
| static_cast<size_t>(kExportLen)); |
| EXPECT_EQ(client_keying_material_export, server_keying_material_export); |
| } |
| |
| TEST_P(EndToEndTest, SimpleRequestResponse) { |
| ASSERT_TRUE(Initialize()); |
| |
| SendSynchronousFooRequestAndCheckResponse(); |
| EXPECT_FALSE(client_->client()->EarlyDataAccepted()); |
| EXPECT_FALSE(client_->client()->ReceivedInchoateReject()); |
| if (version_.UsesHttp3()) { |
| QuicSpdyClientSession* client_session = GetClientSession(); |
| ASSERT_TRUE(client_session); |
| EXPECT_TRUE(QuicSpdySessionPeer::GetSendControlStream(client_session)); |
| EXPECT_TRUE(QuicSpdySessionPeer::GetReceiveControlStream(client_session)); |
| server_thread_->Pause(); |
| QuicSpdySession* server_session = GetServerSession(); |
| if (server_session != nullptr) { |
| EXPECT_TRUE(QuicSpdySessionPeer::GetSendControlStream(server_session)); |
| EXPECT_TRUE(QuicSpdySessionPeer::GetReceiveControlStream(server_session)); |
| } else { |
| ADD_FAILURE() << "Missing server session"; |
| } |
| server_thread_->Resume(); |
| } |
| QuicConnectionStats client_stats = GetClientConnection()->GetStats(); |
| EXPECT_TRUE(client_stats.handshake_completion_time.IsInitialized()); |
| } |
| |
| TEST_P(EndToEndTest, HandshakeConfirmed) { |
| ASSERT_TRUE(Initialize()); |
| if (!version_.UsesTls()) { |
| return; |
| } |
| SendSynchronousFooRequestAndCheckResponse(); |
| // Verify handshake state. |
| QuicSpdyClientSession* client_session = GetClientSession(); |
| ASSERT_TRUE(client_session); |
| EXPECT_EQ(HANDSHAKE_CONFIRMED, client_session->GetHandshakeState()); |
| server_thread_->Pause(); |
| QuicSpdySession* server_session = GetServerSession(); |
| if (server_session != nullptr) { |
| EXPECT_EQ(HANDSHAKE_CONFIRMED, server_session->GetHandshakeState()); |
| } else { |
| ADD_FAILURE() << "Missing server session"; |
| } |
| server_thread_->Resume(); |
| client_->Disconnect(); |
| } |
| |
| TEST_P(EndToEndTest, SendAndReceiveCoalescedPackets) { |
| ASSERT_TRUE(Initialize()); |
| if (!version_.CanSendCoalescedPackets()) { |
| return; |
| } |
| SendSynchronousFooRequestAndCheckResponse(); |
| // Verify client successfully processes coalesced packets. |
| QuicConnection* client_connection = GetClientConnection(); |
| ASSERT_TRUE(client_connection); |
| QuicConnectionStats client_stats = client_connection->GetStats(); |
| EXPECT_LT(0u, client_stats.num_coalesced_packets_received); |
| EXPECT_EQ(client_stats.num_coalesced_packets_processed, |
| client_stats.num_coalesced_packets_received); |
| // TODO(fayang): verify server successfully processes coalesced packets. |
| } |
| |
| // Simple transaction, but set a non-default ack delay at the client |
| // and ensure it gets to the server. |
| TEST_P(EndToEndTest, SimpleRequestResponseWithAckDelayChange) { |
| // Force the ACK delay to be something other than the default. |
| constexpr uint32_t kClientMaxAckDelay = kDefaultDelayedAckTimeMs + 100u; |
| client_config_.SetMaxAckDelayToSendMs(kClientMaxAckDelay); |
| ASSERT_TRUE(Initialize()); |
| |
| SendSynchronousFooRequestAndCheckResponse(); |
| EXPECT_FALSE(client_->client()->EarlyDataAccepted()); |
| EXPECT_FALSE(client_->client()->ReceivedInchoateReject()); |
| |
| server_thread_->Pause(); |
| const QuicSentPacketManager* server_sent_packet_manager = |
| GetSentPacketManagerFromFirstServerSession(); |
| if (server_sent_packet_manager != nullptr) { |
| EXPECT_EQ( |
| kClientMaxAckDelay, |
| server_sent_packet_manager->peer_max_ack_delay().ToMilliseconds()); |
| } else { |
| ADD_FAILURE() << "Missing server sent packet manager"; |
| } |
| server_thread_->Resume(); |
| } |
| |
| // Simple transaction, but set a non-default ack exponent at the client |
| // and ensure it gets to the server. |
| TEST_P(EndToEndTest, SimpleRequestResponseWithAckExponentChange) { |
| const uint32_t kClientAckDelayExponent = 19; |
| EXPECT_NE(kClientAckDelayExponent, kDefaultAckDelayExponent); |
| // Force the ACK exponent to be something other than the default. |
| // Note that it is sent only with QUIC+TLS. |
| client_config_.SetAckDelayExponentToSend(kClientAckDelayExponent); |
| ASSERT_TRUE(Initialize()); |
| |
| SendSynchronousFooRequestAndCheckResponse(); |
| |
| EXPECT_FALSE(client_->client()->EarlyDataAccepted()); |
| EXPECT_FALSE(client_->client()->ReceivedInchoateReject()); |
| server_thread_->Pause(); |
| QuicConnection* server_connection = GetServerConnection(); |
| if (server_connection != nullptr) { |
| if (version_.UsesTls()) { |
| // Should be only sent with QUIC+TLS. |
| EXPECT_EQ(kClientAckDelayExponent, |
| server_connection->framer().peer_ack_delay_exponent()); |
| } else { |
| // No change for QUIC_CRYPTO. |
| EXPECT_EQ(kDefaultAckDelayExponent, |
| server_connection->framer().peer_ack_delay_exponent()); |
| } |
| // No change, regardless of version. |
| EXPECT_EQ(kDefaultAckDelayExponent, |
| server_connection->framer().local_ack_delay_exponent()); |
| } else { |
| ADD_FAILURE() << "Missing server connection"; |
| } |
| server_thread_->Resume(); |
| } |
| |
| TEST_P(EndToEndTest, SimpleRequestResponseForcedVersionNegotiation) { |
| client_supported_versions_.insert(client_supported_versions_.begin(), |
| QuicVersionReservedForNegotiation()); |
| NiceMock<MockQuicConnectionDebugVisitor> visitor; |
| connection_debug_visitor_ = &visitor; |
| EXPECT_CALL(visitor, OnVersionNegotiationPacket(_)).Times(1); |
| ASSERT_TRUE(Initialize()); |
| ASSERT_TRUE(ServerSendsVersionNegotiation()); |
| |
| SendSynchronousFooRequestAndCheckResponse(); |
| |
| EXPECT_FALSE(client_->client()->EarlyDataAccepted()); |
| EXPECT_FALSE(client_->client()->ReceivedInchoateReject()); |
| } |
| |
| TEST_P(EndToEndTest, ForcedVersionNegotiation) { |
| client_supported_versions_.insert(client_supported_versions_.begin(), |
| QuicVersionReservedForNegotiation()); |
| ASSERT_TRUE(Initialize()); |
| ASSERT_TRUE(ServerSendsVersionNegotiation()); |
| |
| SendSynchronousFooRequestAndCheckResponse(); |
| } |
| |
| TEST_P(EndToEndTest, SimpleRequestResponseZeroConnectionID) { |
| if (!version_.AllowsVariableLengthConnectionIds() || |
| override_server_connection_id_length_ > -1) { |
| ASSERT_TRUE(Initialize()); |
| return; |
| } |
| override_server_connection_id_length_ = 0; |
| expected_server_connection_id_length_ = 0; |
| ASSERT_TRUE(Initialize()); |
| |
| SendSynchronousFooRequestAndCheckResponse(); |
| EXPECT_FALSE(client_->client()->EarlyDataAccepted()); |
| EXPECT_FALSE(client_->client()->ReceivedInchoateReject()); |
| QuicConnection* client_connection = GetClientConnection(); |
| ASSERT_TRUE(client_connection); |
| EXPECT_EQ(client_connection->connection_id(), |
| QuicUtils::CreateZeroConnectionId(version_.transport_version)); |
| } |
| |
| TEST_P(EndToEndTest, ZeroConnectionID) { |
| if (!version_.AllowsVariableLengthConnectionIds() || |
| override_server_connection_id_length_ > -1) { |
| ASSERT_TRUE(Initialize()); |
| return; |
| } |
| override_server_connection_id_length_ = 0; |
| expected_server_connection_id_length_ = 0; |
| ASSERT_TRUE(Initialize()); |
| |
| SendSynchronousFooRequestAndCheckResponse(); |
| QuicConnection* client_connection = GetClientConnection(); |
| ASSERT_TRUE(client_connection); |
| EXPECT_EQ(client_connection->connection_id(), |
| QuicUtils::CreateZeroConnectionId(version_.transport_version)); |
| } |
| |
| TEST_P(EndToEndTest, BadConnectionIdLength) { |
| if (!version_.AllowsVariableLengthConnectionIds() || |
| override_server_connection_id_length_ > -1) { |
| ASSERT_TRUE(Initialize()); |
| return; |
| } |
| override_server_connection_id_length_ = 9; |
| ASSERT_TRUE(Initialize()); |
| SendSynchronousFooRequestAndCheckResponse(); |
| EXPECT_EQ(kQuicDefaultConnectionIdLength, client_->client() |
| ->client_session() |
| ->connection() |
| ->connection_id() |
| .length()); |
| } |
| |
| TEST_P(EndToEndTest, ClientConnectionId) { |
| if (!version_.SupportsClientConnectionIds()) { |
| ASSERT_TRUE(Initialize()); |
| return; |
| } |
| override_client_connection_id_length_ = kQuicDefaultConnectionIdLength; |
| ASSERT_TRUE(Initialize()); |
| SendSynchronousFooRequestAndCheckResponse(); |
| EXPECT_EQ(override_client_connection_id_length_, client_->client() |
| ->client_session() |
| ->connection() |
| ->client_connection_id() |
| .length()); |
| } |
| |
| TEST_P(EndToEndTest, ForcedVersionNegotiationAndClientConnectionId) { |
| if (!version_.SupportsClientConnectionIds()) { |
| ASSERT_TRUE(Initialize()); |
| return; |
| } |
| client_supported_versions_.insert(client_supported_versions_.begin(), |
| QuicVersionReservedForNegotiation()); |
| override_client_connection_id_length_ = kQuicDefaultConnectionIdLength; |
| ASSERT_TRUE(Initialize()); |
| ASSERT_TRUE(ServerSendsVersionNegotiation()); |
| SendSynchronousFooRequestAndCheckResponse(); |
| EXPECT_EQ(override_client_connection_id_length_, client_->client() |
| ->client_session() |
| ->connection() |
| ->client_connection_id() |
| .length()); |
| } |
| |
| TEST_P(EndToEndTest, ForcedVersionNegotiationAndBadConnectionIdLength) { |
| if (!version_.AllowsVariableLengthConnectionIds() || |
| override_server_connection_id_length_ > -1) { |
| ASSERT_TRUE(Initialize()); |
| return; |
| } |
| client_supported_versions_.insert(client_supported_versions_.begin(), |
| QuicVersionReservedForNegotiation()); |
| override_server_connection_id_length_ = 9; |
| ASSERT_TRUE(Initialize()); |
| ASSERT_TRUE(ServerSendsVersionNegotiation()); |
| SendSynchronousFooRequestAndCheckResponse(); |
| EXPECT_EQ(kQuicDefaultConnectionIdLength, client_->client() |
| ->client_session() |
| ->connection() |
| ->connection_id() |
| .length()); |
| } |
| |
| // Forced Version Negotiation with a client connection ID and a long |
| // connection ID. |
| TEST_P(EndToEndTest, ForcedVersNegoAndClientCIDAndLongCID) { |
| if (!version_.SupportsClientConnectionIds() || |
| !version_.AllowsVariableLengthConnectionIds() || |
| override_server_connection_id_length_ != kLongConnectionIdLength) { |
| ASSERT_TRUE(Initialize()); |
| return; |
| } |
| client_supported_versions_.insert(client_supported_versions_.begin(), |
| QuicVersionReservedForNegotiation()); |
| override_client_connection_id_length_ = 18; |
| ASSERT_TRUE(Initialize()); |
| ASSERT_TRUE(ServerSendsVersionNegotiation()); |
| SendSynchronousFooRequestAndCheckResponse(); |
| EXPECT_EQ(kQuicDefaultConnectionIdLength, client_->client() |
| ->client_session() |
| ->connection() |
| ->connection_id() |
| .length()); |
| EXPECT_EQ(override_client_connection_id_length_, client_->client() |
| ->client_session() |
| ->connection() |
| ->client_connection_id() |
| .length()); |
| } |
| |
| TEST_P(EndToEndTest, MixGoodAndBadConnectionIdLengths) { |
| if (!version_.AllowsVariableLengthConnectionIds() || |
| override_server_connection_id_length_ > -1) { |
| ASSERT_TRUE(Initialize()); |
| return; |
| } |
| |
| // Start client_ which will use a bad connection ID length. |
| override_server_connection_id_length_ = 9; |
| ASSERT_TRUE(Initialize()); |
| override_server_connection_id_length_ = -1; |
| |
| // Start client2 which will use a good connection ID length. |
| std::unique_ptr<QuicTestClient> client2(CreateQuicClient(nullptr)); |
| Http2HeaderBlock headers; |
| headers[":method"] = "POST"; |
| headers[":path"] = "/foo"; |
| headers[":scheme"] = "https"; |
| headers[":authority"] = server_hostname_; |
| headers["content-length"] = "3"; |
| client2->SendMessage(headers, "", /*fin=*/false); |
| client2->SendData("eep", true); |
| |
| SendSynchronousFooRequestAndCheckResponse(); |
| EXPECT_EQ(kQuicDefaultConnectionIdLength, client_->client() |
| ->client_session() |
| ->connection() |
| ->connection_id() |
| .length()); |
| |
| WaitForFooResponseAndCheckIt(client2.get()); |
| EXPECT_EQ(kQuicDefaultConnectionIdLength, client2->client() |
| ->client_session() |
| ->connection() |
| ->connection_id() |
| .length()); |
| } |
| |
| TEST_P(EndToEndTest, SimpleRequestResponseWithIetfDraftSupport) { |
| if (!version_.HasIetfQuicFrames()) { |
| ASSERT_TRUE(Initialize()); |
| return; |
| } |
| QuicVersionInitializeSupportForIetfDraft(); |
| ASSERT_TRUE(Initialize()); |
| |
| SendSynchronousFooRequestAndCheckResponse(); |
| } |
| |
| TEST_P(EndToEndTest, SimpleRequestResponseWithLargeReject) { |
| chlo_multiplier_ = 1; |
| ASSERT_TRUE(Initialize()); |
| |
| SendSynchronousFooRequestAndCheckResponse(); |
| EXPECT_FALSE(client_->client()->EarlyDataAccepted()); |
| if (version_.UsesTls()) { |
| // REJ messages are a QUIC crypto feature, so TLS always returns false. |
| EXPECT_FALSE(client_->client()->ReceivedInchoateReject()); |
| } else { |
| EXPECT_TRUE(client_->client()->ReceivedInchoateReject()); |
| } |
| } |
| |
| TEST_P(EndToEndTest, SimpleRequestResponsev6) { |
| server_address_ = |
| QuicSocketAddress(QuicIpAddress::Loopback6(), server_address_.port()); |
| ASSERT_TRUE(Initialize()); |
| |
| SendSynchronousFooRequestAndCheckResponse(); |
| } |
| |
| TEST_P(EndToEndTest, |
| ClientDoesNotAllowServerDataOnServerInitiatedBidirectionalStreams) { |
| set_client_initial_max_stream_data_incoming_bidirectional(0); |
| ASSERT_TRUE(Initialize()); |
| SendSynchronousFooRequestAndCheckResponse(); |
| } |
| |
| TEST_P(EndToEndTest, |
| ServerDoesNotAllowClientDataOnServerInitiatedBidirectionalStreams) { |
| set_server_initial_max_stream_data_outgoing_bidirectional(0); |
| ASSERT_TRUE(Initialize()); |
| SendSynchronousFooRequestAndCheckResponse(); |
| } |
| |
| TEST_P(EndToEndTest, |
| BothEndpointsDisallowDataOnServerInitiatedBidirectionalStreams) { |
| set_client_initial_max_stream_data_incoming_bidirectional(0); |
| set_server_initial_max_stream_data_outgoing_bidirectional(0); |
| ASSERT_TRUE(Initialize()); |
| SendSynchronousFooRequestAndCheckResponse(); |
| } |
| |
| // Regression test for a bug where we would always fail to decrypt the first |
| // initial packet. Undecryptable packets can be seen after the handshake |
| // is complete due to dropping the initial keys at that point, so we only test |
| // for undecryptable packets before then. |
| TEST_P(EndToEndTest, NoUndecryptablePacketsBeforeHandshakeComplete) { |
| ASSERT_TRUE(Initialize()); |
| |
| SendSynchronousFooRequestAndCheckResponse(); |
| |
| QuicConnection* client_connection = GetClientConnection(); |
| ASSERT_TRUE(client_connection); |
| QuicConnectionStats client_stats = client_connection->GetStats(); |
| EXPECT_EQ( |
| 0u, |
| client_stats.undecryptable_packets_received_before_handshake_complete); |
| |
| server_thread_->Pause(); |
| QuicConnection* server_connection = GetServerConnection(); |
| if (server_connection != nullptr) { |
| QuicConnectionStats server_stats = server_connection->GetStats(); |
| EXPECT_EQ( |
| 0u, |
| server_stats.undecryptable_packets_received_before_handshake_complete); |
| } else { |
| ADD_FAILURE() << "Missing server connection"; |
| } |
| server_thread_->Resume(); |
| } |
| |
| TEST_P(EndToEndTest, SeparateFinPacket) { |
| ASSERT_TRUE(Initialize()); |
| |
| // Send a request in two parts: the request and then an empty packet with FIN. |
| Http2HeaderBlock headers; |
| headers[":method"] = "POST"; |
| headers[":path"] = "/foo"; |
| headers[":scheme"] = "https"; |
| headers[":authority"] = server_hostname_; |
| client_->SendMessage(headers, "", /*fin=*/false); |
| client_->SendData("", true); |
| WaitForFooResponseAndCheckIt(); |
| |
| // Now do the same thing but with a content length. |
| headers["content-length"] = "3"; |
| client_->SendMessage(headers, "", /*fin=*/false); |
| client_->SendData("foo", true); |
| WaitForFooResponseAndCheckIt(); |
| } |
| |
| TEST_P(EndToEndTest, MultipleRequestResponse) { |
| ASSERT_TRUE(Initialize()); |
| |
| SendSynchronousFooRequestAndCheckResponse(); |
| SendSynchronousBarRequestAndCheckResponse(); |
| } |
| |
| TEST_P(EndToEndTest, MultipleRequestResponseZeroConnectionID) { |
| if (!version_.AllowsVariableLengthConnectionIds() || |
| override_server_connection_id_length_ > -1) { |
| ASSERT_TRUE(Initialize()); |
| return; |
| } |
| override_server_connection_id_length_ = 0; |
| expected_server_connection_id_length_ = 0; |
| ASSERT_TRUE(Initialize()); |
| |
| SendSynchronousFooRequestAndCheckResponse(); |
| SendSynchronousBarRequestAndCheckResponse(); |
| } |
| |
| TEST_P(EndToEndTest, MultipleStreams) { |
| // Verifies quic_test_client can track responses of all active streams. |
| ASSERT_TRUE(Initialize()); |
| |
| const int kNumRequests = 10; |
| |
| Http2HeaderBlock headers; |
| headers[":method"] = "POST"; |
| headers[":path"] = "/foo"; |
| headers[":scheme"] = "https"; |
| headers[":authority"] = server_hostname_; |
| headers["content-length"] = "3"; |
| |
| for (int i = 0; i < kNumRequests; ++i) { |
| client_->SendMessage(headers, "bar", /*fin=*/true); |
| } |
| |
| while (kNumRequests > client_->num_responses()) { |
| client_->ClearPerRequestState(); |
| ASSERT_TRUE(WaitForFooResponseAndCheckIt()); |
| } |
| } |
| |
| TEST_P(EndToEndTest, MultipleClients) { |
| ASSERT_TRUE(Initialize()); |
| std::unique_ptr<QuicTestClient> client2(CreateQuicClient(nullptr)); |
| |
| Http2HeaderBlock headers; |
| headers[":method"] = "POST"; |
| headers[":path"] = "/foo"; |
| headers[":scheme"] = "https"; |
| headers[":authority"] = server_hostname_; |
| headers["content-length"] = "3"; |
| |
| client_->SendMessage(headers, "", /*fin=*/false); |
| client2->SendMessage(headers, "", /*fin=*/false); |
| |
| client_->SendData("bar", true); |
| WaitForFooResponseAndCheckIt(); |
| |
| client2->SendData("eep", true); |
| WaitForFooResponseAndCheckIt(client2.get()); |
| } |
| |
| TEST_P(EndToEndTest, RequestOverMultiplePackets) { |
| // Send a large enough request to guarantee fragmentation. |
| std::string huge_request = |
| "/some/path?query=" + std::string(kMaxOutgoingPacketSize, '.'); |
| AddToCache(huge_request, 200, kBarResponseBody); |
| |
| ASSERT_TRUE(Initialize()); |
| |
| SendSynchronousRequestAndCheckResponse(huge_request, kBarResponseBody); |
| } |
| |
| TEST_P(EndToEndTest, MultiplePacketsRandomOrder) { |
| // Send a large enough request to guarantee fragmentation. |
| std::string huge_request = |
| "/some/path?query=" + std::string(kMaxOutgoingPacketSize, '.'); |
| AddToCache(huge_request, 200, kBarResponseBody); |
| |
| ASSERT_TRUE(Initialize()); |
| SetPacketSendDelay(QuicTime::Delta::FromMilliseconds(2)); |
| SetReorderPercentage(50); |
| |
| SendSynchronousRequestAndCheckResponse(huge_request, kBarResponseBody); |
| } |
| |
| TEST_P(EndToEndTest, PostMissingBytes) { |
| ASSERT_TRUE(Initialize()); |
| |
| // Add a content length header with no body. |
| Http2HeaderBlock headers; |
| headers[":method"] = "POST"; |
| headers[":path"] = "/foo"; |
| headers[":scheme"] = "https"; |
| headers[":authority"] = server_hostname_; |
| headers["content-length"] = "3"; |
| |
| // This should be detected as stream fin without complete request, |
| // triggering an error response. |
| client_->SendCustomSynchronousRequest(headers, ""); |
| EXPECT_EQ(QuicSimpleServerStream::kErrorResponseBody, |
| client_->response_body()); |
| CheckResponseHeaders("500"); |
| } |
| |
| TEST_P(EndToEndTest, LargePostNoPacketLoss) { |
| ASSERT_TRUE(Initialize()); |
| |
| EXPECT_TRUE(client_->client()->WaitForOneRttKeysAvailable()); |
| |
| // 1 MB body. |
| std::string body(1024 * 1024, 'a'); |
| Http2HeaderBlock headers; |
| headers[":method"] = "POST"; |
| headers[":path"] = "/foo"; |
| headers[":scheme"] = "https"; |
| headers[":authority"] = server_hostname_; |
| |
| EXPECT_EQ(kFooResponseBody, |
| client_->SendCustomSynchronousRequest(headers, body)); |
| // TODO(ianswett): There should not be packet loss in this test, but on some |
| // platforms the receive buffer overflows. |
| VerifyCleanConnection(true); |
| } |
| |
| // Marked as slow since this adds a real-clock one second of delay. |
| TEST_P(EndToEndTest, QUICHE_SLOW_TEST(LargePostNoPacketLoss1sRTT)) { |
| ASSERT_TRUE(Initialize()); |
| SetPacketSendDelay(QuicTime::Delta::FromMilliseconds(1000)); |
| |
| EXPECT_TRUE(client_->client()->WaitForOneRttKeysAvailable()); |
| |
| // 100 KB body. |
| std::string body(100 * 1024, 'a'); |
| Http2HeaderBlock headers; |
| headers[":method"] = "POST"; |
| headers[":path"] = "/foo"; |
| headers[":scheme"] = "https"; |
| headers[":authority"] = server_hostname_; |
| |
| EXPECT_EQ(kFooResponseBody, |
| client_->SendCustomSynchronousRequest(headers, body)); |
| VerifyCleanConnection(false); |
| } |
| |
| TEST_P(EndToEndTest, LargePostWithPacketLoss) { |
| // Connect with lower fake packet loss than we'd like to test. |
| // Until b/10126687 is fixed, losing handshake packets is pretty |
| // brutal. |
| // Disable blackhole detection as this test is testing loss recovery. |
| client_extra_copts_.push_back(kNBHD); |
| SetPacketLossPercentage(5); |
| ASSERT_TRUE(Initialize()); |
| EXPECT_TRUE(client_->client()->WaitForHandshakeConfirmed()); |
| SetPacketLossPercentage(30); |
| |
| // 10 KB body. |
| std::string body(1024 * 10, 'a'); |
| Http2HeaderBlock headers; |
| headers[":method"] = "POST"; |
| headers[":path"] = "/foo"; |
| headers[":scheme"] = "https"; |
| headers[":authority"] = server_hostname_; |
| |
| EXPECT_EQ(kFooResponseBody, |
| client_->SendCustomSynchronousRequest(headers, body)); |
| if (override_server_connection_id_length_ == -1) { |
| // If the client sends a longer connection ID, we can end up with dropped |
| // packets. The packets_dropped counter increments whenever a packet arrives |
| // with a new server connection ID that is not INITIAL, RETRY, or 1-RTT. |
| // With packet losses, we could easily lose a server INITIAL and have the |
| // first observed server packet be HANDSHAKE. |
| VerifyCleanConnection(true); |
| } |
| } |
| |
| // Regression test for b/80090281. |
| TEST_P(EndToEndTest, LargePostWithPacketLossAndAlwaysBundleWindowUpdates) { |
| // Disable blackhole detection as this test is testing loss recovery. |
| client_extra_copts_.push_back(kNBHD); |
| ASSERT_TRUE(Initialize()); |
| EXPECT_TRUE(client_->client()->WaitForHandshakeConfirmed()); |
| server_thread_->WaitForCryptoHandshakeConfirmed(); |
| |
| // Normally server only bundles a retransmittable frame once every other |
| // kMaxConsecutiveNonRetransmittablePackets ack-only packets. Setting the max |
| // to 0 to reliably reproduce b/80090281. |
| server_thread_->Schedule([this]() { |
| QuicConnection* server_connection = GetServerConnection(); |
| if (server_connection != nullptr) { |
| QuicConnectionPeer:: |
| SetMaxConsecutiveNumPacketsWithNoRetransmittableFrames( |
| server_connection, 0); |
| } else { |
| ADD_FAILURE() << "Missing server connection"; |
| } |
| }); |
| |
| SetPacketLossPercentage(30); |
| |
| // 10 KB body. |
| std::string body(1024 * 10, 'a'); |
| Http2HeaderBlock headers; |
| headers[":method"] = "POST"; |
| headers[":path"] = "/foo"; |
| headers[":scheme"] = "https"; |
| headers[":authority"] = server_hostname_; |
| |
| EXPECT_EQ(kFooResponseBody, |
| client_->SendCustomSynchronousRequest(headers, body)); |
| VerifyCleanConnection(true); |
| } |
| |
| TEST_P(EndToEndTest, LargePostWithPacketLossAndBlockedSocket) { |
| // Connect with lower fake packet loss than we'd like to test. Until |
| // b/10126687 is fixed, losing handshake packets is pretty brutal. |
| // Disable blackhole detection as this test is testing loss recovery. |
| client_extra_copts_.push_back(kNBHD); |
| SetPacketLossPercentage(5); |
| ASSERT_TRUE(Initialize()); |
| EXPECT_TRUE(client_->client()->WaitForHandshakeConfirmed()); |
| SetPacketLossPercentage(10); |
| client_writer_->set_fake_blocked_socket_percentage(10); |
| |
| // 10 KB body. |
| std::string body(1024 * 10, 'a'); |
| Http2HeaderBlock headers; |
| headers[":method"] = "POST"; |
| headers[":path"] = "/foo"; |
| headers[":scheme"] = "https"; |
| headers[":authority"] = server_hostname_; |
| |
| EXPECT_EQ(kFooResponseBody, |
| client_->SendCustomSynchronousRequest(headers, body)); |
| } |
| |
| TEST_P(EndToEndTest, LargePostNoPacketLossWithDelayAndReordering) { |
| ASSERT_TRUE(Initialize()); |
| EXPECT_TRUE(client_->client()->WaitForHandshakeConfirmed()); |
| // Both of these must be called when the writer is not actively used. |
| SetPacketSendDelay(QuicTime::Delta::FromMilliseconds(2)); |
| SetReorderPercentage(30); |
| |
| // 1 MB body. |
| std::string body(1024 * 1024, 'a'); |
| Http2HeaderBlock headers; |
| headers[":method"] = "POST"; |
| headers[":path"] = "/foo"; |
| headers[":scheme"] = "https"; |
| headers[":authority"] = server_hostname_; |
| |
| EXPECT_EQ(kFooResponseBody, |
| client_->SendCustomSynchronousRequest(headers, body)); |
| } |
| |
| // TODO(b/214587920): make this test not rely on timeouts. |
| TEST_P(EndToEndTest, QUICHE_SLOW_TEST(AddressToken)) { |
| client_config_.set_max_time_before_crypto_handshake( |
| QuicTime::Delta::FromSeconds(3)); |
| client_config_.set_max_idle_time_before_crypto_handshake( |
| QuicTime::Delta::FromSeconds(1)); |
| |
| client_extra_copts_.push_back(kTRTT); |
| ASSERT_TRUE(Initialize()); |
| if (!version_.HasIetfQuicFrames()) { |
| return; |
| } |
| |
| SendSynchronousFooRequestAndCheckResponse(); |
| QuicSpdyClientSession* client_session = GetClientSession(); |
| ASSERT_TRUE(client_session); |
| EXPECT_FALSE(client_session->EarlyDataAccepted()); |
| EXPECT_FALSE(client_session->ReceivedInchoateReject()); |
| EXPECT_FALSE(client_->client()->EarlyDataAccepted()); |
| EXPECT_FALSE(client_->client()->ReceivedInchoateReject()); |
| |
| client_->Disconnect(); |
| |
| // The 0-RTT handshake should succeed. |
| client_->Connect(); |
| EXPECT_TRUE(client_->client()->WaitForHandshakeConfirmed()); |
| ASSERT_TRUE(client_->client()->connected()); |
| SendSynchronousFooRequestAndCheckResponse(); |
| |
| client_session = GetClientSession(); |
| ASSERT_TRUE(client_session); |
| EXPECT_TRUE(client_session->EarlyDataAccepted()); |
| EXPECT_TRUE(client_->client()->EarlyDataAccepted()); |
| |
| server_thread_->Pause(); |
| QuicSpdySession* server_session = GetServerSession(); |
| QuicConnection* server_connection = GetServerConnection(); |
| if (server_session != nullptr && server_connection != nullptr) { |
| // Verify address is validated via validating token received in INITIAL |
| // packet. |
| EXPECT_FALSE( |
| server_connection->GetStats().address_validated_via_decrypting_packet); |
| EXPECT_TRUE(server_connection->GetStats().address_validated_via_token); |
| |
| // Verify the server received a cached min_rtt from the token and used it as |
| // the initial rtt. |
| const CachedNetworkParameters* server_received_network_params = |
| static_cast<const QuicCryptoServerStreamBase*>( |
| server_session->GetCryptoStream()) |
| ->PreviousCachedNetworkParams(); |
| |
| ASSERT_NE(server_received_network_params, nullptr); |
| // QuicSentPacketManager::SetInitialRtt clamps the initial_rtt to between |
| // [min_initial_rtt, max_initial_rtt]. |
| const QuicTime::Delta min_initial_rtt = |
| QuicTime::Delta::FromMicroseconds(kMinTrustedInitialRoundTripTimeUs); |
| const QuicTime::Delta max_initial_rtt = |
| QuicTime::Delta::FromMicroseconds(kMaxInitialRoundTripTimeUs); |
| const QuicTime::Delta expected_initial_rtt = |
| std::max(min_initial_rtt, |
| std::min(max_initial_rtt, |
| QuicTime::Delta::FromMilliseconds( |
| server_received_network_params->min_rtt_ms()))); |
| EXPECT_EQ( |
| server_connection->sent_packet_manager().GetRttStats()->initial_rtt(), |
| expected_initial_rtt); |
| } else { |
| ADD_FAILURE() << "Missing server connection"; |
| } |
| |
| server_thread_->Resume(); |
| |
| client_->Disconnect(); |
| |
| // Regression test for b/206087883. |
| // Mock server crash. |
| StopServer(); |
| |
| // The handshake fails due to idle timeout. |
| client_->Connect(); |
| ASSERT_FALSE(client_->client()->WaitForOneRttKeysAvailable()); |
| client_->WaitForWriteToFlush(); |
| client_->WaitForResponse(); |
| ASSERT_FALSE(client_->client()->connected()); |
| EXPECT_THAT(client_->connection_error(), IsError(QUIC_NETWORK_IDLE_TIMEOUT)); |
| |
| // Server restarts. |
| server_writer_ = new PacketDroppingTestWriter(); |
| StartServer(); |
| |
| // Client re-connect. |
| client_->Connect(); |
| ASSERT_TRUE(client_->client()->WaitForHandshakeConfirmed()); |
| client_->WaitForWriteToFlush(); |
| client_->WaitForResponse(); |
| ASSERT_TRUE(client_->client()->connected()); |
| client_session = GetClientSession(); |
| ASSERT_TRUE(client_session); |
| EXPECT_FALSE(client_session->EarlyDataAccepted()); |
| EXPECT_FALSE(client_->client()->EarlyDataAccepted()); |
| server_thread_->Pause(); |
| server_session = GetServerSession(); |
| server_connection = GetServerConnection(); |
| // Verify address token is only used once. |
| if (server_session != nullptr && server_connection != nullptr) { |
| // Verify address is validated via decrypting packet. |
| EXPECT_TRUE( |
| server_connection->GetStats().address_validated_via_decrypting_packet); |
| EXPECT_FALSE(server_connection->GetStats().address_validated_via_token); |
| } else { |
| ADD_FAILURE() << "Missing server connection"; |
| } |
| server_thread_->Resume(); |
| |
| client_->Disconnect(); |
| } |
| |
| // Verify that client does not reuse a source address token. |
| // TODO(b/214587920): make this test not rely on timeouts. |
| TEST_P(EndToEndTest, QUICHE_SLOW_TEST(AddressTokenNotReusedByClient)) { |
| client_config_.set_max_time_before_crypto_handshake( |
| QuicTime::Delta::FromSeconds(3)); |
| client_config_.set_max_idle_time_before_crypto_handshake( |
| QuicTime::Delta::FromSeconds(1)); |
| |
| ASSERT_TRUE(Initialize()); |
| if (!version_.HasIetfQuicFrames()) { |
| return; |
| } |
| |
| QuicCryptoClientConfig* client_crypto_config = |
| client_->client()->crypto_config(); |
| QuicServerId server_id = client_->client()->server_id(); |
| |
| SendSynchronousFooRequestAndCheckResponse(); |
| EXPECT_FALSE(GetClientSession()->EarlyDataAccepted()); |
| |
| client_->Disconnect(); |
| |
| QuicClientSessionCache* session_cache = static_cast<QuicClientSessionCache*>( |
| client_crypto_config->mutable_session_cache()); |
| ASSERT_TRUE( |
| !QuicClientSessionCachePeer::GetToken(session_cache, server_id).empty()); |
| |
| // Pause the server thread again to blackhole packets from client. |
| server_thread_->Pause(); |
| client_->Connect(); |
| EXPECT_FALSE(client_->client()->WaitForOneRttKeysAvailable()); |
| EXPECT_FALSE(client_->client()->connected()); |
| |
| // Verify address token gets cleared. |
| ASSERT_TRUE( |
| QuicClientSessionCachePeer::GetToken(session_cache, server_id).empty()); |
| server_thread_->Resume(); |
| } |
| |
| TEST_P(EndToEndTest, LargePostZeroRTTFailure) { |
| // Send a request and then disconnect. This prepares the client to attempt |
| // a 0-RTT handshake for the next request. |
| ASSERT_TRUE(Initialize()); |
| |
| std::string body(20480, 'a'); |
| Http2HeaderBlock headers; |
| headers[":method"] = "POST"; |
| headers[":path"] = "/foo"; |
| headers[":scheme"] = "https"; |
| headers[":authority"] = server_hostname_; |
| |
| EXPECT_EQ(kFooResponseBody, |
| client_->SendCustomSynchronousRequest(headers, body)); |
| QuicSpdyClientSession* client_session = GetClientSession(); |
| ASSERT_TRUE(client_session); |
| EXPECT_FALSE(client_session->EarlyDataAccepted()); |
| EXPECT_FALSE(client_session->ReceivedInchoateReject()); |
| EXPECT_FALSE(client_->client()->EarlyDataAccepted()); |
| EXPECT_FALSE(client_->client()->ReceivedInchoateReject()); |
| |
| client_->Disconnect(); |
| |
| // The 0-RTT handshake should succeed. |
| client_->Connect(); |
| EXPECT_TRUE(client_->client()->WaitForOneRttKeysAvailable()); |
| ASSERT_TRUE(client_->client()->connected()); |
| EXPECT_EQ(kFooResponseBody, |
| client_->SendCustomSynchronousRequest(headers, body)); |
| |
| client_session = GetClientSession(); |
| ASSERT_TRUE(client_session); |
| EXPECT_TRUE(client_session->EarlyDataAccepted()); |
| EXPECT_TRUE(client_->client()->EarlyDataAccepted()); |
| |
| client_->Disconnect(); |
| |
| // Restart the server so that the 0-RTT handshake will take 1 RTT. |
| StopServer(); |
| server_writer_ = new PacketDroppingTestWriter(); |
| StartServer(); |
| |
| client_->Connect(); |
| EXPECT_TRUE(client_->client()->WaitForOneRttKeysAvailable()); |
| ASSERT_TRUE(client_->client()->connected()); |
| EXPECT_EQ(kFooResponseBody, |
| client_->SendCustomSynchronousRequest(headers, body)); |
| client_session = GetClientSession(); |
| ASSERT_TRUE(client_session); |
| EXPECT_FALSE(client_session->EarlyDataAccepted()); |
| EXPECT_FALSE(client_session->ReceivedInchoateReject()); |
| EXPECT_FALSE(client_->client()->EarlyDataAccepted()); |
| EXPECT_FALSE(client_->client()->ReceivedInchoateReject()); |
| VerifyCleanConnection(false); |
| } |
| |
| // Regression test for b/168020146. |
| TEST_P(EndToEndTest, MultipleZeroRtt) { |
| ASSERT_TRUE(Initialize()); |
| |
| EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo")); |
| QuicSpdyClientSession* client_session = GetClientSession(); |
| ASSERT_TRUE(client_session); |
| EXPECT_FALSE(client_session->EarlyDataAccepted()); |
| EXPECT_FALSE(client_session->ReceivedInchoateReject()); |
| EXPECT_FALSE(client_->client()->EarlyDataAccepted()); |
| EXPECT_FALSE(client_->client()->ReceivedInchoateReject()); |
| |
| client_->Disconnect(); |
| |
| // The 0-RTT handshake should succeed. |
| client_->Connect(); |
| EXPECT_TRUE(client_->client()->WaitForOneRttKeysAvailable()); |
| ASSERT_TRUE(client_->client()->connected()); |
| EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo")); |
| |
| client_session = GetClientSession(); |
| ASSERT_TRUE(client_session); |
| EXPECT_TRUE(client_session->EarlyDataAccepted()); |
| EXPECT_TRUE(client_->client()->EarlyDataAccepted()); |
| |
| client_->Disconnect(); |
| |
| client_->Connect(); |
| EXPECT_TRUE(client_->client()->WaitForOneRttKeysAvailable()); |
| ASSERT_TRUE(client_->client()->connected()); |
| EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo")); |
| |
| client_session = GetClientSession(); |
| ASSERT_TRUE(client_session); |
| EXPECT_TRUE(client_session->EarlyDataAccepted()); |
| EXPECT_TRUE(client_->client()->EarlyDataAccepted()); |
| |
| client_->Disconnect(); |
| } |
| |
| TEST_P(EndToEndTest, SynchronousRequestZeroRTTFailure) { |
| // Send a request and then disconnect. This prepares the client to attempt |
| // a 0-RTT handshake for the next request. |
| ASSERT_TRUE(Initialize()); |
| |
| EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo")); |
| QuicSpdyClientSession* client_session = GetClientSession(); |
| ASSERT_TRUE(client_session); |
| EXPECT_FALSE(client_session->EarlyDataAccepted()); |
| EXPECT_FALSE(client_session->ReceivedInchoateReject()); |
| EXPECT_FALSE(client_->client()->EarlyDataAccepted()); |
| EXPECT_FALSE(client_->client()->ReceivedInchoateReject()); |
| |
| client_->Disconnect(); |
| |
| // The 0-RTT handshake should succeed. |
| client_->Connect(); |
| EXPECT_TRUE(client_->client()->WaitForOneRttKeysAvailable()); |
| ASSERT_TRUE(client_->client()->connected()); |
| EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo")); |
| |
| client_session = GetClientSession(); |
| ASSERT_TRUE(client_session); |
| EXPECT_TRUE(client_session->EarlyDataAccepted()); |
| EXPECT_TRUE(client_->client()->EarlyDataAccepted()); |
| |
| client_->Disconnect(); |
| |
| // Restart the server so that the 0-RTT handshake will take 1 RTT. |
| StopServer(); |
| server_writer_ = new PacketDroppingTestWriter(); |
| StartServer(); |
| |
| client_->Connect(); |
| EXPECT_TRUE(client_->client()->WaitForOneRttKeysAvailable()); |
| ASSERT_TRUE(client_->client()->connected()); |
| EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo")); |
| |
| client_session = GetClientSession(); |
| ASSERT_TRUE(client_session); |
| EXPECT_FALSE(client_session->EarlyDataAccepted()); |
| EXPECT_FALSE(client_session->ReceivedInchoateReject()); |
| EXPECT_FALSE(client_->client()->EarlyDataAccepted()); |
| EXPECT_FALSE(client_->client()->ReceivedInchoateReject()); |
| |
| VerifyCleanConnection(false); |
| } |
| |
| TEST_P(EndToEndTest, LargePostSynchronousRequest) { |
| // Send a request and then disconnect. This prepares the client to attempt |
| // a 0-RTT handshake for the next request. |
| ASSERT_TRUE(Initialize()); |
| |
| std::string body(20480, 'a'); |
| Http2HeaderBlock headers; |
| headers[":method"] = "POST"; |
| headers[":path"] = "/foo"; |
| headers[":scheme"] = "https"; |
| headers[":authority"] = server_hostname_; |
| |
| EXPECT_EQ(kFooResponseBody, |
| client_->SendCustomSynchronousRequest(headers, body)); |
| QuicSpdyClientSession* client_session = GetClientSession(); |
| ASSERT_TRUE(client_session); |
| EXPECT_FALSE(client_session->EarlyDataAccepted()); |
| EXPECT_FALSE(client_session->ReceivedInchoateReject()); |
| EXPECT_FALSE(client_->client()->EarlyDataAccepted()); |
| EXPECT_FALSE(client_->client()->ReceivedInchoateReject()); |
| |
| client_->Disconnect(); |
| |
| // The 0-RTT handshake should succeed. |
| client_->Connect(); |
| EXPECT_TRUE(client_->client()->WaitForOneRttKeysAvailable()); |
| ASSERT_TRUE(client_->client()->connected()); |
| EXPECT_EQ(kFooResponseBody, |
| client_->SendCustomSynchronousRequest(headers, body)); |
| |
| client_session = GetClientSession(); |
| ASSERT_TRUE(client_session); |
| EXPECT_TRUE(client_session->EarlyDataAccepted()); |
| EXPECT_TRUE(client_->client()->EarlyDataAccepted()); |
| |
| client_->Disconnect(); |
| |
| // Restart the server so that the 0-RTT handshake will take 1 RTT. |
| StopServer(); |
| server_writer_ = new PacketDroppingTestWriter(); |
| StartServer(); |
| |
| client_->Connect(); |
| EXPECT_TRUE(client_->client()->WaitForOneRttKeysAvailable()); |
| ASSERT_TRUE(client_->client()->connected()); |
| EXPECT_EQ(kFooResponseBody, |
| client_->SendCustomSynchronousRequest(headers, body)); |
| |
| client_session = GetClientSession(); |
| ASSERT_TRUE(client_session); |
| EXPECT_FALSE(client_session->EarlyDataAccepted()); |
| EXPECT_FALSE(client_session->ReceivedInchoateReject()); |
| EXPECT_FALSE(client_->client()->EarlyDataAccepted()); |
| EXPECT_FALSE(client_->client()->ReceivedInchoateReject()); |
| |
| VerifyCleanConnection(false); |
| } |
| |
| TEST_P(EndToEndTest, DisableResumption) { |
| client_extra_copts_.push_back(kNRES); |
| ASSERT_TRUE(Initialize()); |
| if (!version_.UsesTls()) { |
| return; |
| } |
| SendSynchronousFooRequestAndCheckResponse(); |
| QuicSpdyClientSession* client_session = GetClientSession(); |
| ASSERT_TRUE(client_session); |
| EXPECT_EQ(client_session->GetCryptoStream()->EarlyDataReason(), |
| ssl_early_data_no_session_offered); |
| client_->Disconnect(); |
| |
| SendSynchronousFooRequestAndCheckResponse(); |
| client_session = GetClientSession(); |
| ASSERT_TRUE(client_session); |
| if (GetQuicReloadableFlag(quic_enable_disable_resumption)) { |
| EXPECT_EQ(client_session->GetCryptoStream()->EarlyDataReason(), |
| ssl_early_data_session_not_resumed); |
| } else { |
| EXPECT_EQ(client_session->GetCryptoStream()->EarlyDataReason(), |
| ssl_early_data_accepted); |
| } |
| } |
| |
| // This is a regression test for b/162595387 |
| TEST_P(EndToEndTest, PostZeroRTTRequestDuringHandshake) { |
| if (!version_.UsesTls()) { |
| // This test is TLS specific. |
| ASSERT_TRUE(Initialize()); |
| return; |
| } |
| // Send a request and then disconnect. This prepares the client to attempt |
| // a 0-RTT handshake for the next request. |
| NiceMock<MockQuicConnectionDebugVisitor> visitor; |
| connection_debug_visitor_ = &visitor; |
| ASSERT_TRUE(Initialize()); |
| |
| SendSynchronousFooRequestAndCheckResponse(); |
| QuicSpdyClientSession* client_session = GetClientSession(); |
| ASSERT_TRUE(client_session); |
| EXPECT_FALSE(client_session->EarlyDataAccepted()); |
| EXPECT_FALSE(client_session->ReceivedInchoateReject()); |
| EXPECT_FALSE(client_->client()->EarlyDataAccepted()); |
| EXPECT_FALSE(client_->client()->ReceivedInchoateReject()); |
| |
| client_->Disconnect(); |
| |
| // The 0-RTT handshake should succeed. |
| ON_CALL(visitor, OnCryptoFrame(_)) |
| .WillByDefault(Invoke([this](const QuicCryptoFrame& frame) { |
| if (frame.level != ENCRYPTION_HANDSHAKE) { |
| return; |
| } |
| // At this point in the handshake, the client should have derived |
| // ENCRYPTION_ZERO_RTT keys (thus set encryption_established). It |
| // should also have set ENCRYPTION_HANDSHAKE keys after receiving |
| // the server's ENCRYPTION_INITIAL flight. |
| EXPECT_TRUE( |
| GetClientSession()->GetCryptoStream()->encryption_established()); |
| EXPECT_TRUE( |
| GetClientConnection()->framer().HasEncrypterOfEncryptionLevel( |
| ENCRYPTION_HANDSHAKE)); |
| Http2HeaderBlock headers; |
| headers[":method"] = "POST"; |
| headers[":path"] = "/foo"; |
| headers[":scheme"] = "https"; |
| headers[":authority"] = server_hostname_; |
| EXPECT_GT( |
| client_->SendMessage(headers, "", /*fin*/ true, /*flush*/ false), |
| 0); |
| })); |
| client_->Connect(); |
| ASSERT_TRUE(client_->client()->WaitForOneRttKeysAvailable()); |
| client_->WaitForWriteToFlush(); |
| client_->WaitForResponse(); |
| ASSERT_TRUE(client_->client()->connected()); |
| EXPECT_EQ(kFooResponseBody, client_->response_body()); |
| |
| client_session = GetClientSession(); |
| ASSERT_TRUE(client_session); |
| EXPECT_TRUE(client_session->EarlyDataAccepted()); |
| EXPECT_TRUE(client_->client()->EarlyDataAccepted()); |
| } |
| |
| // Regression test for b/166836136. |
| TEST_P(EndToEndTest, RetransmissionAfterZeroRTTRejectBeforeOneRtt) { |
| if (!version_.UsesTls()) { |
| // This test is TLS specific. |
| ASSERT_TRUE(Initialize()); |
| return; |
| } |
| // Send a request and then disconnect. This prepares the client to attempt |
| // a 0-RTT handshake for the next request. |
| NiceMock<MockQuicConnectionDebugVisitor> visitor; |
| connection_debug_visitor_ = &visitor; |
| ASSERT_TRUE(Initialize()); |
| |
| SendSynchronousFooRequestAndCheckResponse(); |
| QuicSpdyClientSession* client_session = GetClientSession(); |
| ASSERT_TRUE(client_session); |
| EXPECT_FALSE(client_session->EarlyDataAccepted()); |
| EXPECT_FALSE(client_session->ReceivedInchoateReject()); |
| EXPECT_FALSE(client_->client()->EarlyDataAccepted()); |
| EXPECT_FALSE(client_->client()->ReceivedInchoateReject()); |
| |
| client_->Disconnect(); |
| |
| client_->Connect(); |
| EXPECT_TRUE(client_->client()->WaitForOneRttKeysAvailable()); |
| ASSERT_TRUE(client_->client()->connected()); |
| EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo")); |
| |
| client_session = GetClientSession(); |
| ASSERT_TRUE(client_session); |
| EXPECT_TRUE(client_session->EarlyDataAccepted()); |
| EXPECT_TRUE(client_->client()->EarlyDataAccepted()); |
| |
| client_->Disconnect(); |
| |
| // Restart the server so that the 0-RTT handshake will take 1 RTT. |
| StopServer(); |
| server_writer_ = new PacketDroppingTestWriter(); |
| StartServer(); |
| |
| ON_CALL(visitor, OnZeroRttRejected(_)).WillByDefault(Invoke([this]() { |
| EXPECT_FALSE(GetClientSession()->IsEncryptionEstablished()); |
| })); |
| |
| // The 0-RTT handshake should fail. |
| client_->Connect(); |
| ASSERT_TRUE(client_->client()->WaitForOneRttKeysAvailable()); |
| client_->WaitForWriteToFlush(); |
| client_->WaitForResponse(); |
| ASSERT_TRUE(client_->client()->connected()); |
| |
| client_session = GetClientSession(); |
| ASSERT_TRUE(client_session); |
| EXPECT_FALSE(client_session->EarlyDataAccepted()); |
| EXPECT_FALSE(client_->client()->EarlyDataAccepted()); |
| } |
| |
| TEST_P(EndToEndTest, RejectWithPacketLoss) { |
| // In this test, we intentionally drop the first packet from the |
| // server, which corresponds with the initial REJ response from |
| // the server. |
| server_writer_->set_fake_drop_first_n_packets(1); |
| ASSERT_TRUE(Initialize()); |
| } |
| |
| TEST_P(EndToEndTest, SetInitialReceivedConnectionOptions) { |
| QuicTagVector initial_received_options; |
| initial_received_options.push_back(kTBBR); |
| initial_received_options.push_back(kIW10); |
| initial_received_options.push_back(kPRST); |
| EXPECT_TRUE(server_config_.SetInitialReceivedConnectionOptions( |
| initial_received_options)); |
| |
| ASSERT_TRUE(Initialize()); |
| EXPECT_TRUE(client_->client()->WaitForOneRttKeysAvailable()); |
| server_thread_->WaitForCryptoHandshakeConfirmed(); |
| |
| EXPECT_FALSE(server_config_.SetInitialReceivedConnectionOptions( |
| initial_received_options)); |
| |
| // Verify that server's configuration is correct. |
| server_thread_->Pause(); |
| EXPECT_TRUE(server_config_.HasReceivedConnectionOptions()); |
| EXPECT_TRUE( |
| ContainsQuicTag(server_config_.ReceivedConnectionOptions(), kTBBR)); |
| EXPECT_TRUE( |
| ContainsQuicTag(server_config_.ReceivedConnectionOptions(), kIW10)); |
| EXPECT_TRUE( |
| ContainsQuicTag(server_config_.ReceivedConnectionOptions(), kPRST)); |
| } |
| |
| TEST_P(EndToEndTest, LargePostSmallBandwidthLargeBuffer) { |
| ASSERT_TRUE(Initialize()); |
| SetPacketSendDelay(QuicTime::Delta::FromMicroseconds(1)); |
| // 256KB per second with a 256KB buffer from server to client. Wireless |
| // clients commonly have larger buffers, but our max CWND is 200. |
| server_writer_->set_max_bandwidth_and_buffer_size( |
| QuicBandwidth::FromBytesPerSecond(256 * 1024), 256 * 1024); |
| |
| EXPECT_TRUE(client_->client()->WaitForOneRttKeysAvailable()); |
| |
| // 1 MB body. |
| std::string body(1024 * 1024, 'a'); |
| Http2HeaderBlock headers; |
| headers[":method"] = "POST"; |
| headers[":path"] = "/foo"; |
| headers[":scheme"] = "https"; |
| headers[":authority"] = server_hostname_; |
| |
| EXPECT_EQ(kFooResponseBody, |
| client_->SendCustomSynchronousRequest(headers, body)); |
| // This connection may drop packets, because the buffer is smaller than the |
| // max CWND. |
| VerifyCleanConnection(true); |
| } |
| |
| TEST_P(EndToEndTest, DoNotSetSendAlarmIfConnectionFlowControlBlocked) { |
| // Regression test for b/14677858. |
| // Test that the resume write alarm is not set in QuicConnection::OnCanWrite |
| // if currently connection level flow control blocked. If set, this results in |
| // an infinite loop in the EventLoop, as the alarm fires and is immediately |
| // rescheduled. |
| ASSERT_TRUE(Initialize()); |
| EXPECT_TRUE(client_->client()->WaitForOneRttKeysAvailable()); |
| |
| // Ensure both stream and connection level are flow control blocked by setting |
| // the send window offset to 0. |
| const uint64_t flow_control_window = |
| server_config_.GetInitialStreamFlowControlWindowToSend(); |
| QuicSpdyClientStream* stream = client_->GetOrCreateStream(); |
| QuicSession* session = GetClientSession(); |
| ASSERT_TRUE(session); |
| QuicStreamPeer::SetSendWindowOffset(stream, 0); |
| QuicFlowControllerPeer::SetSendWindowOffset(session->flow_controller(), 0); |
| EXPECT_TRUE(stream->IsFlowControlBlocked()); |
| EXPECT_TRUE(session->flow_controller()->IsBlocked()); |
| |
| // Make sure that the stream has data pending so that it will be marked as |
| // write blocked when it receives a stream level WINDOW_UPDATE. |
| stream->WriteOrBufferBody("hello", false); |
| |
| // The stream now attempts to write, fails because it is still connection |
| // level flow control blocked, and is added to the write blocked list. |
| QuicWindowUpdateFrame window_update(kInvalidControlFrameId, stream->id(), |
| 2 * flow_control_window); |
| stream->OnWindowUpdateFrame(window_update); |
| |
| // Prior to fixing b/14677858 this call would result in an infinite loop in |
| // Chromium. As a proxy for detecting this, we now check whether the |
| // send alarm is set after OnCanWrite. It should not be, as the |
| // connection is still flow control blocked. |
| session->connection()->OnCanWrite(); |
| |
| QuicAlarm* send_alarm = |
| QuicConnectionPeer::GetSendAlarm(session->connection()); |
| EXPECT_FALSE(send_alarm->IsSet()); |
| } |
| |
| TEST_P(EndToEndTest, InvalidStream) { |
| ASSERT_TRUE(Initialize()); |
| EXPECT_TRUE(client_->client()->WaitForOneRttKeysAvailable()); |
| |
| std::string body(kMaxOutgoingPacketSize, 'a'); |
| Http2HeaderBlock headers; |
| headers[":method"] = "POST"; |
| headers[":path"] = "/foo"; |
| headers[":scheme"] = "https"; |
| headers[":authority"] = server_hostname_; |
| |
| // Force the client to write with a stream ID belonging to a nonexistent |
| // server-side stream. |
| QuicSpdySession* session = GetClientSession(); |
| ASSERT_TRUE(session); |
| QuicSessionPeer::SetNextOutgoingBidirectionalStreamId( |
| session, GetNthServerInitiatedBidirectionalId(0)); |
| |
| client_->SendCustomSynchronousRequest(headers, body); |
| EXPECT_THAT(client_->stream_error(), |
| IsStreamError(QUIC_STREAM_CONNECTION_ERROR)); |
| EXPECT_THAT(client_->connection_error(), IsError(QUIC_INVALID_STREAM_ID)); |
| } |
| |
| // Test that the server resets the stream if the client sends a request |
| // with overly large headers. |
| TEST_P(EndToEndTest, LargeHeaders) { |
| ASSERT_TRUE(Initialize()); |
| EXPECT_TRUE(client_->client()->WaitForOneRttKeysAvailable()); |
| |
| std::string body(kMaxOutgoingPacketSize, 'a'); |
| Http2HeaderBlock headers; |
| headers[":method"] = "POST"; |
| headers[":path"] = "/foo"; |
| headers[":scheme"] = "https"; |
| headers[":authority"] = server_hostname_; |
| headers["key1"] = std::string(15 * 1024, 'a'); |
| headers["key2"] = std::string(15 * 1024, 'a'); |
| headers["key3"] = std::string(15 * 1024, 'a'); |
| |
| client_->SendCustomSynchronousRequest(headers, body); |
| |
| if (version_.UsesHttp3()) { |
| // QuicSpdyStream::OnHeadersTooLarge() resets the stream with |
| // QUIC_HEADERS_TOO_LARGE. This is sent as H3_EXCESSIVE_LOAD, the closest |
| // HTTP/3 error code, and translated back to QUIC_STREAM_EXCESSIVE_LOAD on |
| // the receiving side. |
| EXPECT_THAT(client_->stream_error(), |
| IsStreamError(QUIC_STREAM_EXCESSIVE_LOAD)); |
| } else { |
| EXPECT_THAT(client_->stream_error(), IsStreamError(QUIC_HEADERS_TOO_LARGE)); |
| } |
| EXPECT_THAT(client_->connection_error(), IsQuicNoError()); |
| } |
| |
| TEST_P(EndToEndTest, EarlyResponseWithQuicStreamNoError) { |
| ASSERT_TRUE(Initialize()); |
| EXPECT_TRUE(client_->client()->WaitForOneRttKeysAvailable()); |
| |
| std::string large_body(1024 * 1024, 'a'); |
| Http2HeaderBlock headers; |
| headers[":method"] = "POST"; |
| headers[":path"] = "/foo"; |
| headers[":scheme"] = "https"; |
| headers[":authority"] = server_hostname_; |
| // Insert an invalid content_length field in request to trigger an early |
| // response from server. |
| headers["content-length"] = "-3"; |
| |
| client_->SendCustomSynchronousRequest(headers, large_body); |
| EXPECT_EQ("bad", client_->response_body()); |
| CheckResponseHeaders("500"); |
| EXPECT_THAT(client_->stream_error(), IsQuicStreamNoError()); |
| EXPECT_THAT(client_->connection_error(), IsQuicNoError()); |
| } |
| |
| // TODO(rch): this test seems to cause net_unittests timeouts :| |
| TEST_P(EndToEndTest, QUIC_TEST_DISABLED_IN_CHROME(MultipleTermination)) { |
| ASSERT_TRUE(Initialize()); |
| |
| // Set the offset so we won't frame. Otherwise when we pick up termination |
| // before HTTP framing is complete, we send an error and close the stream, |
| // and the second write is picked up as writing on a closed stream. |
| QuicSpdyClientStream* stream = client_->GetOrCreateStream(); |
| ASSERT_TRUE(stream != nullptr); |
| QuicStreamPeer::SetStreamBytesWritten(3, stream); |
| |
| client_->SendData("bar", true); |
| client_->WaitForWriteToFlush(); |
| |
| // By default the stream protects itself from writes after terminte is set. |
| // Override this to test the server handling buggy clients. |
| QuicStreamPeer::SetWriteSideClosed(false, client_->GetOrCreateStream()); |
| |
| EXPECT_QUIC_BUG(client_->SendData("eep", true), "Fin already buffered"); |
| } |
| |
| TEST_P(EndToEndTest, Timeout) { |
| client_config_.SetIdleNetworkTimeout(QuicTime::Delta::FromMicroseconds(500)); |
| // Note: we do NOT ASSERT_TRUE: we may time out during initial handshake: |
| // that's enough to validate timeout in this case. |
| Initialize(); |
| while (client_->client()->connected()) { |
| client_->client()->WaitForEvents(); |
| } |
| } |
| |
| TEST_P(EndToEndTest, MaxDynamicStreamsLimitRespected) { |
| // Set a limit on maximum number of incoming dynamic streams. |
| // Make sure the limit is respected by the peer. |
| const uint32_t kServerMaxDynamicStreams = 1; |
| server_config_.SetMaxBidirectionalStreamsToSend(kServerMaxDynamicStreams); |
| ASSERT_TRUE(Initialize()); |
| if (version_.HasIetfQuicFrames()) { |
| // Do not run this test for /IETF QUIC. This test relies on the fact that |
| // Google QUIC allows a small number of additional streams beyond the |
| // negotiated limit, which is not supported in IETF QUIC. Note that the test |
| // needs to be here, after calling Initialize(), because all tests end up |
| // calling EndToEndTest::TearDown(), which asserts that Initialize has been |
| // called and then proceeds to tear things down -- which fails if they are |
| // not properly set up. |
| return; |
| } |
| EXPECT_TRUE(client_->client()->WaitForOneRttKeysAvailable()); |
| |
| // Make the client misbehave after negotiation. |
| const int kServerMaxStreams = kMaxStreamsMinimumIncrement + 1; |
| QuicSpdyClientSession* client_session = GetClientSession(); |
| ASSERT_TRUE(client_session); |
| QuicSessionPeer::SetMaxOpenOutgoingStreams(client_session, |
| kServerMaxStreams + 1); |
| |
| Http2HeaderBlock headers; |
| headers[":method"] = "POST"; |
| headers[":path"] = "/foo"; |
| headers[":scheme"] = "https"; |
| headers[":authority"] = server_hostname_; |
| headers["content-length"] = "3"; |
| |
| // The server supports a small number of additional streams beyond the |
| // negotiated limit. Open enough streams to go beyond that limit. |
| for (int i = 0; i < kServerMaxStreams + 1; ++i) { |
| client_->SendMessage(headers, "", /*fin=*/false); |
| } |
| client_->WaitForResponse(); |
| |
| EXPECT_TRUE(client_->connected()); |
| EXPECT_THAT(client_->stream_error(), IsStreamError(QUIC_REFUSED_STREAM)); |
| EXPECT_THAT(client_->connection_error(), IsQuicNoError()); |
| } |
| |
| TEST_P(EndToEndTest, SetIndependentMaxDynamicStreamsLimits) { |
| // Each endpoint can set max dynamic streams independently. |
| const uint32_t kClientMaxDynamicStreams = 4; |
| const uint32_t kServerMaxDynamicStreams = 3; |
| client_config_.SetMaxBidirectionalStreamsToSend(kClientMaxDynamicStreams); |
| server_config_.SetMaxBidirectionalStreamsToSend(kServerMaxDynamicStreams); |
| client_config_.SetMaxUnidirectionalStreamsToSend(kClientMaxDynamicStreams); |
| server_config_.SetMaxUnidirectionalStreamsToSend(kServerMaxDynamicStreams); |
| |
| ASSERT_TRUE(Initialize()); |
| EXPECT_TRUE(client_->client()->WaitForOneRttKeysAvailable()); |
| |
| // The client has received the server's limit and vice versa. |
| QuicSpdyClientSession* client_session = GetClientSession(); |
| ASSERT_TRUE(client_session); |
| // The value returned by max_allowed... includes the Crypto and Header |
| // stream (created as a part of initialization). The config. values, |
| // above, are treated as "number of requests/responses" - that is, they do |
| // not include the static Crypto and Header streams. Reduce the value |
| // returned by max_allowed... by 2 to remove the static streams from the |
| // count. |
| size_t client_max_open_outgoing_bidirectional_streams = |
| version_.HasIetfQuicFrames() |
| ? QuicSessionPeer::ietf_streamid_manager(client_session) |
| ->max_outgoing_bidirectional_streams() |
| : QuicSessionPeer::GetStreamIdManager(client_session) |
| ->max_open_outgoing_streams(); |
| size_t client_max_open_outgoing_unidirectional_streams = |
| version_.HasIetfQuicFrames() |
| ? QuicSessionPeer::ietf_streamid_manager(client_session) |
| ->max_outgoing_unidirectional_streams() - |
| kHttp3StaticUnidirectionalStreamCount |
| : QuicSessionPeer::GetStreamIdManager(client_session) |
| ->max_open_outgoing_streams(); |
| EXPECT_EQ(kServerMaxDynamicStreams, |
| client_max_open_outgoing_bidirectional_streams); |
| EXPECT_EQ(kServerMaxDynamicStreams, |
| client_max_open_outgoing_unidirectional_streams); |
| server_thread_->Pause(); |
| QuicSession* server_session = GetServerSession(); |
| if (server_session != nullptr) { |
| size_t server_max_open_outgoing_bidirectional_streams = |
| version_.HasIetfQuicFrames() |
| ? QuicSessionPeer::ietf_streamid_manager(server_session) |
| ->max_outgoing_bidirectional_streams() |
| : QuicSessionPeer::GetStreamIdManager(server_session) |
| ->max_open_outgoing_streams(); |
| size_t server_max_open_outgoing_unidirectional_streams = |
| version_.HasIetfQuicFrames() |
| ? QuicSessionPeer::ietf_streamid_manager(server_session) |
| ->max_outgoing_unidirectional_streams() - |
| kHttp3StaticUnidirectionalStreamCount |
| : QuicSessionPeer::GetStreamIdManager(server_session) |
| ->max_open_outgoing_streams(); |
| EXPECT_EQ(kClientMaxDynamicStreams, |
| server_max_open_outgoing_bidirectional_streams); |
| EXPECT_EQ(kClientMaxDynamicStreams, |
| server_max_open_outgoing_unidirectional_streams); |
| } else { |
| ADD_FAILURE() << "Missing server session"; |
| } |
| server_thread_->Resume(); |
| } |
| |
| TEST_P(EndToEndTest, NegotiateCongestionControl) { |
| ASSERT_TRUE(Initialize()); |
| |
| EXPECT_TRUE(client_->client()->WaitForOneRttKeysAvailable()); |
| |
| CongestionControlType expected_congestion_control_type = kRenoBytes; |
| switch (GetParam().congestion_control_tag) { |
| case kRENO: |
| expected_congestion_control_type = kRenoBytes; |
| break; |
| case kTBBR: |
| expected_congestion_control_type = kBBR; |
| break; |
| case kQBIC: |
| expected_congestion_control_type = kCubicBytes; |
| break; |
| case kB2ON: |
| expected_congestion_control_type = kBBRv2; |
| break; |
| default: |
| QUIC_DLOG(FATAL) << "Unexpected congestion control tag"; |
| } |
| |
| server_thread_->Pause(); |
| const QuicSentPacketManager* server_sent_packet_manager = |
| GetSentPacketManagerFromFirstServerSession(); |
| if (server_sent_packet_manager != nullptr) { |
| EXPECT_EQ( |
| expected_congestion_control_type, |
| QuicSentPacketManagerPeer::GetSendAlgorithm(*server_sent_packet_manager) |
| ->GetCongestionControlType()); |
| } else { |
| ADD_FAILURE() << "Missing server sent packet manager"; |
| } |
| server_thread_->Resume(); |
| } |
| |
| TEST_P(EndToEndTest, ClientSuggestsRTT) { |
| // Client suggests initial RTT, verify it is used. |
| const QuicTime::Delta kInitialRTT = QuicTime::Delta::FromMicroseconds(20000); |
| client_config_.SetInitialRoundTripTimeUsToSend(kInitialRTT.ToMicroseconds()); |
| |
| ASSERT_TRUE(Initialize()); |
| EXPECT_TRUE(client_->client()->WaitForOneRttKeysAvailable()); |
| ASSERT_TRUE(server_thread_); |
| server_thread_->WaitForCryptoHandshakeConfirmed(); |
| |
| // Pause the server so we can access the server's internals without races. |
| server_thread_->Pause(); |
| const QuicSentPacketManager* client_sent_packet_manager = |
| GetSentPacketManagerFromClientSession(); |
| const QuicSentPacketManager* server_sent_packet_manager = |
| GetSentPacketManagerFromFirstServerSession(); |
| if (client_sent_packet_manager != nullptr && |
| server_sent_packet_manager != nullptr) { |
| EXPECT_EQ(kInitialRTT, |
| client_sent_packet_manager->GetRttStats()->initial_rtt()); |
| EXPECT_EQ(kInitialRTT, |
| server_sent_packet_manager->GetRttStats()->initial_rtt()); |
| } else { |
| ADD_FAILURE() << "Missing sent packet manager"; |
| } |
| server_thread_->Resume(); |
| } |
| |
| TEST_P(EndToEndTest, ClientSuggestsIgnoredRTT) { |
| // Client suggests initial RTT, but also specifies NRTT, so it's not used. |
| const QuicTime::Delta kInitialRTT = QuicTime::Delta::FromMicroseconds(20000); |
| client_config_.SetInitialRoundTripTimeUsToSend(kInitialRTT.ToMicroseconds()); |
| QuicTagVector options; |
| options.push_back(kNRTT); |
| client_config_.SetConnectionOptionsToSend(options); |
| |
| ASSERT_TRUE(Initialize()); |
| EXPECT_TRUE(client_->client()->WaitForOneRttKeysAvailable()); |
| ASSERT_TRUE(server_thread_); |
| server_thread_->WaitForCryptoHandshakeConfirmed(); |
| |
| // Pause the server so we can access the server's internals without races. |
| server_thread_->Pause(); |
| const QuicSentPacketManager* client_sent_packet_manager = |
| GetSentPacketManagerFromClientSession(); |
| const QuicSentPacketManager* server_sent_packet_manager = |
| GetSentPacketManagerFromFirstServerSession(); |
| if (client_sent_packet_manager != nullptr && |
| server_sent_packet_manager != nullptr) { |
| EXPECT_EQ(kInitialRTT, |
| client_sent_packet_manager->GetRttStats()->initial_rtt()); |
| EXPECT_EQ(kInitialRTT, |
| server_sent_packet_manager->GetRttStats()->initial_rtt()); |
| } else { |
| ADD_FAILURE() << "Missing sent packet manager"; |
| } |
| server_thread_->Resume(); |
| } |
| |
| // Regression test for b/171378845 |
| TEST_P(EndToEndTest, ClientDisablesGQuicZeroRtt) { |
| if (version_.UsesTls()) { |
| // This feature is gQUIC only. |
| ASSERT_TRUE(Initialize()); |
| return; |
| } |
| QuicTagVector options; |
| options.push_back(kQNZ2); |
| client_config_.SetClientConnectionOptions(options); |
| |
| ASSERT_TRUE(Initialize()); |
| |
| EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo")); |
| QuicSpdyClientSession* client_session = GetClientSession(); |
| ASSERT_TRUE(client_session); |
| EXPECT_FALSE(client_session->EarlyDataAccepted()); |
| EXPECT_FALSE(client_session->ReceivedInchoateReject()); |
| EXPECT_FALSE(client_->client()->EarlyDataAccepted()); |
| EXPECT_FALSE(client_->client()->ReceivedInchoateReject()); |
| |
| client_->Disconnect(); |
| |
| // Make sure that the request succeeds but 0-RTT was not used. |
| client_->Connect(); |
| EXPECT_TRUE(client_->client()->WaitForOneRttKeysAvailable()); |
| ASSERT_TRUE(client_->client()->connected()); |
| EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo")); |
| |
| client_session = GetClientSession(); |
| ASSERT_TRUE(client_session); |
| EXPECT_FALSE(client_session->EarlyDataAccepted()); |
| EXPECT_FALSE(client_->client()->EarlyDataAccepted()); |
| } |
| |
| TEST_P(EndToEndTest, MaxInitialRTT) { |
| // Client tries to suggest twice the server's max initial rtt and the server |
| // uses the max. |
| client_config_.SetInitialRoundTripTimeUsToSend(2 * |
| kMaxInitialRoundTripTimeUs); |
| |
| ASSERT_TRUE(Initialize()); |
| EXPECT_TRUE(client_->client()->WaitForOneRttKeysAvailable()); |
| ASSERT_TRUE(server_thread_); |
| server_thread_->WaitForCryptoHandshakeConfirmed(); |
| |
| // Pause the server so we can access the server's internals without races. |
| server_thread_->Pause(); |
| const QuicSentPacketManager* client_sent_packet_manager = |
| GetSentPacketManagerFromClientSession(); |
| const QuicSentPacketManager* server_sent_packet_manager = |
| GetSentPacketManagerFromFirstServerSession(); |
| if (client_sent_packet_manager != nullptr && |
| server_sent_packet_manager != nullptr) { |
| // Now that acks have been exchanged, the RTT estimate has decreased on the |
| // server and is not infinite on the client. |
| EXPECT_FALSE( |
| client_sent_packet_manager->GetRttStats()->smoothed_rtt().IsInfinite()); |
| const RttStats* server_rtt_stats = |
| server_sent_packet_manager->GetRttStats(); |
| EXPECT_EQ(static_cast<int64_t>(kMaxInitialRoundTripTimeUs), |
| server_rtt_stats->initial_rtt().ToMicroseconds()); |
| EXPECT_GE(static_cast<int64_t>(kMaxInitialRoundTripTimeUs), |
| server_rtt_stats->smoothed_rtt().ToMicroseconds()); |
| } else { |
| ADD_FAILURE() << "Missing sent packet manager"; |
| } |
| server_thread_->Resume(); |
| } |
| |
| TEST_P(EndToEndTest, MinInitialRTT) { |
| // Client tries to suggest 0 and the server uses the default. |
| client_config_.SetInitialRoundTripTimeUsToSend(0); |
| |
| ASSERT_TRUE(Initialize()); |
| EXPECT_TRUE(client_->client()->WaitForOneRttKeysAvailable()); |
| server_thread_->WaitForCryptoHandshakeConfirmed(); |
| |
| // Pause the server so we can access the server's internals without races. |
| server_thread_->Pause(); |
| const QuicSentPacketManager* client_sent_packet_manager = |
| GetSentPacketManagerFromClientSession(); |
| const QuicSentPacketManager* server_sent_packet_manager = |
| GetSentPacketManagerFromFirstServerSession(); |
| if (client_sent_packet_manager != nullptr && |
| server_sent_packet_manager != nullptr) { |
| // Now that acks have been exchanged, the RTT estimate has decreased on the |
| // server and is not infinite on the client. |
| EXPECT_FALSE( |
| client_sent_packet_manager->GetRttStats()->smoothed_rtt().IsInfinite()); |
| // Expect the default rtt of 100ms. |
| EXPECT_EQ(QuicTime::Delta::FromMilliseconds(100), |
| server_sent_packet_manager->GetRttStats()->initial_rtt()); |
| // Ensure the bandwidth is valid. |
| client_sent_packet_manager->BandwidthEstimate(); |
| server_sent_packet_manager->BandwidthEstimate(); |
| } else { |
| ADD_FAILURE() << "Missing sent packet manager"; |
| } |
| server_thread_->Resume(); |
| } |
| |
| TEST_P(EndToEndTest, 0ByteConnectionId) { |
| if (version_.HasIetfInvariantHeader()) { |
| // SetBytesForConnectionIdToSend only applies to Google QUIC encoding. |
| ASSERT_TRUE(Initialize()); |
| return; |
| } |
| client_config_.SetBytesForConnectionIdToSend(0); |
| ASSERT_TRUE(Initialize()); |
| |
| SendSynchronousFooRequestAndCheckResponse(); |
| QuicConnection* client_connection = GetClientConnection(); |
| ASSERT_TRUE(client_connection); |
| QuicPacketHeader* header = |
| QuicConnectionPeer::GetLastHeader(client_connection); |
| EXPECT_EQ(CONNECTION_ID_ABSENT, header->source_connection_id_included); |
| } |
| |
| TEST_P(EndToEndTest, 8ByteConnectionId) { |
| if (version_.HasIetfInvariantHeader()) { |
| // SetBytesForConnectionIdToSend only applies to Google QUIC encoding. |
| ASSERT_TRUE(Initialize()); |
| return; |
| } |
| client_config_.SetBytesForConnectionIdToSend(8); |
| ASSERT_TRUE(Initialize()); |
| |
| SendSynchronousFooRequestAndCheckResponse(); |
| QuicConnection* client_connection = GetClientConnection(); |
| ASSERT_TRUE(client_connection); |
| QuicPacketHeader* header = |
| QuicConnectionPeer::GetLastHeader(client_connection); |
| EXPECT_EQ(CONNECTION_ID_PRESENT, header->destination_connection_id_included); |
| } |
| |
| TEST_P(EndToEndTest, 15ByteConnectionId) { |
| if (version_.HasIetfInvariantHeader()) { |
| // SetBytesForConnectionIdToSend only applies to Google QUIC encoding. |
| ASSERT_TRUE(Initialize()); |
| return; |
| } |
| client_config_.SetBytesForConnectionIdToSend(15); |
| ASSERT_TRUE(Initialize()); |
| |
| // Our server is permissive and allows for out of bounds values. |
| SendSynchronousFooRequestAndCheckResponse(); |
| QuicConnection* client_connection = GetClientConnection(); |
| ASSERT_TRUE(client_connection); |
| QuicPacketHeader* header = |
| QuicConnectionPeer::GetLastHeader(client_connection); |
| EXPECT_EQ(CONNECTION_ID_PRESENT, header->destination_connection_id_included); |
| } |
| |
| TEST_P(EndToEndTest, ResetConnection) { |
| ASSERT_TRUE(Initialize()); |
| |
| SendSynchronousFooRequestAndCheckResponse(); |
| client_->ResetConnection(); |
| EXPECT_TRUE(client_->client()->WaitForOneRttKeysAvailable()); |
| SendSynchronousBarRequestAndCheckResponse(); |
| } |
| |
| // Regression test for b/180737158. |
| TEST_P( |
| EndToEndTest, |
| HalfRttResponseBlocksShloRetransmissionWithoutTokenBasedAddressValidation) { |
| // Turn off token based address validation to make the server get constrained |
| // by amplification factor during handshake. |
| SetQuicFlag(quic_reject_retry_token_in_initial_packet, true); |
| ASSERT_TRUE(Initialize()); |
| if (!version_.SupportsAntiAmplificationLimit()) { |
| return; |
| } |
| // Perform a full 1-RTT handshake to get the new session ticket such that the |
| // next connection will perform a 0-RTT handshake. |
| EXPECT_TRUE(client_->client()->WaitForHandshakeConfirmed()); |
| client_->Disconnect(); |
| |
| server_thread_->Pause(); |
| // Drop the 1st server packet which is the coalesced INITIAL + HANDSHAKE + |
| // 1RTT. |
| PacketDroppingTestWriter* writer = new PacketDroppingTestWriter(); |
| writer->set_fake_drop_first_n_packets(1); |
| QuicDispatcherPeer::UseWriter( |
| QuicServerPeer::GetDispatcher(server_thread_->server()), writer); |
| server_thread_->Resume(); |
| |
| // Large response (100KB) for 0-RTT request. |
| std::string large_body(102400, 'a'); |
| AddToCache("/large_response", 200, large_body); |
| SendSynchronousRequestAndCheckResponse(client_.get(), "/large_response", |
| large_body); |
| } |
| |
| TEST_P(EndToEndTest, MaxStreamsUberTest) { |
| // Connect with lower fake packet loss than we'd like to test. Until |
| // b/10126687 is fixed, losing handshake packets is pretty brutal. |
| SetPacketLossPercentage(1); |
| ASSERT_TRUE(Initialize()); |
| std::string large_body(10240, 'a'); |
| int max_streams = 100; |
| |
| AddToCache("/large_response", 200, large_body); |
| |
| EXPECT_TRUE(client_->client()->WaitForOneRttKeysAvailable()); |
| SetPacketLossPercentage(10); |
| |
| for (int i = 0; i < max_streams; ++i) { |
| EXPECT_LT(0, client_->SendRequest("/large_response")); |
| } |
| |
| // WaitForEvents waits 50ms and returns true if there are outstanding |
| // requests. |
| while (client_->client()->WaitForEvents()) { |
| ASSERT_TRUE(client_->connected()); |
| } |
| } |
| |
| TEST_P(EndToEndTest, StreamCancelErrorTest) { |
| ASSERT_TRUE(Initialize()); |
| std::string small_body(256, 'a'); |
| |
| AddToCache("/small_response", 200, small_body); |
| |
| EXPECT_TRUE(client_->client()->WaitForOneRttKeysAvailable()); |
| |
| QuicSession* session = GetClientSession(); |
| ASSERT_TRUE(session); |
| // Lose the request. |
| SetPacketLossPercentage(100); |
| EXPECT_LT(0, client_->SendRequest("/small_response")); |
| client_->client()->WaitForEvents(); |
| // Transmit the cancel, and ensure the connection is torn down properly. |
| SetPacketLossPercentage(0); |
| QuicStreamId stream_id = GetNthClientInitiatedBidirectionalId(0); |
| QuicConnection* client_connection = GetClientConnection(); |
| ASSERT_TRUE(client_connection); |
| const QuicPacketCount packets_sent_before = |
| client_connection->GetStats().packets_sent; |
| session->ResetStream(stream_id, QUIC_STREAM_CANCELLED); |
| const QuicPacketCount packets_sent_now = |
| client_connection->GetStats().packets_sent; |
| |
| if (version_.UsesHttp3()) { |
| // Make sure 2 packets were sent, one for QPACK instructions, another for |
| // RESET_STREAM and STOP_SENDING. |
| EXPECT_EQ(packets_sent_before + 2, packets_sent_now); |
| } |
| |
| // WaitForEvents waits 50ms and returns true if there are outstanding |
| // requests. |
| while (client_->client()->WaitForEvents()) { |
| ASSERT_TRUE(client_->connected()); |
| } |
| // It should be completely fine to RST a stream before any data has been |
| // received for that stream. |
| EXPECT_THAT(client_->connection_error(), IsQuicNoError()); |
| } |
| |
| TEST_P(EndToEndTest, ConnectionMigrationClientIPChanged) { |
| ASSERT_TRUE(Initialize()); |
| if (GetQuicFlag(quic_enforce_strict_amplification_factor)) { |
| return; |
| } |
| SendSynchronousFooRequestAndCheckResponse(); |
| |
| // Store the client IP address which was used to send the first request. |
| QuicIpAddress old_host = |
| client_->client()->network_helper()->GetLatestClientAddress().host(); |
| |
| // Migrate socket to the new IP address. |
| QuicIpAddress new_host = TestLoopback(2); |
| EXPECT_NE(old_host, new_host); |
| ASSERT_TRUE(client_->client()->MigrateSocket(new_host)); |
| |
| // Send a request using the new socket. |
| SendSynchronousBarRequestAndCheckResponse(); |
| |
| if (!version_.HasIetfQuicFrames() || |
| !client_->client()->session()->connection()->validate_client_address()) { |
| return; |
| } |
| QuicConnection* client_connection = GetClientConnection(); |
| ASSERT_TRUE(client_connection); |
| EXPECT_EQ(1u, |
| client_connection->GetStats().num_connectivity_probing_received); |
| |
| // Send another request. |
| SendSynchronousBarRequestAndCheckResponse(); |
| // By the time the 2nd request is completed, the PATH_RESPONSE must have been |
| // received by the server. |
| server_thread_->Pause(); |
| QuicConnection* server_connection = GetServerConnection(); |
| if (server_connection != nullptr) { |
| EXPECT_FALSE(server_connection->HasPendingPathValidation()); |
| EXPECT_EQ(1u, server_connection->GetStats().num_validated_peer_migration); |
| } else { |
| ADD_FAILURE() << "Missing server connection"; |
| } |
| server_thread_->Resume(); |
| } |
| |
| TEST_P(EndToEndTest, IetfConnectionMigrationClientIPChangedMultipleTimes) { |
| ASSERT_TRUE(Initialize()); |
| if (!GetClientConnection()->connection_migration_use_new_cid() || |
| GetQuicFlag(quic_enforce_strict_amplification_factor)) { |
| return; |
| } |
| SendSynchronousFooRequestAndCheckResponse(); |
| |
| // Store the client IP address which was used to send the first request. |
| QuicIpAddress host0 = |
| client_->client()->network_helper()->GetLatestClientAddress().host(); |
| QuicConnection* client_connection = GetClientConnection(); |
| ASSERT_TRUE(client_connection != nullptr); |
| |
| // Migrate socket to a new IP address. |
| QuicIpAddress host1 = TestLoopback(2); |
| EXPECT_NE(host0, host1); |
| ASSERT_TRUE( |
| QuicConnectionPeer::HasUnusedPeerIssuedConnectionId(client_connection)); |
| QuicConnectionId server_cid0 = client_connection->connection_id(); |
| EXPECT_TRUE(QuicConnectionPeer::GetServerConnectionIdOnAlternativePath( |
| client_connection) |
| .IsEmpty()); |
| EXPECT_TRUE(client_->client()->MigrateSocket(host1)); |
| QuicConnectionId server_cid1 = client_connection->connection_id(); |
| EXPECT_FALSE(server_cid1.IsEmpty()); |
| EXPECT_NE(server_cid0, server_cid1); |
| EXPECT_TRUE(QuicConnectionPeer::GetServerConnectionIdOnAlternativePath( |
| client_connection) |
| .IsEmpty()); |
| |
| // Send a request using the new socket. |
| SendSynchronousBarRequestAndCheckResponse(); |
| EXPECT_EQ(1u, |
| client_connection->GetStats().num_connectivity_probing_received); |
| |
| // Send another request and wait for response making sure path response is |
| // received at server. |
| SendSynchronousBarRequestAndCheckResponse(); |
| |
| // Migrate socket to a new IP address. |
| WaitForNewConnectionIds(); |
| EXPECT_EQ(1u, client_connection->GetStats().num_retire_connection_id_sent); |
| QuicIpAddress host2 = TestLoopback(3); |
| EXPECT_NE(host0, host2); |
| EXPECT_NE(host1, host2); |
| EXPECT_TRUE(QuicConnectionPeer::GetServerConnectionIdOnAlternativePath( |
| client_connection) |
| .IsEmpty()); |
| EXPECT_TRUE(client_->client()->MigrateSocket(host2)); |
| QuicConnectionId server_cid2 = client_connection->connection_id(); |
| EXPECT_FALSE(server_cid2.IsEmpty()); |
| EXPECT_NE(server_cid0, server_cid2); |
| EXPECT_NE(server_cid1, server_cid2); |
| EXPECT_TRUE(QuicConnectionPeer::GetServerConnectionIdOnAlternativePath( |
| client_connection) |
| .IsEmpty()); |
| |
| // Send another request using the new socket and wait for response making sure |
| // path response is received at server. |
| SendSynchronousBarRequestAndCheckResponse(); |
| EXPECT_EQ(2u, |
| client_connection->GetStats().num_connectivity_probing_received); |
| |
| // Migrate socket back to an old IP address. |
| WaitForNewConnectionIds(); |
| EXPECT_EQ(2u, client_connection->GetStats().num_retire_connection_id_sent |