Project import generated by Copybara. PiperOrigin-RevId: 237361882 Change-Id: I109a68f44db867b20f8c6a7732b0ce657133e52a
diff --git a/quic/quartc/counting_packet_filter.h b/quic/quartc/counting_packet_filter.h new file mode 100644 index 0000000..0f67008 --- /dev/null +++ b/quic/quartc/counting_packet_filter.h
@@ -0,0 +1,42 @@ +// Copyright (c) 2018 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. + +#ifndef QUICHE_QUIC_QUARTC_COUNTING_PACKET_FILTER_H_ +#define QUICHE_QUIC_QUARTC_COUNTING_PACKET_FILTER_H_ + +#include "net/third_party/quiche/src/quic/platform/api/quic_string.h" +#include "net/third_party/quiche/src/quic/test_tools/simulator/packet_filter.h" +#include "net/third_party/quiche/src/quic/test_tools/simulator/port.h" +#include "net/third_party/quiche/src/quic/test_tools/simulator/simulator.h" + +namespace quic { +namespace simulator { + +// Simple packet filter which drops the first N packets it observes. +class CountingPacketFilter : public simulator::PacketFilter { + public: + CountingPacketFilter(simulator::Simulator* simulator, + const QuicString& name, + simulator::Endpoint* endpoint) + : PacketFilter(simulator, name, endpoint) {} + + void set_packets_to_drop(int count) { packets_to_drop_ = count; } + + protected: + bool FilterPacket(const simulator::Packet& packet) override { + if (packets_to_drop_ > 0) { + --packets_to_drop_; + return false; + } + return true; + } + + private: + int packets_to_drop_ = 0; +}; + +} // namespace simulator +} // namespace quic + +#endif // QUICHE_QUIC_QUARTC_COUNTING_PACKET_FILTER_H_
diff --git a/quic/quartc/quartc_connection_helper.cc b/quic/quartc/quartc_connection_helper.cc new file mode 100644 index 0000000..6c6ee97 --- /dev/null +++ b/quic/quartc/quartc_connection_helper.cc
@@ -0,0 +1,24 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/quic/quartc/quartc_connection_helper.h" + +namespace quic { + +QuartcConnectionHelper::QuartcConnectionHelper(const QuicClock* clock) + : clock_(clock) {} + +const QuicClock* QuartcConnectionHelper::GetClock() const { + return clock_; +} + +QuicRandom* QuartcConnectionHelper::GetRandomGenerator() { + return QuicRandom::GetInstance(); +} + +QuicBufferAllocator* QuartcConnectionHelper::GetStreamSendBufferAllocator() { + return &buffer_allocator_; +} + +} // namespace quic
diff --git a/quic/quartc/quartc_connection_helper.h b/quic/quartc/quartc_connection_helper.h new file mode 100644 index 0000000..2b1d62f --- /dev/null +++ b/quic/quartc/quartc_connection_helper.h
@@ -0,0 +1,33 @@ +// Copyright (c) 2017 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. + +#ifndef QUICHE_QUIC_QUARTC_QUARTC_CONNECTION_HELPER_H_ +#define QUICHE_QUIC_QUARTC_QUARTC_CONNECTION_HELPER_H_ + +#include "net/third_party/quiche/src/quic/core/crypto/quic_random.h" +#include "net/third_party/quiche/src/quic/core/quic_buffer_allocator.h" +#include "net/third_party/quiche/src/quic/core/quic_connection.h" +#include "net/third_party/quiche/src/quic/core/quic_simple_buffer_allocator.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_clock.h" + +namespace quic { + +// Simple implementation of QuicConnectionHelperInterface for Quartc. +class QuartcConnectionHelper : public QuicConnectionHelperInterface { + public: + explicit QuartcConnectionHelper(const QuicClock* clock); + + // QuicConnectionHelperInterface overrides. + const QuicClock* GetClock() const override; + QuicRandom* GetRandomGenerator() override; + QuicBufferAllocator* GetStreamSendBufferAllocator() override; + + private: + const QuicClock* clock_; + SimpleBufferAllocator buffer_allocator_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_QUARTC_QUARTC_CONNECTION_HELPER_H_
diff --git a/quic/quartc/quartc_crypto_helpers.cc b/quic/quartc/quartc_crypto_helpers.cc new file mode 100644 index 0000000..865fec4 --- /dev/null +++ b/quic/quartc/quartc_crypto_helpers.cc
@@ -0,0 +1,162 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/quic/quartc/quartc_crypto_helpers.h" +#include "net/third_party/quiche/src/quic/core/quic_utils.h" +#include "net/third_party/quiche/src/quic/core/tls_client_handshaker.h" +#include "net/third_party/quiche/src/quic/core/tls_server_handshaker.h" + +namespace quic { + +void DummyProofSource::GetProof(const QuicSocketAddress& server_address, + const QuicString& hostname, + const QuicString& server_config, + QuicTransportVersion transport_version, + QuicStringPiece chlo_hash, + std::unique_ptr<Callback> callback) { + QuicReferenceCountedPointer<ProofSource::Chain> chain = + GetCertChain(server_address, hostname); + QuicCryptoProof proof; + proof.signature = "Dummy signature"; + proof.leaf_cert_scts = "Dummy timestamp"; + callback->Run(true, chain, proof, nullptr /* details */); +} + +QuicReferenceCountedPointer<DummyProofSource::Chain> +DummyProofSource::GetCertChain(const QuicSocketAddress& server_address, + const QuicString& hostname) { + std::vector<QuicString> certs; + certs.push_back(kDummyCertName); + return QuicReferenceCountedPointer<ProofSource::Chain>( + new ProofSource::Chain(certs)); +} + +void DummyProofSource::ComputeTlsSignature( + const QuicSocketAddress& server_address, + const QuicString& hostname, + uint16_t signature_algorithm, + QuicStringPiece in, + std::unique_ptr<SignatureCallback> callback) { + callback->Run(true, "Dummy signature"); +} + +QuicAsyncStatus InsecureProofVerifier::VerifyProof( + const QuicString& hostname, + const uint16_t port, + const QuicString& server_config, + QuicTransportVersion transport_version, + QuicStringPiece chlo_hash, + const std::vector<QuicString>& certs, + const QuicString& cert_sct, + const QuicString& signature, + const ProofVerifyContext* context, + QuicString* error_details, + std::unique_ptr<ProofVerifyDetails>* verify_details, + std::unique_ptr<ProofVerifierCallback> callback) { + return QUIC_SUCCESS; +} + +QuicAsyncStatus InsecureProofVerifier::VerifyCertChain( + const QuicString& hostname, + const std::vector<QuicString>& certs, + const ProofVerifyContext* context, + QuicString* error_details, + std::unique_ptr<ProofVerifyDetails>* details, + std::unique_ptr<ProofVerifierCallback> callback) { + return QUIC_SUCCESS; +} + +std::unique_ptr<ProofVerifyContext> +InsecureProofVerifier::CreateDefaultContext() { + return nullptr; +} + +QuicConnectionId QuartcCryptoServerStreamHelper::GenerateConnectionIdForReject( + QuicTransportVersion version, + QuicConnectionId connection_id) const { + // TODO(b/124399417): Request a zero-length connection id here when the QUIC + // server perspective supports it. Right now, the stateless rejector requires + // a connection id that is not the same as the client-chosen connection id. + return QuicUtils::CreateRandomConnectionId(); +} + +bool QuartcCryptoServerStreamHelper::CanAcceptClientHello( + const CryptoHandshakeMessage& message, + const QuicSocketAddress& client_address, + const QuicSocketAddress& peer_address, + const QuicSocketAddress& self_address, + QuicString* error_details) const { + return true; +} + +std::unique_ptr<QuicCryptoClientConfig> CreateCryptoClientConfig( + QuicStringPiece pre_shared_key) { + auto config = QuicMakeUnique<QuicCryptoClientConfig>( + QuicMakeUnique<InsecureProofVerifier>(), + TlsClientHandshaker::CreateSslCtx()); + config->set_pad_inchoate_hello(false); + config->set_pad_full_hello(false); + if (!pre_shared_key.empty()) { + config->set_pre_shared_key(pre_shared_key); + } + return config; +} + +CryptoServerConfig CreateCryptoServerConfig(QuicRandom* random, + const QuicClock* clock, + QuicStringPiece pre_shared_key) { + CryptoServerConfig crypto_server_config; + + // Generate a random source address token secret. For long-running servers + // it's better to not regenerate it for each connection to enable zero-RTT + // handshakes, but for transient clients it does not matter. + char source_address_token_secret[kInputKeyingMaterialLength]; + random->RandBytes(source_address_token_secret, kInputKeyingMaterialLength); + auto config = QuicMakeUnique<QuicCryptoServerConfig>( + QuicString(source_address_token_secret, kInputKeyingMaterialLength), + random, QuicMakeUnique<DummyProofSource>(), KeyExchangeSource::Default(), + TlsServerHandshaker::CreateSslCtx()); + + // We run QUIC over ICE, and ICE is verifying remote side with STUN pings. + // We disable source address token validation in order to allow for 0-rtt + // setup (plus source ip addresses are changing even during the connection + // when ICE is used). + config->set_validate_source_address_token(false); + + // Effectively disables the anti-amplification measures (we don't need + // them because we use ICE, and we need to disable them because we disable + // padding of crypto packets). + // This multiplier must be large enough so that the crypto handshake packet + // (approx. 300 bytes) multiplied by this multiplier is larger than a fully + // sized packet (currently 1200 bytes). + // 1500 is a bit extreme: if you can imagine sending a 1 byte packet, and + // your largest MTU would be below 1500 bytes, 1500*1 >= + // any_packet_that_you_can_imagine_sending. + // (again, we hardcode packet size to 1200, so we are not dealing with jumbo + // frames). + config->set_chlo_multiplier(1500); + + // We are sending small client hello, we must not validate its size. + config->set_validate_chlo_size(false); + + // Provide server with serialized config string to prove ownership. + QuicCryptoServerConfig::ConfigOptions options; + // The |message| is used to handle the return value of AddDefaultConfig + // which is raw pointer of the CryptoHandshakeMessage. + std::unique_ptr<CryptoHandshakeMessage> message( + config->AddDefaultConfig(random, clock, options)); + config->set_pad_rej(false); + config->set_pad_shlo(false); + if (!pre_shared_key.empty()) { + config->set_pre_shared_key(pre_shared_key); + } + crypto_server_config.config = std::move(config); + const QuicData& data = message->GetSerialized(); + + crypto_server_config.serialized_crypto_config = + QuicString(data.data(), data.length()); + return crypto_server_config; +} + +} // namespace quic
diff --git a/quic/quartc/quartc_crypto_helpers.h b/quic/quartc/quartc_crypto_helpers.h new file mode 100644 index 0000000..2b280b5 --- /dev/null +++ b/quic/quartc/quartc_crypto_helpers.h
@@ -0,0 +1,121 @@ +// Copyright (c) 2017 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. + +#ifndef QUICHE_QUIC_QUARTC_QUARTC_CRYPTO_HELPERS_H_ +#define QUICHE_QUIC_QUARTC_QUARTC_CRYPTO_HELPERS_H_ + +#include "net/third_party/quiche/src/quic/core/crypto/crypto_handshake_message.h" +#include "net/third_party/quiche/src/quic/core/crypto/proof_source.h" +#include "net/third_party/quiche/src/quic/core/crypto/proof_verifier.h" +#include "net/third_party/quiche/src/quic/core/crypto/quic_crypto_client_config.h" +#include "net/third_party/quiche/src/quic/core/crypto/quic_crypto_server_config.h" +#include "net/third_party/quiche/src/quic/core/quic_connection_id.h" +#include "net/third_party/quiche/src/quic/core/quic_crypto_server_stream.h" +#include "net/third_party/quiche/src/quic/core/quic_versions.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_reference_counted.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_string.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h" + +namespace quic { + +// Never, ever, change this certificate name. You will break 0-rtt handshake if +// you do. +static constexpr char kDummyCertName[] = "Dummy cert"; + +struct CryptoServerConfig { + std::unique_ptr<QuicCryptoServerConfig> config; + QuicString serialized_crypto_config; +}; + +// Length of HKDF input keying material, equal to its number of bytes. +// https://tools.ietf.org/html/rfc5869#section-2.2. +// TODO(zhihuang): Verify that input keying material length is correct. +constexpr size_t kInputKeyingMaterialLength = 32; + +// Used by QuicCryptoServerConfig to provide dummy proof credentials. +// TODO(zhihuang): Remove when secure P2P QUIC handshake is possible. +class DummyProofSource : public ProofSource { + public: + DummyProofSource() {} + ~DummyProofSource() override {} + + // ProofSource overrides. + void GetProof(const QuicSocketAddress& server_address, + const QuicString& hostname, + const QuicString& server_config, + QuicTransportVersion transport_version, + QuicStringPiece chlo_hash, + std::unique_ptr<Callback> callback) override; + + QuicReferenceCountedPointer<Chain> GetCertChain( + const QuicSocketAddress& server_address, + const QuicString& hostname) override; + + void ComputeTlsSignature( + const QuicSocketAddress& server_address, + const QuicString& hostname, + uint16_t signature_algorithm, + QuicStringPiece in, + std::unique_ptr<SignatureCallback> callback) override; +}; + +// Used by QuicCryptoClientConfig to ignore the peer's credentials +// and establish an insecure QUIC connection. +// TODO(zhihuang): Remove when secure P2P QUIC handshake is possible. +class InsecureProofVerifier : public ProofVerifier { + public: + InsecureProofVerifier() {} + ~InsecureProofVerifier() override {} + + // ProofVerifier overrides. + QuicAsyncStatus VerifyProof( + const QuicString& hostname, + const uint16_t port, + const QuicString& server_config, + QuicTransportVersion transport_version, + QuicStringPiece chlo_hash, + const std::vector<QuicString>& certs, + const QuicString& cert_sct, + const QuicString& signature, + const ProofVerifyContext* context, + QuicString* error_details, + std::unique_ptr<ProofVerifyDetails>* verify_details, + std::unique_ptr<ProofVerifierCallback> callback) override; + + QuicAsyncStatus VerifyCertChain( + const QuicString& hostname, + const std::vector<QuicString>& certs, + const ProofVerifyContext* context, + QuicString* error_details, + std::unique_ptr<ProofVerifyDetails>* details, + std::unique_ptr<ProofVerifierCallback> callback) override; + + std::unique_ptr<ProofVerifyContext> CreateDefaultContext() override; +}; + +// Implementation of the server-side crypto stream helper. +class QuartcCryptoServerStreamHelper : public QuicCryptoServerStream::Helper { + public: + QuicConnectionId GenerateConnectionIdForReject( + QuicTransportVersion version, + QuicConnectionId connection_id) const override; + + bool CanAcceptClientHello(const CryptoHandshakeMessage& message, + const QuicSocketAddress& client_address, + const QuicSocketAddress& peer_address, + const QuicSocketAddress& self_address, + QuicString* error_details) const override; +}; + +std::unique_ptr<QuicCryptoClientConfig> CreateCryptoClientConfig( + QuicStringPiece pre_shared_key); + +CryptoServerConfig CreateCryptoServerConfig(QuicRandom* random, + const QuicClock* clock, + QuicStringPiece pre_shared_key); + +} // namespace quic + +#endif // QUICHE_QUIC_QUARTC_QUARTC_CRYPTO_HELPERS_H_
diff --git a/quic/quartc/quartc_dispatcher.cc b/quic/quartc/quartc_dispatcher.cc new file mode 100644 index 0000000..8070203 --- /dev/null +++ b/quic/quartc/quartc_dispatcher.cc
@@ -0,0 +1,80 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/quic/quartc/quartc_dispatcher.h" + +#include "net/third_party/quiche/src/quic/core/quic_utils.h" +#include "net/third_party/quiche/src/quic/core/quic_versions.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h" +#include "net/third_party/quiche/src/quic/quartc/quartc_factory.h" + +namespace quic { + +QuartcDispatcher::QuartcDispatcher( + std::unique_ptr<QuicConfig> config, + std::unique_ptr<QuicCryptoServerConfig> crypto_config, + QuicStringPiece crypto_config_serialized, + QuicVersionManager* version_manager, + std::unique_ptr<QuicConnectionHelperInterface> helper, + std::unique_ptr<QuicCryptoServerStream::Helper> session_helper, + std::unique_ptr<QuicAlarmFactory> alarm_factory, + std::unique_ptr<QuartcPacketWriter> packet_writer, + Delegate* delegate) + : QuicDispatcher(config.get(), + crypto_config.get(), + version_manager, + std::move(helper), + std::move(session_helper), + std::move(alarm_factory), + kQuicDefaultConnectionIdLength), + owned_quic_config_(std::move(config)), + owned_crypto_config_(std::move(crypto_config)), + crypto_config_(crypto_config_serialized), + delegate_(delegate), + packet_writer_(packet_writer.get()) { + // QuicDispatcher takes ownership of the writer. + QuicDispatcher::InitializeWithWriter(packet_writer.release()); + // NB: This must happen *after* InitializeWithWriter. It can call us back + // with OnTransportCanWrite() immediately, and the dispatcher needs to be + // fully initialized to handle that. + packet_writer_->SetPacketTransportDelegate(this); +} + +QuartcDispatcher::~QuartcDispatcher() { + packet_writer_->SetPacketTransportDelegate(nullptr); +} + +QuartcSession* QuartcDispatcher::CreateQuicSession( + QuicConnectionId connection_id, + const QuicSocketAddress& client_address, + QuicStringPiece alpn, + const ParsedQuicVersion& version) { + std::unique_ptr<QuicConnection> connection = CreateQuicConnection( + connection_id, client_address, helper(), alarm_factory(), writer(), + Perspective::IS_SERVER, ParsedQuicVersionVector{version}); + QuartcSession* session = new QuartcServerSession( + std::move(connection), /*visitor=*/this, config(), GetSupportedVersions(), + helper()->GetClock(), crypto_config(), compressed_certs_cache(), + session_helper()); + delegate_->OnSessionCreated(session); + return session; +} + +void QuartcDispatcher::OnTransportCanWrite() { + OnCanWrite(); +} + +void QuartcDispatcher::OnTransportReceived(const char* data, size_t data_len) { + // QuartcPacketTransport does not surface real peer addresses, so the + // dispatcher uses a dummy address when processing incoming packets. Note that + // the dispatcher refuses to process anything with port 0. + static const QuicSocketAddress* dummy_address = + new QuicSocketAddress(QuicIpAddress::Any4(), /*port=*/1); + + QuicReceivedPacket packet(data, data_len, helper()->GetClock()->Now()); + ProcessPacket(/*self_address=*/*dummy_address, + /*peer_address=*/*dummy_address, packet); +} + +} // namespace quic
diff --git a/quic/quartc/quartc_dispatcher.h b/quic/quartc/quartc_dispatcher.h new file mode 100644 index 0000000..e660107 --- /dev/null +++ b/quic/quartc/quartc_dispatcher.h
@@ -0,0 +1,73 @@ +// Copyright (c) 2017 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. + +#ifndef QUICHE_QUIC_QUARTC_QUARTC_DISPATCHER_H_ +#define QUICHE_QUIC_QUARTC_QUARTC_DISPATCHER_H_ + +#include "net/third_party/quiche/src/quic/core/crypto/quic_crypto_server_config.h" +#include "net/third_party/quiche/src/quic/core/quic_alarm_factory.h" +#include "net/third_party/quiche/src/quic/core/quic_config.h" +#include "net/third_party/quiche/src/quic/core/quic_connection.h" +#include "net/third_party/quiche/src/quic/core/quic_connection_id.h" +#include "net/third_party/quiche/src/quic/core/quic_crypto_server_stream.h" +#include "net/third_party/quiche/src/quic/core/quic_dispatcher.h" +#include "net/third_party/quiche/src/quic/core/quic_version_manager.h" +#include "net/third_party/quiche/src/quic/core/quic_versions.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h" +#include "net/third_party/quiche/src/quic/quartc/quartc_session.h" + +namespace quic { + +class QuartcDispatcher : public QuicDispatcher, + QuartcPacketTransport::Delegate { + public: + class Delegate { + public: + virtual ~Delegate() = default; + virtual void OnSessionCreated(QuartcSession* session) = 0; + }; + + QuartcDispatcher( + std::unique_ptr<QuicConfig> config, + std::unique_ptr<QuicCryptoServerConfig> crypto_config, + QuicStringPiece crypto_config_serialized, + QuicVersionManager* version_manager, + std::unique_ptr<QuicConnectionHelperInterface> helper, + std::unique_ptr<QuicCryptoServerStream::Helper> session_helper, + std::unique_ptr<QuicAlarmFactory> alarm_factory, + std::unique_ptr<QuartcPacketWriter> packet_writer, + Delegate* delegate); + ~QuartcDispatcher() override; + + QuartcSession* CreateQuicSession(QuicConnectionId connection_id, + const QuicSocketAddress& client_address, + QuicStringPiece alpn, + const ParsedQuicVersion& version) override; + + // QuartcPacketTransport::Delegate overrides. + void OnTransportCanWrite() override; + void OnTransportReceived(const char* data, size_t data_len) override; + + // A serialized server config in quic wire format. + QuicStringPiece server_crypto_config() const { return crypto_config_; } + + private: + // Members owned by QuartcDispatcher but not QuicDispatcher. + std::unique_ptr<QuicConfig> owned_quic_config_; + std::unique_ptr<QuicCryptoServerConfig> owned_crypto_config_; + QuicString crypto_config_; + + // Delegate invoked when the dispatcher creates a new session. + Delegate* delegate_; + + // The packet writer used by this dispatcher. Owned by the base class, but + // the base class upcasts it to QuicPacketWriter (which prevents detaching the + // transport delegate without a downcast). + QuartcPacketWriter* packet_writer_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_QUARTC_QUARTC_DISPATCHER_H_
diff --git a/quic/quartc/quartc_endpoint.cc b/quic/quartc/quartc_endpoint.cc new file mode 100644 index 0000000..2882705 --- /dev/null +++ b/quic/quartc/quartc_endpoint.cc
@@ -0,0 +1,125 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/quic/quartc/quartc_endpoint.h" + +#include "net/third_party/quiche/src/quic/core/quic_version_manager.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h" +#include "net/third_party/quiche/src/quic/quartc/quartc_connection_helper.h" +#include "net/third_party/quiche/src/quic/quartc/quartc_crypto_helpers.h" +#include "net/third_party/quiche/src/quic/quartc/quartc_dispatcher.h" + +namespace quic { + +namespace { + +// Wrapper around a QuicAlarmFactory which delegates to the wrapped factory. +// Usee to convert an unowned pointer into an owned pointer, so that the new +// "owner" does not delete the underlying factory. Note that this is only valid +// when the unowned pointer is already guaranteed to outlive the new "owner". +class QuartcAlarmFactoryWrapper : public QuicAlarmFactory { + public: + explicit QuartcAlarmFactoryWrapper(QuicAlarmFactory* impl) : impl_(impl) {} + + QuicAlarm* CreateAlarm(QuicAlarm::Delegate* delegate) override; + QuicArenaScopedPtr<QuicAlarm> CreateAlarm( + QuicArenaScopedPtr<QuicAlarm::Delegate> delegate, + QuicConnectionArena* arena) override; + + private: + QuicAlarmFactory* impl_; +}; + +QuicAlarm* QuartcAlarmFactoryWrapper::CreateAlarm( + QuicAlarm::Delegate* delegate) { + return impl_->CreateAlarm(delegate); +} + +QuicArenaScopedPtr<QuicAlarm> QuartcAlarmFactoryWrapper::CreateAlarm( + QuicArenaScopedPtr<QuicAlarm::Delegate> delegate, + QuicConnectionArena* arena) { + return impl_->CreateAlarm(std::move(delegate), arena); +} + +QuartcFactoryConfig CreateFactoryConfig(QuicAlarmFactory* alarm_factory, + const QuicClock* clock) { + QuartcFactoryConfig config; + config.alarm_factory = alarm_factory; + config.clock = clock; + return config; +} + +} // namespace + +QuartcClientEndpoint::QuartcClientEndpoint( + QuicAlarmFactory* alarm_factory, + const QuicClock* clock, + QuartcEndpoint::Delegate* delegate, + const QuartcSessionConfig& config, + QuicStringPiece serialized_server_config, + std::unique_ptr<QuicVersionManager> version_manager) + : alarm_factory_(alarm_factory), + clock_(clock), + delegate_(delegate), + serialized_server_config_(serialized_server_config), + version_manager_(version_manager ? std::move(version_manager) + : QuicMakeUnique<QuicVersionManager>( + AllSupportedVersions())), + create_session_alarm_(QuicWrapUnique( + alarm_factory_->CreateAlarm(new CreateSessionDelegate(this)))), + factory_(QuicMakeUnique<QuartcFactory>( + CreateFactoryConfig(alarm_factory, clock))), + config_(config) {} + +void QuartcClientEndpoint::Connect(QuartcPacketTransport* packet_transport) { + packet_transport_ = packet_transport; + create_session_alarm_->Set(clock_->Now()); +} + +void QuartcClientEndpoint::OnCreateSessionAlarm() { + session_ = factory_->CreateQuartcClientSession( + config_, version_manager_->GetSupportedVersions(), + serialized_server_config_, packet_transport_); + delegate_->OnSessionCreated(session_.get()); +} + +QuartcServerEndpoint::QuartcServerEndpoint( + QuicAlarmFactory* alarm_factory, + const QuicClock* clock, + QuartcEndpoint::Delegate* delegate, + const QuartcSessionConfig& config, + std::unique_ptr<QuicVersionManager> version_manager) + : alarm_factory_(alarm_factory), + delegate_(delegate), + config_(config), + version_manager_(version_manager ? std::move(version_manager) + : QuicMakeUnique<QuicVersionManager>( + AllSupportedVersions())), + pre_connection_helper_(QuicMakeUnique<QuartcConnectionHelper>(clock)), + crypto_config_( + CreateCryptoServerConfig(pre_connection_helper_->GetRandomGenerator(), + clock, + config.pre_shared_key)) {} + +void QuartcServerEndpoint::Connect(QuartcPacketTransport* packet_transport) { + DCHECK(pre_connection_helper_ != nullptr); + dispatcher_ = QuicMakeUnique<QuartcDispatcher>( + QuicMakeUnique<QuicConfig>(CreateQuicConfig(config_)), + std::move(crypto_config_.config), crypto_config_.serialized_crypto_config, + version_manager_.get(), std::move(pre_connection_helper_), + QuicMakeUnique<QuartcCryptoServerStreamHelper>(), + QuicMakeUnique<QuartcAlarmFactoryWrapper>(alarm_factory_), + QuicMakeUnique<QuartcPacketWriter>(packet_transport, + config_.max_packet_size), + this); + // The dispatcher requires at least one call to |ProcessBufferedChlos| to + // set the number of connections it is allowed to create. + dispatcher_->ProcessBufferedChlos(/*max_connections_to_create=*/1); +} + +void QuartcServerEndpoint::OnSessionCreated(QuartcSession* session) { + delegate_->OnSessionCreated(session); +} + +} // namespace quic
diff --git a/quic/quartc/quartc_endpoint.h b/quic/quartc/quartc_endpoint.h new file mode 100644 index 0000000..ee856ea --- /dev/null +++ b/quic/quartc/quartc_endpoint.h
@@ -0,0 +1,183 @@ +// Copyright (c) 2017 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. + +#ifndef QUICHE_QUIC_QUARTC_QUARTC_ENDPOINT_H_ +#define QUICHE_QUIC_QUARTC_QUARTC_ENDPOINT_H_ + +#include "net/third_party/quiche/src/quic/core/quic_alarm_factory.h" +#include "net/third_party/quiche/src/quic/core/quic_error_codes.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_clock.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_string.h" +#include "net/third_party/quiche/src/quic/quartc/quartc_connection_helper.h" +#include "net/third_party/quiche/src/quic/quartc/quartc_crypto_helpers.h" +#include "net/third_party/quiche/src/quic/quartc/quartc_dispatcher.h" +#include "net/third_party/quiche/src/quic/quartc/quartc_factory.h" + +namespace quic { + +// Private implementation of QuartcEndpoint. Enables different implementations +// for client and server endpoints. +class QuartcEndpointImpl { + public: + virtual ~QuartcEndpointImpl() = default; + + virtual QuicStringPiece server_crypto_config() const = 0; +}; + +// Endpoint (client or server) in a peer-to-peer Quartc connection. +class QuartcEndpoint { + public: + class Delegate { + public: + virtual ~Delegate() = default; + + // Called when an endpoint creates a new session, before any packets are + // processed or sent. The callee should perform any additional + // configuration required, such as setting a session delegate, before + // returning. |session| is owned by the endpoint, but remains safe to use + // until another call to |OnSessionCreated| occurs, at which point previous + // session is destroyed. + virtual void OnSessionCreated(QuartcSession* session) = 0; + + // Called if the endpoint fails to establish a session after a call to + // Connect. (The most likely cause is a network idle timeout.) + virtual void OnConnectError(QuicErrorCode error, + const QuicString& error_details) = 0; + }; + + virtual ~QuartcEndpoint() = default; + + // Connects the endpoint using the given session config. After |Connect| is + // called, the endpoint will asynchronously create a session, then call + // |Delegate::OnSessionCreated|. + virtual void Connect(QuartcPacketTransport* packet_transport) = 0; +}; + +// Implementation of QuartcEndpoint which immediately (but asynchronously) +// creates a session by scheduling a QuicAlarm. Only suitable for use with the +// client perspective. +class QuartcClientEndpoint : public QuartcEndpoint { + public: + // |alarm_factory|, |clock|, and |delegate| are owned by the caller and must + // outlive the endpoint. + QuartcClientEndpoint( + QuicAlarmFactory* alarm_factory, + const QuicClock* clock, + Delegate* delegate, + const QuartcSessionConfig& config, + QuicStringPiece serialized_server_config, + std::unique_ptr<QuicVersionManager> version_manager = nullptr); + + void Connect(QuartcPacketTransport* packet_transport) override; + + private: + friend class CreateSessionDelegate; + class CreateSessionDelegate : public QuicAlarm::Delegate { + public: + CreateSessionDelegate(QuartcClientEndpoint* endpoint) + : endpoint_(endpoint) {} + + void OnAlarm() override { endpoint_->OnCreateSessionAlarm(); } + + private: + QuartcClientEndpoint* endpoint_; + }; + + // Callback which occurs when |create_session_alarm_| fires. + void OnCreateSessionAlarm(); + + // Implementation of QuicAlarmFactory used by this endpoint. Unowned. + QuicAlarmFactory* alarm_factory_; + + // Implementation of QuicClock used by this endpoint. Unowned. + const QuicClock* clock_; + + // Delegate which receives callbacks for newly created sessions. + QuartcEndpoint::Delegate* delegate_; + + // Server config. If valid, used to perform a 0-RTT connection. + const QuicString serialized_server_config_; + + // Version manager. May be injected to control version negotiation in tests. + std::unique_ptr<QuicVersionManager> version_manager_; + + // Alarm for creating sessions asynchronously. The alarm is set when + // Connect() is called. When it fires, the endpoint creates a session and + // calls the delegate. + std::unique_ptr<QuicAlarm> create_session_alarm_; + + // QuartcFactory used by this endpoint to create sessions. This is an + // implementation detail of the QuartcEndpoint, and will eventually be + // replaced by a dispatcher (for servers) or version-negotiation agent (for + // clients). + std::unique_ptr<QuartcFactory> factory_; + + // Config to be used for new sessions. + QuartcSessionConfig config_; + + // The currently-active session. Nullptr until |Connect| and + // |Delegate::OnSessionCreated| are called. + std::unique_ptr<QuartcSession> session_; + + QuartcPacketTransport* packet_transport_; +}; + +// Implementation of QuartcEndpoint which uses a QuartcDispatcher to listen for +// an incoming CHLO and create a session when one arrives. Only suitable for +// use with the server perspective. +class QuartcServerEndpoint : public QuartcEndpoint, + public QuartcDispatcher::Delegate { + public: + QuartcServerEndpoint( + QuicAlarmFactory* alarm_factory, + const QuicClock* clock, + QuartcEndpoint::Delegate* delegate, + const QuartcSessionConfig& config, + std::unique_ptr<QuicVersionManager> version_manager = nullptr); + + // Implements QuartcEndpoint. + void Connect(QuartcPacketTransport* packet_transport) override; + + // Implements QuartcDispatcher::Delegate. + void OnSessionCreated(QuartcSession* session) override; + + // Accessor to retrieve the server crypto config. May only be called after + // Connect(). + QuicStringPiece server_crypto_config() const { + return crypto_config_.serialized_crypto_config; + } + + const std::vector<ParsedQuicVersion> GetSupportedQuicVersions() const { + return version_manager_->GetSupportedVersions(); + } + + private: + // Implementation of QuicAlarmFactory used by this endpoint. Unowned. + QuicAlarmFactory* alarm_factory_; + + // Delegate which receives callbacks for newly created sessions. + QuartcEndpoint::Delegate* delegate_; + + // Config to be used for new sessions. + QuartcSessionConfig config_; + + // Version manager. May be injected to control version negotiation in tests. + std::unique_ptr<QuicVersionManager> version_manager_; + + // QuartcDispatcher waits for an incoming CHLO, then either rejects it or + // creates a session to respond to it. The dispatcher owns all sessions it + // creates. + std::unique_ptr<QuartcDispatcher> dispatcher_; + + // This field is only available before connection was started. + std::unique_ptr<QuartcConnectionHelper> pre_connection_helper_; + + // A configuration, containing public key, that may need to be passed to the + // client to enable 0rtt. + CryptoServerConfig crypto_config_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_QUARTC_QUARTC_ENDPOINT_H_
diff --git a/quic/quartc/quartc_endpoint_test.cc b/quic/quartc/quartc_endpoint_test.cc new file mode 100644 index 0000000..3d7d85e --- /dev/null +++ b/quic/quartc/quartc_endpoint_test.cc
@@ -0,0 +1,214 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/quic/quartc/quartc_endpoint.h" + +#include "net/third_party/quiche/src/quic/core/quic_versions.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_test.h" +#include "net/third_party/quiche/src/quic/quartc/quartc_crypto_helpers.h" +#include "net/third_party/quiche/src/quic/quartc/quartc_fakes.h" +#include "net/third_party/quiche/src/quic/quartc/simulated_packet_transport.h" +#include "net/third_party/quiche/src/quic/test_tools/simulator/link.h" +#include "net/third_party/quiche/src/quic/test_tools/simulator/simulator.h" + +namespace quic { +namespace { + +class QuartcEndpointTest : public QuicTest { + protected: + QuartcEndpointTest() + : client_transport_(&simulator_, + "client_transport", + "server_transport", + 10 * kDefaultMaxPacketSize), + server_transport_(&simulator_, + "server_transport", + "client_transport", + 10 * kDefaultMaxPacketSize), + client_server_link_(&client_transport_, + &server_transport_, + QuicBandwidth::FromKBitsPerSecond(10000), + QuicTime::Delta::FromMilliseconds(1)), + server_session_delegate_(&server_stream_delegate_, + simulator_.GetClock()), + server_endpoint_delegate_(&server_session_delegate_), + server_endpoint_( + QuicMakeUnique<QuartcServerEndpoint>(simulator_.GetAlarmFactory(), + simulator_.GetClock(), + &server_endpoint_delegate_, + QuartcSessionConfig())), + client_session_delegate_(&client_stream_delegate_, + simulator_.GetClock()), + client_endpoint_delegate_(&client_session_delegate_), + client_endpoint_(QuicMakeUnique<QuartcClientEndpoint>( + simulator_.GetAlarmFactory(), + simulator_.GetClock(), + &client_endpoint_delegate_, + QuartcSessionConfig(), + /*serialized_server_config=*/"")) {} + + simulator::Simulator simulator_; + + simulator::SimulatedQuartcPacketTransport client_transport_; + simulator::SimulatedQuartcPacketTransport server_transport_; + simulator::SymmetricLink client_server_link_; + + FakeQuartcStreamDelegate server_stream_delegate_; + FakeQuartcSessionDelegate server_session_delegate_; + FakeQuartcEndpointDelegate server_endpoint_delegate_; + + std::unique_ptr<QuartcServerEndpoint> server_endpoint_; + + FakeQuartcStreamDelegate client_stream_delegate_; + FakeQuartcSessionDelegate client_session_delegate_; + FakeQuartcEndpointDelegate client_endpoint_delegate_; + + std::unique_ptr<QuartcClientEndpoint> client_endpoint_; +}; + +// After calling Connect, the client endpoint must wait for an async callback. +// The callback occurs after a finite amount of time and produces a session. +TEST_F(QuartcEndpointTest, ClientCreatesSessionAsynchronously) { + client_endpoint_->Connect(&client_transport_); + + EXPECT_EQ(client_endpoint_delegate_.session(), nullptr); + + EXPECT_TRUE(simulator_.RunUntil( + [this] { return client_endpoint_delegate_.session() != nullptr; })); +} + +// Tests that the server can negotiate for an older QUIC version if the client +// attempts to connect using a newer version. +TEST_F(QuartcEndpointTest, + QUIC_TEST_DISABLED_IN_CHROME(ServerNegotiatesForOldVersion)) { + // Note: for this test, we need support for two versions. Which two shouldn't + // matter, but they must be enabled so that the version manager doesn't filter + // them out. + SetQuicReloadableFlag(quic_enable_version_46, true); + SetQuicReloadableFlag(quic_enable_version_43, true); + + // Reset the client endpoint to prefer version 46 but also be capable of + // speaking version 43. + ParsedQuicVersionVector client_versions; + client_versions.push_back({PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_46}); + client_versions.push_back({PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_43}); + client_endpoint_ = QuicMakeUnique<QuartcClientEndpoint>( + simulator_.GetAlarmFactory(), simulator_.GetClock(), + &client_endpoint_delegate_, QuartcSessionConfig(), + /*serialized_server_config=*/"", + QuicMakeUnique<QuicVersionManager>(client_versions)); + + // Reset the server endpoint to only speak version 43. + ParsedQuicVersionVector server_versions; + server_versions.push_back({PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_43}); + server_endpoint_ = QuicMakeUnique<QuartcServerEndpoint>( + simulator_.GetAlarmFactory(), simulator_.GetClock(), + &server_endpoint_delegate_, QuartcSessionConfig(), + QuicMakeUnique<QuicVersionManager>(server_versions)); + + // The endpoints should be able to establish a connection using version 46. + server_endpoint_->Connect(&server_transport_); + client_endpoint_->Connect(&client_transport_); + + ASSERT_TRUE(simulator_.RunUntil([this] { + return client_endpoint_delegate_.session() != nullptr && + client_endpoint_delegate_.session()->IsEncryptionEstablished() && + server_endpoint_delegate_.session() != nullptr && + server_endpoint_delegate_.session()->IsEncryptionEstablished(); + })); + EXPECT_EQ(client_endpoint_delegate_.session()->connection()->version(), + server_versions[0]); + EXPECT_EQ(server_endpoint_delegate_.session()->connection()->version(), + server_versions[0]); +} + +// Tests that the server can accept connections from clients that use older +// QUIC versions. +TEST_F(QuartcEndpointTest, + QUIC_TEST_DISABLED_IN_CHROME(ServerAcceptsOldVersion)) { + // Note: for this test, we need support for two versions. Which two shouldn't + // matter, but they must be enabled so that the version manager doesn't filter + // them out. + SetQuicReloadableFlag(quic_enable_version_46, true); + SetQuicReloadableFlag(quic_enable_version_43, true); + + // Reset the client endpoint to only speak version 43. + ParsedQuicVersionVector client_versions; + client_versions.push_back({PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_43}); + client_endpoint_ = QuicMakeUnique<QuartcClientEndpoint>( + simulator_.GetAlarmFactory(), simulator_.GetClock(), + &client_endpoint_delegate_, QuartcSessionConfig(), + /*serialized_server_config=*/"", + QuicMakeUnique<QuicVersionManager>(client_versions)); + + // Reset the server endpoint to prefer version 46 but also be capable of + // speaking version 43. + ParsedQuicVersionVector server_versions; + server_versions.push_back({PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_46}); + server_versions.push_back({PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_43}); + server_endpoint_ = QuicMakeUnique<QuartcServerEndpoint>( + simulator_.GetAlarmFactory(), simulator_.GetClock(), + &server_endpoint_delegate_, QuartcSessionConfig(), + QuicMakeUnique<QuicVersionManager>(server_versions)); + + // The endpoints should be able to establish a connection using version 46. + server_endpoint_->Connect(&server_transport_); + client_endpoint_->Connect(&client_transport_); + + ASSERT_TRUE(simulator_.RunUntil([this] { + return client_endpoint_delegate_.session() != nullptr && + client_endpoint_delegate_.session()->IsEncryptionEstablished() && + server_endpoint_delegate_.session() != nullptr && + server_endpoint_delegate_.session()->IsEncryptionEstablished(); + })); + EXPECT_EQ(client_endpoint_delegate_.session()->connection()->version(), + client_versions[0]); + EXPECT_EQ(server_endpoint_delegate_.session()->connection()->version(), + client_versions[0]); +} + +// Tests that version negotiation fails when the client and server support +// completely disjoint sets of versions. +TEST_F(QuartcEndpointTest, + QUIC_TEST_DISABLED_IN_CHROME(VersionNegotiationWithDisjointVersions)) { + // Note: for this test, we need support for two versions. Which two shouldn't + // matter, but they must be enabled so that the version manager doesn't filter + // them out. + SetQuicReloadableFlag(quic_enable_version_46, true); + SetQuicReloadableFlag(quic_enable_version_43, true); + + // Reset the client endpoint to only speak version 43. + ParsedQuicVersionVector client_versions; + client_versions.push_back({PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_43}); + client_endpoint_ = QuicMakeUnique<QuartcClientEndpoint>( + simulator_.GetAlarmFactory(), simulator_.GetClock(), + &client_endpoint_delegate_, QuartcSessionConfig(), + /*serialized_server_config=*/"", + QuicMakeUnique<QuicVersionManager>(client_versions)); + + // Reset the server endpoint to only speak version 46. + ParsedQuicVersionVector server_versions; + server_versions.push_back({PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_46}); + server_endpoint_ = QuicMakeUnique<QuartcServerEndpoint>( + simulator_.GetAlarmFactory(), simulator_.GetClock(), + &server_endpoint_delegate_, QuartcSessionConfig(), + QuicMakeUnique<QuicVersionManager>(server_versions)); + + // The endpoints should be unable to establish a connection. + server_endpoint_->Connect(&server_transport_); + client_endpoint_->Connect(&client_transport_); + + // Note that the error is reported from the client and *not* the server. The + // server sees an invalid version, sends a version negotiation packet, and + // never gets a response, because the client stops sending when it can't find + // a mutually supported versions. + ASSERT_TRUE(simulator_.RunUntil([this] { + return client_endpoint_delegate_.session() != nullptr && + client_endpoint_delegate_.session()->error() != QUIC_NO_ERROR; + })); + EXPECT_EQ(client_endpoint_delegate_.session()->error(), QUIC_INVALID_VERSION); +} + +} // namespace +} // namespace quic
diff --git a/quic/quartc/quartc_factory.cc b/quic/quartc/quartc_factory.cc new file mode 100644 index 0000000..803122a --- /dev/null +++ b/quic/quartc/quartc_factory.cc
@@ -0,0 +1,255 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/quic/quartc/quartc_factory.h" + +#include "net/third_party/quiche/src/quic/core/crypto/quic_random.h" +#include "net/third_party/quiche/src/quic/core/quic_utils.h" +#include "net/third_party/quiche/src/quic/core/tls_client_handshaker.h" +#include "net/third_party/quiche/src/quic/core/tls_server_handshaker.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h" +#include "net/third_party/quiche/src/quic/quartc/quartc_connection_helper.h" +#include "net/third_party/quiche/src/quic/quartc/quartc_crypto_helpers.h" +#include "net/third_party/quiche/src/quic/quartc/quartc_session.h" + +namespace quic { + +QuartcFactory::QuartcFactory(const QuartcFactoryConfig& factory_config) + : alarm_factory_(factory_config.alarm_factory), + clock_(factory_config.clock), + connection_helper_(QuicMakeUnique<QuartcConnectionHelper>(clock_)), + compressed_certs_cache_(QuicMakeUnique<QuicCompressedCertsCache>( + QuicCompressedCertsCache::kQuicCompressedCertsCacheSize)), + stream_helper_(QuicMakeUnique<QuartcCryptoServerStreamHelper>()) {} + +std::unique_ptr<QuartcSession> QuartcFactory::CreateQuartcClientSession( + const QuartcSessionConfig& quartc_session_config, + const ParsedQuicVersionVector& supported_versions, + QuicStringPiece server_crypto_config, + QuartcPacketTransport* packet_transport) { + DCHECK(packet_transport); + + // QuartcSession will eventually own both |writer| and |quic_connection|. + auto writer = QuicMakeUnique<QuartcPacketWriter>( + packet_transport, quartc_session_config.max_packet_size); + + // While the QuicConfig is not directly used by the connection, creating it + // also sets flag values which must be set before creating the connection. + QuicConfig quic_config = CreateQuicConfig(quartc_session_config); + std::unique_ptr<QuicConnection> quic_connection = CreateQuicConnection( + Perspective::IS_CLIENT, supported_versions, writer.get()); + + return QuicMakeUnique<QuartcClientSession>( + std::move(quic_connection), quic_config, supported_versions, clock_, + std::move(writer), + CreateCryptoClientConfig(quartc_session_config.pre_shared_key), + server_crypto_config); +} + +void ConfigureGlobalQuicSettings() { + // Fixes behavior of StopReading() with level-triggered stream sequencers. + SetQuicReloadableFlag(quic_stop_reading_when_level_triggered, true); + + // Fix b/110259444. + SetQuicReloadableFlag(quic_fix_spurious_ack_alarm, true); + + // Enable version 46+ to enable SendMessage API and 'quic bit' per draft 17. + SetQuicReloadableFlag(quic_enable_version_46, true); + + // Fix for inconsistent reporting of crypto handshake. + SetQuicReloadableFlag(quic_fix_has_pending_crypto_data, true); + + // Ensure that we don't drop data because QUIC streams refuse to buffer it. + // TODO(b/120099046): Replace this with correct handling of WriteMemSlices(). + SetQuicFlag(&FLAGS_quic_buffered_data_threshold, + std::numeric_limits<int>::max()); + + // TODO(b/117157454): Perform version negotiation for Quartc outside of + // QuicSession/QuicConnection. Currently default of + // quic_restart_flag_quic_no_server_conn_ver_negotiation2 is false, + // but we fail blueprint test that sets all QUIC flags to true. + // + // Forcing flag to false to pass blueprint tests, but eventually we'll have + // to implement negotiation outside of QuicConnection. + SetQuicRestartFlag(quic_no_server_conn_ver_negotiation2, false); + SetQuicReloadableFlag(quic_no_client_conn_ver_negotiation, false); + + // Enable and request QUIC to include receive timestamps in ACK frames. + SetQuicReloadableFlag(quic_send_timestamps, true); + + // Enable ACK_DECIMATION_WITH_REORDERING. It requires ack_decimation to be + // false. + SetQuicReloadableFlag(quic_enable_ack_decimation, false); + + // Note: flag settings have no effect for Exoblaze builds since + // SetQuicReloadableFlag() gets stubbed out. + SetQuicReloadableFlag(quic_bbr_less_probe_rtt, true); // Enable BBR6,7,8. + SetQuicReloadableFlag(quic_unified_iw_options, true); // Enable IWXX opts. + SetQuicReloadableFlag(quic_bbr_slower_startup3, true); // Enable BBQX opts. + SetQuicReloadableFlag(quic_bbr_flexible_app_limited, true); // Enable BBR9. +} + +QuicConfig CreateQuicConfig(const QuartcSessionConfig& quartc_session_config) { + // TODO(b/124398962): Figure out a better way to initialize QUIC flags. + // Creating a config shouldn't have global side-effects on flags. However, + // this has the advantage of ensuring that flag values stay in sync with the + // options requested by configs, so simply splitting the config and flag + // settings doesn't seem preferable. + ConfigureGlobalQuicSettings(); + + // In exoblaze this may return false. DCHECK to avoid problems caused by + // incorrect flags configuration. + DCHECK(GetQuicReloadableFlag(quic_enable_version_46)) + << "Your build does not support quic reloadable flags and shouldn't " + "place Quartc calls"; + + QuicTagVector copt; + copt.push_back(kNSTP); + + // Enable and request QUIC to include receive timestamps in ACK frames. + copt.push_back(kSTMP); + + // Enable ACK_DECIMATION_WITH_REORDERING. It requires ack_decimation to be + // false. + copt.push_back(kAKD2); + + // Use unlimited decimation in order to reduce number of unbundled ACKs. + copt.push_back(kAKDU); + + // Enable time-based loss detection. + copt.push_back(kTIME); + + copt.push_back(kBBR3); // Stay in low-gain until in-flight < BDP. + copt.push_back(kBBR5); // 40 RTT ack aggregation. + copt.push_back(kBBR6); // Use a 0.75 * BDP cwnd during PROBE_RTT. + copt.push_back(kBBR8); // Skip PROBE_RTT if app-limited. + copt.push_back(kBBR9); // Ignore app-limited if enough data is in flight. + copt.push_back(kBBQ1); // 2.773 pacing gain in STARTUP. + copt.push_back(kBBQ2); // 2.0 CWND gain in STARTUP. + copt.push_back(kBBQ4); // 0.75 pacing gain in DRAIN. + copt.push_back(k1RTT); // Exit STARTUP after 1 RTT with no gains. + copt.push_back(kIW10); // 10-packet (14600 byte) initial cwnd. + + if (!quartc_session_config.enable_tail_loss_probe) { + copt.push_back(kNTLP); + } + + // TODO(b/112192153): Test and possible enable slower startup when pipe + // filling is ready to use. Slower startup is kBBRS. + + QuicConfig quic_config; + + // Use the limits for the session & stream flow control. The default 16KB + // limit leads to significantly undersending (not reaching BWE on the outgoing + // bitrate) due to blocked frames, and it leads to high latency (and one-way + // delay). Setting it to its limits is not going to cause issues (our streams + // are small generally, and if we were to buffer 24MB it wouldn't be the end + // of the world). We can consider setting different limits in future (e.g. 1MB + // stream, 1.5MB session). It's worth noting that on 1mbps bitrate, limit of + // 24MB can capture approx 4 minutes of the call, and the default increase in + // size of the window (half of the window size) is approximately 2 minutes of + // the call. + quic_config.SetInitialSessionFlowControlWindowToSend( + kSessionReceiveWindowLimit); + quic_config.SetInitialStreamFlowControlWindowToSend( + kStreamReceiveWindowLimit); + quic_config.SetConnectionOptionsToSend(copt); + quic_config.SetClientConnectionOptions(copt); + if (quartc_session_config.max_time_before_crypto_handshake > + QuicTime::Delta::Zero()) { + quic_config.set_max_time_before_crypto_handshake( + quartc_session_config.max_time_before_crypto_handshake); + } + if (quartc_session_config.max_idle_time_before_crypto_handshake > + QuicTime::Delta::Zero()) { + quic_config.set_max_idle_time_before_crypto_handshake( + quartc_session_config.max_idle_time_before_crypto_handshake); + } + if (quartc_session_config.idle_network_timeout > QuicTime::Delta::Zero()) { + quic_config.SetIdleNetworkTimeout( + quartc_session_config.idle_network_timeout, + quartc_session_config.idle_network_timeout); + } + + // The ICE transport provides a unique 5-tuple for each connection. Save + // overhead by omitting the connection id. + quic_config.SetBytesForConnectionIdToSend(0); + + // Allow up to 1000 incoming streams at once. Quartc streams typically contain + // one audio or video frame and close immediately. However, when a video frame + // becomes larger than one packet, there is some delay between the start and + // end of each stream. The default maximum of 100 only leaves about 1 second + // of headroom (Quartc sends ~30 video frames per second) before QUIC starts + // to refuse incoming streams. Back-pressure should clear backlogs of + // incomplete streams, but targets 1 second for recovery. Increasing the + // number of open streams gives sufficient headroom to recover before QUIC + // refuses new streams. + quic_config.SetMaxIncomingDynamicStreamsToSend(1000); + + return quic_config; +} + +std::unique_ptr<QuicConnection> QuartcFactory::CreateQuicConnection( + Perspective perspective, + const ParsedQuicVersionVector& supported_versions, + QuartcPacketWriter* packet_writer) { + // |dummy_id| and |dummy_address| are used because Quartc network layer will + // not use these two. + char connection_id_bytes[sizeof(uint64_t)] = {}; + QuicConnectionId dummy_id = QuicConnectionId( + static_cast<char*>(connection_id_bytes), sizeof(connection_id_bytes)); + QuicSocketAddress dummy_address(QuicIpAddress::Any4(), /*port=*/0); + return quic::CreateQuicConnection( + dummy_id, dummy_address, connection_helper_.get(), alarm_factory_, + packet_writer, perspective, supported_versions); +} + +std::unique_ptr<QuicConnection> CreateQuicConnection( + QuicConnectionId connection_id, + const QuicSocketAddress& peer_address, + QuicConnectionHelperInterface* connection_helper, + QuicAlarmFactory* alarm_factory, + QuicPacketWriter* packet_writer, + Perspective perspective, + ParsedQuicVersionVector supported_versions) { + auto quic_connection = QuicMakeUnique<QuicConnection>( + connection_id, peer_address, connection_helper, alarm_factory, + packet_writer, + /*owns_writer=*/false, perspective, supported_versions); + + QuicSentPacketManager& sent_packet_manager = + quic_connection->sent_packet_manager(); + + // Default delayed ack time is 25ms. + // If data packets are sent less often (e.g. because p-time was modified), + // we would force acks to be sent every 25ms regardless, increasing + // overhead. Since generally we guarantee a packet every 20ms, changing + // this value should have miniscule effect on quality on good connections, + // but on poor connections, changing this number significantly reduced the + // number of ack-only packets. + // The p-time can go up to as high as 120ms, and when it does, it's + // when the low overhead is the most important thing. Ideally it should be + // above 120ms, but it cannot be higher than 0.5*RTO, which equals to 100ms. + sent_packet_manager.set_delayed_ack_time( + QuicTime::Delta::FromMilliseconds(100)); + + quic_connection->set_fill_up_link_during_probing(true); + + // We start ack decimation after 15 packets. Typically, we would see + // 1-2 crypto handshake packets, one media packet, and 10 probing packets. + // We want to get acks for the probing packets as soon as possible, + // but we can start using ack decimation right after first probing completes. + // The default was to not start ack decimation for the first 100 packets. + quic_connection->set_min_received_before_ack_decimation(15); + + return quic_connection; +} + +std::unique_ptr<QuartcFactory> CreateQuartcFactory( + const QuartcFactoryConfig& factory_config) { + return std::unique_ptr<QuartcFactory>(new QuartcFactory(factory_config)); +} + +} // namespace quic
diff --git a/quic/quartc/quartc_factory.h b/quic/quartc/quartc_factory.h new file mode 100644 index 0000000..5496387 --- /dev/null +++ b/quic/quartc/quartc_factory.h
@@ -0,0 +1,110 @@ +// Copyright (c) 2017 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. + +#ifndef QUICHE_QUIC_QUARTC_QUARTC_FACTORY_H_ +#define QUICHE_QUIC_QUARTC_QUARTC_FACTORY_H_ + +#include "net/third_party/quiche/src/quic/core/quic_alarm_factory.h" +#include "net/third_party/quiche/src/quic/core/quic_connection.h" +#include "net/third_party/quiche/src/quic/core/quic_simple_buffer_allocator.h" +#include "net/third_party/quiche/src/quic/quartc/quartc_packet_writer.h" +#include "net/third_party/quiche/src/quic/quartc/quartc_session.h" + +namespace quic { + +// The configuration for creating a QuartcFactory. +struct QuartcFactoryConfig { + // Factory for |QuicAlarm|s. Implemented by the Quartc user with different + // mechanisms. For example in WebRTC, it is implemented with rtc::Thread. + // Owned by the user, and needs to stay alive for as long as the QuartcFactory + // exists. + QuicAlarmFactory* alarm_factory = nullptr; + // The clock used by |QuicAlarm|s. Implemented by the Quartc user. Owned by + // the user, and needs to stay alive for as long as the QuartcFactory exists. + const QuicClock* clock = nullptr; +}; + +struct QuartcSessionConfig { + // If a pre-shared cryptographic key is available for this session, specify it + // here. This value will only be used if non-empty. + QuicString pre_shared_key; + + // The maximum size of the packet can be written with the packet writer. + // 1200 bytes by default. + QuicPacketLength max_packet_size = 1200; + + // Timeouts for the crypto handshake. Set them to higher values to + // prevent closing the session before it started on a slow network. + // Zero entries are ignored and QUIC defaults are used in that case. + QuicTime::Delta max_idle_time_before_crypto_handshake = + QuicTime::Delta::Zero(); + QuicTime::Delta max_time_before_crypto_handshake = QuicTime::Delta::Zero(); + QuicTime::Delta idle_network_timeout = QuicTime::Delta::Zero(); + + // Tail loss probes (TLP) are enabled by default, but it may be useful to + // disable them in tests. We can also consider disabling them in production + // if we discover that tail loss probes add overhead in low bitrate audio. + bool enable_tail_loss_probe = true; +}; + +// Factory that creates instances of QuartcSession. Implements the +// QuicConnectionHelperInterface used by the QuicConnections. Only one +// QuartcFactory is expected to be created. +class QuartcFactory { + public: + explicit QuartcFactory(const QuartcFactoryConfig& factory_config); + + // Creates a new QuartcSession using the given configuration. + std::unique_ptr<QuartcSession> CreateQuartcClientSession( + const QuartcSessionConfig& quartc_session_config, + const ParsedQuicVersionVector& supported_versions, + QuicStringPiece server_crypto_config, + QuartcPacketTransport* packet_transport); + + private: + std::unique_ptr<QuicConnection> CreateQuicConnection( + Perspective perspective, + const ParsedQuicVersionVector& supported_versions, + QuartcPacketWriter* packet_writer); + + // Used to implement QuicAlarmFactory. Owned by the user and must outlive + // QuartcFactory. + QuicAlarmFactory* alarm_factory_; + // Used to implement the QuicConnectionHelperInterface. Owned by the user and + // must outlive QuartcFactory. + const QuicClock* clock_; + + // Helper used by all QuicConnections. + std::unique_ptr<QuicConnectionHelperInterface> connection_helper_; + + // Used by QuicCryptoServerStream to track most recently compressed certs. + std::unique_ptr<QuicCompressedCertsCache> compressed_certs_cache_; + + // This helper is needed to create QuicCryptoServerStreams. + std::unique_ptr<QuicCryptoServerStream::Helper> stream_helper_; +}; + +// Configures global settings, such as supported quic versions. +// Must execute on QUIC thread. +void ConfigureGlobalQuicSettings(); + +// Must execute on QUIC thread. +QuicConfig CreateQuicConfig(const QuartcSessionConfig& quartc_session_config); + +std::unique_ptr<QuicConnection> CreateQuicConnection( + QuicConnectionId connection_id, + const QuicSocketAddress& peer_address, + QuicConnectionHelperInterface* connection_helper, + QuicAlarmFactory* alarm_factory, + QuicPacketWriter* packet_writer, + Perspective perspective, + ParsedQuicVersionVector supported_versions); + +// Creates a new instance of QuartcFactory. +std::unique_ptr<QuartcFactory> CreateQuartcFactory( + const QuartcFactoryConfig& factory_config); + +} // namespace quic + +#endif // QUICHE_QUIC_QUARTC_QUARTC_FACTORY_H_
diff --git a/quic/quartc/quartc_fakes.h b/quic/quartc/quartc_fakes.h new file mode 100644 index 0000000..16ff9c7 --- /dev/null +++ b/quic/quartc/quartc_fakes.h
@@ -0,0 +1,138 @@ +// Copyright (c) 2017 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. + +#ifndef QUICHE_QUIC_QUARTC_QUARTC_FAKES_H_ +#define QUICHE_QUIC_QUARTC_QUARTC_FAKES_H_ + +#include "net/third_party/quiche/src/quic/core/quic_error_codes.h" +#include "net/third_party/quiche/src/quic/core/quic_types.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_clock.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_string.h" +#include "net/third_party/quiche/src/quic/quartc/quartc_endpoint.h" +#include "net/third_party/quiche/src/quic/quartc/quartc_session.h" +#include "net/third_party/quiche/src/quic/quartc/quartc_stream.h" + +namespace quic { + +class FakeQuartcEndpointDelegate : public QuartcEndpoint::Delegate { + public: + explicit FakeQuartcEndpointDelegate(QuartcSession::Delegate* session_delegate) + : session_delegate_(session_delegate) {} + + void OnSessionCreated(QuartcSession* session) override { + CHECK_EQ(session_, nullptr); + CHECK_NE(session, nullptr); + session_ = session; + session_->SetDelegate(session_delegate_); + session_->StartCryptoHandshake(); + } + + void OnConnectError(QuicErrorCode error, + const QuicString& error_details) override { + LOG(FATAL) << "Unexpected error during QuartcEndpoint::Connect(); error=" + << error << ", error_details=" << error_details; + } + + QuartcSession* session() { return session_; } + + private: + QuartcSession::Delegate* session_delegate_; + QuartcSession* session_ = nullptr; +}; + +class FakeQuartcSessionDelegate : public QuartcSession::Delegate { + public: + explicit FakeQuartcSessionDelegate(QuartcStream::Delegate* stream_delegate, + const QuicClock* clock) + : stream_delegate_(stream_delegate), clock_(clock) {} + + void OnConnectionWritable() override { + LOG(INFO) << "Connection writable!"; + if (!writable_time_.IsInitialized()) { + writable_time_ = clock_->Now(); + } + } + + // Called when peers have established forward-secure encryption + void OnCryptoHandshakeComplete() override { + LOG(INFO) << "Crypto handshake complete!"; + crypto_handshake_time_ = clock_->Now(); + } + + // Called when connection closes locally, or remotely by peer. + void OnConnectionClosed(QuicErrorCode error_code, + const QuicString& error_details, + ConnectionCloseSource source) override { + connected_ = false; + } + + // Called when an incoming QUIC stream is created. + void OnIncomingStream(QuartcStream* quartc_stream) override { + last_incoming_stream_ = quartc_stream; + last_incoming_stream_->SetDelegate(stream_delegate_); + } + + void OnMessageReceived(QuicStringPiece message) override { + incoming_messages_.emplace_back(message); + } + + void OnCongestionControlChange(QuicBandwidth bandwidth_estimate, + QuicBandwidth pacing_rate, + QuicTime::Delta latest_rtt) override {} + + QuartcStream* last_incoming_stream() { return last_incoming_stream_; } + + // Returns all received messages. + const std::vector<QuicString>& incoming_messages() { + return incoming_messages_; + } + + bool connected() { return connected_; } + QuicTime writable_time() const { return writable_time_; } + QuicTime crypto_handshake_time() const { return crypto_handshake_time_; } + + private: + QuartcStream* last_incoming_stream_; + std::vector<QuicString> incoming_messages_; + bool connected_ = true; + QuartcStream::Delegate* stream_delegate_; + QuicTime writable_time_ = QuicTime::Zero(); + QuicTime crypto_handshake_time_ = QuicTime::Zero(); + const QuicClock* clock_; +}; + +class FakeQuartcStreamDelegate : public QuartcStream::Delegate { + public: + size_t OnReceived(QuartcStream* stream, + iovec* iov, + size_t iov_length, + bool fin) override { + size_t bytes_consumed = 0; + for (size_t i = 0; i < iov_length; ++i) { + received_data_[stream->id()] += + QuicString(static_cast<const char*>(iov[i].iov_base), iov[i].iov_len); + bytes_consumed += iov[i].iov_len; + } + return bytes_consumed; + } + + void OnClose(QuartcStream* stream) override { + errors_[stream->id()] = stream->stream_error(); + } + + void OnBufferChanged(QuartcStream* stream) override {} + + bool has_data() { return !received_data_.empty(); } + std::map<QuicStreamId, QuicString> data() { return received_data_; } + + QuicRstStreamErrorCode stream_error(QuicStreamId id) { return errors_[id]; } + + private: + std::map<QuicStreamId, QuicString> received_data_; + std::map<QuicStreamId, QuicRstStreamErrorCode> errors_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_QUARTC_QUARTC_FAKES_H_
diff --git a/quic/quartc/quartc_interval_counter.h b/quic/quartc/quartc_interval_counter.h new file mode 100644 index 0000000..fe3b083 --- /dev/null +++ b/quic/quartc/quartc_interval_counter.h
@@ -0,0 +1,122 @@ +// Copyright (c) 2018 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. + +#ifndef QUICHE_QUIC_QUARTC_QUARTC_INTERVAL_COUNTER_H_ +#define QUICHE_QUIC_QUARTC_QUARTC_INTERVAL_COUNTER_H_ + +#include <stddef.h> +#include <vector> + +#include "net/third_party/quiche/src/quic/core/quic_interval.h" +#include "net/third_party/quiche/src/quic/core/quic_interval_set.h" + +namespace quic { + +// QuartcIntervalCounter counts the number of times each value appears within +// a set of potentially overlapping intervals. +// +// QuartcIntervalCounter is not intended for widespread use. Consider replacing +// it with a full interval-map if more use cases arise. +// +// QuartcIntervalCounter is only suitable for cases where the maximum count is +// expected to remain low. (For example, counting the number of times the same +// portions of stream data are lost.) It is inefficient when the maximum count +// becomes high. +template <typename T> +class QuartcIntervalCounter { + public: + // Adds |interval| to the counter. The count associated with each value in + // |interval| is incremented by one. |interval| may overlap with previous + // intervals added to the counter. + // + // For each possible value: + // - If the value is present in both |interval| and the counter, the count + // associated with that value is incremented by one. + // - If the value is present in |interval| but not counter, the count + // associated with that value is set to one (incremented from zero). + // - If the value is absent from |interval|, the count is unchanged. + // + // Time complexity is O(|MaxCount| * the complexity of adding an interval to a + // QuicIntervalSet). + void AddInterval(QuicInterval<T> interval); + + // Removes an interval from the counter. This method may be called to prune + // irrelevant intervals from the counter. This is useful to prevent unbounded + // growth. + // + // Time complexity is O(|MaxCount| * the complexity of removing an interval + // from a QuicIntervalSet). + void RemoveInterval(QuicInterval<T> interval); + + // Returns the maximum number of times any single value has appeared in + // intervals added to the counter. + // + // Time complexity is constant. + size_t MaxCount() const { return intervals_by_count_.size(); } + + // Returns the maximum number of times a particular value has appeared in + // intervals added to the counter. + // + // Time complexity is O(|MaxCount| * log(number of non-contiguous intervals)). + size_t Count(const T& value) const; + + private: + // Each entry in this vector represents the intervals of values counted at + // least i + 1 times, where i is the index of the entry. + // + // Whenever an interval is added to the counter, each value in the interval is + // added to the first entry which does not already contain that value. If + // part of an interval is already present in the last entry, a new entry is + // added containing that part. + // + // Note that this means each value present in one of the interval sets will be + // present in all previous sets. + std::vector<QuicIntervalSet<T>> intervals_by_count_; +}; + +template <typename T> +void QuartcIntervalCounter<T>::AddInterval(QuicInterval<T> interval) { + // After the Nth iteration, |leftover| contains the parts of |interval| that + // are already present in the first N entries. These parts of |interval| have + // been added to the counter more than N times. + QuicIntervalSet<T> leftover(interval); + for (auto& intervals : intervals_by_count_) { + QuicIntervalSet<T> tmp = leftover; + leftover.Intersection(intervals); + intervals.Union(tmp); + } + + // Whatever ranges are still in |leftover| are already in all the entries + // Add a new entry containing |leftover|. + if (!leftover.Empty()) { + intervals_by_count_.push_back(leftover); + } +} + +template <typename T> +void QuartcIntervalCounter<T>::RemoveInterval(QuicInterval<T> interval) { + // Remove the interval from each entry in the vector, popping any entries that + // become empty. + for (size_t i = intervals_by_count_.size(); i > 0; --i) { + intervals_by_count_[i - 1].Difference(interval); + if (intervals_by_count_[i - 1].Empty()) { + intervals_by_count_.pop_back(); + } + } +} + +template <typename T> +size_t QuartcIntervalCounter<T>::Count(const T& value) const { + // The index of the last entry containing |value| gives its count. + for (size_t i = intervals_by_count_.size(); i > 0; --i) { + if (intervals_by_count_[i - 1].Contains(value)) { + return i; + } + } + return 0; +} + +} // namespace quic + +#endif // QUICHE_QUIC_QUARTC_QUARTC_INTERVAL_COUNTER_H_
diff --git a/quic/quartc/quartc_interval_counter_test.cc b/quic/quartc/quartc_interval_counter_test.cc new file mode 100644 index 0000000..9bc9af5 --- /dev/null +++ b/quic/quartc/quartc_interval_counter_test.cc
@@ -0,0 +1,110 @@ +// Copyright (c) 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/quic/quartc/quartc_interval_counter.h" + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/quic/core/quic_interval.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_test.h" + +namespace quic { +namespace { + +class QuartcIntervalCounterTest : public QuicTest { + protected: + QuartcIntervalCounter<int> counter_; +}; + +void ExpectCount(const QuartcIntervalCounter<int>& counter, + QuicInterval<int> interval, + size_t count) { + for (int i = interval.min(); i < interval.max(); ++i) { + EXPECT_EQ(counter.Count(i), count) << "i=" << i; + } +} + +TEST_F(QuartcIntervalCounterTest, InitiallyEmpty) { + EXPECT_EQ(counter_.MaxCount(), 0u); +} + +TEST_F(QuartcIntervalCounterTest, SameInterval) { + counter_.AddInterval(QuicInterval<int>(0, 6)); + EXPECT_EQ(counter_.MaxCount(), 1u); + ExpectCount(counter_, QuicInterval<int>(0, 6), 1); + + counter_.AddInterval(QuicInterval<int>(0, 6)); + EXPECT_EQ(counter_.MaxCount(), 2u); + ExpectCount(counter_, QuicInterval<int>(0, 6), 2); +} + +TEST_F(QuartcIntervalCounterTest, DisjointIntervals) { + counter_.AddInterval(QuicInterval<int>(0, 5)); + EXPECT_EQ(counter_.MaxCount(), 1u); + ExpectCount(counter_, QuicInterval<int>(0, 5), 1); + ExpectCount(counter_, QuicInterval<int>(5, 10), 0); + + counter_.AddInterval(QuicInterval<int>(5, 10)); + EXPECT_EQ(counter_.MaxCount(), 1u); + ExpectCount(counter_, QuicInterval<int>(0, 5), 1); + ExpectCount(counter_, QuicInterval<int>(5, 10), 1); +} + +TEST_F(QuartcIntervalCounterTest, OverlappingIntervals) { + counter_.AddInterval(QuicInterval<int>(0, 6)); + EXPECT_EQ(counter_.MaxCount(), 1u); + ExpectCount(counter_, QuicInterval<int>(0, 6), 1); + ExpectCount(counter_, QuicInterval<int>(6, 10), 0); + + counter_.AddInterval(QuicInterval<int>(5, 10)); + EXPECT_EQ(counter_.MaxCount(), 2u); + ExpectCount(counter_, QuicInterval<int>(0, 5), 1); + EXPECT_EQ(counter_.Count(5), 2u); + ExpectCount(counter_, QuicInterval<int>(6, 10), 1); +} + +TEST_F(QuartcIntervalCounterTest, IntervalsWithGapThenOverlap) { + counter_.AddInterval(QuicInterval<int>(0, 4)); + EXPECT_EQ(counter_.MaxCount(), 1u); + ExpectCount(counter_, QuicInterval<int>(0, 4), 1); + ExpectCount(counter_, QuicInterval<int>(4, 10), 0); + + counter_.AddInterval(QuicInterval<int>(7, 10)); + EXPECT_EQ(counter_.MaxCount(), 1u); + ExpectCount(counter_, QuicInterval<int>(0, 4), 1); + ExpectCount(counter_, QuicInterval<int>(4, 7), 0); + ExpectCount(counter_, QuicInterval<int>(7, 10), 1); + + counter_.AddInterval(QuicInterval<int>(3, 8)); + EXPECT_EQ(counter_.MaxCount(), 2u); + ExpectCount(counter_, QuicInterval<int>(0, 3), 1); + EXPECT_EQ(counter_.Count(3), 2u); + ExpectCount(counter_, QuicInterval<int>(4, 7), 1); + EXPECT_EQ(counter_.Count(7), 2u); + ExpectCount(counter_, QuicInterval<int>(8, 10), 1); +} + +TEST_F(QuartcIntervalCounterTest, RemoveIntervals) { + counter_.AddInterval(QuicInterval<int>(0, 5)); + EXPECT_EQ(counter_.MaxCount(), 1u); + ExpectCount(counter_, QuicInterval<int>(0, 5), 1); + + counter_.AddInterval(QuicInterval<int>(4, 10)); + EXPECT_EQ(counter_.MaxCount(), 2u); + ExpectCount(counter_, QuicInterval<int>(0, 4), 1); + EXPECT_EQ(counter_.Count(4), 2u); + ExpectCount(counter_, QuicInterval<int>(5, 10), 1); + + counter_.RemoveInterval(QuicInterval<int>(0, 5)); + EXPECT_EQ(counter_.MaxCount(), 1u); + ExpectCount(counter_, QuicInterval<int>(0, 5), 0); + ExpectCount(counter_, QuicInterval<int>(5, 10), 1); + + counter_.RemoveInterval(QuicInterval<int>(5, 10)); + EXPECT_EQ(counter_.MaxCount(), 0u); + ExpectCount(counter_, QuicInterval<int>(0, 10), 0); +} + +} // namespace +} // namespace quic
diff --git a/quic/quartc/quartc_packet_writer.cc b/quic/quartc/quartc_packet_writer.cc new file mode 100644 index 0000000..64a72a8 --- /dev/null +++ b/quic/quartc/quartc_packet_writer.cc
@@ -0,0 +1,76 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/quic/quartc/quartc_packet_writer.h" + +namespace quic { + +std::unique_ptr<PerPacketOptions> QuartcPerPacketOptions::Clone() const { + return QuicMakeUnique<QuartcPerPacketOptions>(*this); +} + +QuartcPacketWriter::QuartcPacketWriter(QuartcPacketTransport* packet_transport, + QuicByteCount max_packet_size) + : packet_transport_(packet_transport), max_packet_size_(max_packet_size) {} + +WriteResult QuartcPacketWriter::WritePacket( + const char* buffer, + size_t buf_len, + const QuicIpAddress& self_address, + const QuicSocketAddress& peer_address, + PerPacketOptions* options) { + DCHECK(packet_transport_); + + QuartcPacketTransport::PacketInfo info; + QuartcPerPacketOptions* quartc_options = + static_cast<QuartcPerPacketOptions*>(options); + if (quartc_options && quartc_options->connection) { + info.packet_number = + quartc_options->connection->packet_generator().packet_number(); + } + int bytes_written = packet_transport_->Write(buffer, buf_len, info); + if (bytes_written <= 0) { + writable_ = false; + return WriteResult(WRITE_STATUS_BLOCKED, EWOULDBLOCK); + } + return WriteResult(WRITE_STATUS_OK, bytes_written); +} + +bool QuartcPacketWriter::IsWriteBlocked() const { + return !writable_; +} + +QuicByteCount QuartcPacketWriter::GetMaxPacketSize( + const QuicSocketAddress& peer_address) const { + return max_packet_size_; +} + +void QuartcPacketWriter::SetWritable() { + writable_ = true; +} + +bool QuartcPacketWriter::SupportsReleaseTime() const { + return false; +} + +bool QuartcPacketWriter::IsBatchMode() const { + return false; +} + +char* QuartcPacketWriter::GetNextWriteLocation( + const QuicIpAddress& self_address, + const QuicSocketAddress& peer_address) { + return nullptr; +} + +WriteResult QuartcPacketWriter::Flush() { + return WriteResult(WRITE_STATUS_OK, 0); +} + +void QuartcPacketWriter::SetPacketTransportDelegate( + QuartcPacketTransport::Delegate* delegate) { + packet_transport_->SetDelegate(delegate); +} + +} // namespace quic
diff --git a/quic/quartc/quartc_packet_writer.h b/quic/quartc/quartc_packet_writer.h new file mode 100644 index 0000000..f3eb885 --- /dev/null +++ b/quic/quartc/quartc_packet_writer.h
@@ -0,0 +1,113 @@ +// Copyright (c) 2017 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. + +#ifndef QUICHE_QUIC_QUARTC_QUARTC_PACKET_WRITER_H_ +#define QUICHE_QUIC_QUARTC_QUARTC_PACKET_WRITER_H_ + +#include "net/third_party/quiche/src/quic/core/quic_connection.h" +#include "net/third_party/quiche/src/quic/core/quic_packet_writer.h" +#include "net/third_party/quiche/src/quic/core/quic_types.h" + +namespace quic { + +// Send and receive packets, like a virtual UDP socket. For example, this +// could be implemented by WebRTC's IceTransport. +class QuartcPacketTransport { + public: + // Additional metadata provided for each packet written. + struct PacketInfo { + QuicPacketNumber packet_number; + }; + + // Delegate for packet transport callbacks. Note that the delegate is not + // thread-safe. Packet transport implementations must ensure that callbacks + // are synchronized with all other work done by QUIC. + class Delegate { + public: + virtual ~Delegate() = default; + + // Called whenever the transport can write. + virtual void OnTransportCanWrite() = 0; + + // Called when the transport receives a packet. + virtual void OnTransportReceived(const char* data, size_t data_len) = 0; + }; + + virtual ~QuartcPacketTransport() {} + + // Called by the QuartcPacketWriter when writing packets to the network. + // Return the number of written bytes. Return 0 if the write is blocked. + virtual int Write(const char* buffer, + size_t buf_len, + const PacketInfo& info) = 0; + + // Sets the delegate which must be called when the transport can write or + // a packet is received. QUIC sets |delegate| to a nonnull pointer when it + // is ready to process incoming packets and sets |delegate| to nullptr before + // QUIC is deleted. Implementations may assume |delegate| remains valid until + // it is set to nullptr. + virtual void SetDelegate(Delegate* delegate) = 0; +}; + +struct QuartcPerPacketOptions : public PerPacketOptions { + std::unique_ptr<PerPacketOptions> Clone() const override; + + // The connection which is sending this packet. + QuicConnection* connection = nullptr; +}; + +// Implements a QuicPacketWriter using a QuartcPacketTransport, which allows a +// QuicConnection to use (for example), a WebRTC IceTransport. +class QuartcPacketWriter : public QuicPacketWriter { + public: + QuartcPacketWriter(QuartcPacketTransport* packet_transport, + QuicByteCount max_packet_size); + ~QuartcPacketWriter() override {} + + // The QuicConnection calls WritePacket and the QuicPacketWriter writes them + // to the QuartcSession::PacketTransport. + WriteResult WritePacket(const char* buffer, + size_t buf_len, + const QuicIpAddress& self_address, + const QuicSocketAddress& peer_address, + PerPacketOptions* options) override; + + // Whether the underneath |transport_| is blocked. If this returns true, + // outgoing QUIC packets are queued by QuicConnection until SetWritable() is + // called. + bool IsWriteBlocked() const override; + + // Maximum size of the QUIC packet which can be written. Users such as WebRTC + // can set the value through the QuartcFactoryConfig without updating the QUIC + // code. + QuicByteCount GetMaxPacketSize( + const QuicSocketAddress& peer_address) const override; + + // Sets the packet writer to a writable (non-blocked) state. + void SetWritable() override; + + bool SupportsReleaseTime() const override; + + bool IsBatchMode() const override; + + char* GetNextWriteLocation(const QuicIpAddress& self_address, + const QuicSocketAddress& peer_address) override; + + WriteResult Flush() override; + + void SetPacketTransportDelegate(QuartcPacketTransport::Delegate* delegate); + + private: + // QuartcPacketWriter will not own the transport. + QuartcPacketTransport* packet_transport_; + // The maximum size of the packet can be written by this writer. + QuicByteCount max_packet_size_; + + // Whether packets can be written. + bool writable_ = false; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_QUARTC_QUARTC_PACKET_WRITER_H_
diff --git a/quic/quartc/quartc_session.cc b/quic/quartc/quartc_session.cc new file mode 100644 index 0000000..8fa8e96 --- /dev/null +++ b/quic/quartc/quartc_session.cc
@@ -0,0 +1,397 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/quic/quartc/quartc_session.h" + +#include "net/third_party/quiche/src/quic/core/quic_utils.h" +#include "net/third_party/quiche/src/quic/core/tls_client_handshaker.h" +#include "net/third_party/quiche/src/quic/core/tls_server_handshaker.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_mem_slice_storage.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h" +#include "net/third_party/quiche/src/quic/quartc/quartc_crypto_helpers.h" + +namespace quic { +namespace { + +// Arbitrary server port number for net::QuicCryptoClientConfig. +const int kQuicServerPort = 0; + +} // namespace + +QuartcSession::QuartcSession(std::unique_ptr<QuicConnection> connection, + Visitor* visitor, + const QuicConfig& config, + const ParsedQuicVersionVector& supported_versions, + const QuicClock* clock) + : QuicSession(connection.get(), visitor, config, supported_versions), + connection_(std::move(connection)), + clock_(clock), + per_packet_options_(QuicMakeUnique<QuartcPerPacketOptions>()) { + per_packet_options_->connection = connection_.get(); + connection_->set_per_packet_options(per_packet_options_.get()); +} + +QuartcSession::~QuartcSession() {} + +QuartcStream* QuartcSession::CreateOutgoingBidirectionalStream() { + // Use default priority for incoming QUIC streams. + // TODO(zhihuang): Determine if this value is correct. + return ActivateDataStream(CreateDataStream( + GetNextOutgoingBidirectionalStreamId(), QuicStream::kDefaultPriority)); +} + +bool QuartcSession::SendOrQueueMessage(QuicString message) { + if (!CanSendMessage()) { + QUIC_LOG(ERROR) << "Quic session does not support SendMessage"; + return false; + } + + if (message.size() > GetLargestMessagePayload()) { + QUIC_LOG(ERROR) << "Message is too big, message_size=" << message.size() + << ", GetLargestMessagePayload=" + << GetLargestMessagePayload(); + return false; + } + + // There may be other messages in send queue, so we have to add message + // to the queue and call queue processing helper. + send_message_queue_.emplace_back(std::move(message)); + + ProcessSendMessageQueue(); + + return true; +} + +void QuartcSession::ProcessSendMessageQueue() { + while (!send_message_queue_.empty()) { + struct iovec iov = {const_cast<char*>(send_message_queue_.front().data()), + send_message_queue_.front().length()}; + QuicMemSliceStorage storage( + &iov, 1, connection()->helper()->GetStreamSendBufferAllocator(), + send_message_queue_.front().length()); + MessageResult result = SendMessage(storage.ToSpan()); + + const size_t message_size = send_message_queue_.front().size(); + + // Handle errors. + switch (result.status) { + case MESSAGE_STATUS_SUCCESS: + QUIC_VLOG(1) << "Quartc message sent, message_id=" << result.message_id + << ", message_size=" << message_size; + break; + + // If connection is congestion controlled or not writable yet, stop + // send loop and we'll retry again when we get OnCanWrite notification. + case MESSAGE_STATUS_ENCRYPTION_NOT_ESTABLISHED: + case MESSAGE_STATUS_BLOCKED: + QUIC_VLOG(1) << "Quartc message not sent because connection is blocked" + << ", message will be retried later, status=" + << result.status << ", message_size=" << message_size; + + return; + + // Other errors are unexpected. We do not propagate error to Quartc, + // because writes can be delayed. + case MESSAGE_STATUS_UNSUPPORTED: + case MESSAGE_STATUS_TOO_LARGE: + case MESSAGE_STATUS_INTERNAL_ERROR: + QUIC_DLOG(DFATAL) + << "Failed to send quartc message due to unexpected error" + << ", message will not be retried, status=" << result.status + << ", message_size=" << message_size; + break; + } + + send_message_queue_.pop_front(); + } +} + +void QuartcSession::OnCanWrite() { + // TODO(b/119640244): Since we currently use messages for audio and streams + // for video, it makes sense to process queued messages first, then call quic + // core OnCanWrite, which will resend queued streams. Long term we may need + // better solution especially if quic connection is used for both data and + // media. + + // Process quartc messages that were previously blocked. + ProcessSendMessageQueue(); + + QuicSession::OnCanWrite(); +} + +void QuartcSession::OnCryptoHandshakeEvent(CryptoHandshakeEvent event) { + QuicSession::OnCryptoHandshakeEvent(event); + switch (event) { + case ENCRYPTION_FIRST_ESTABLISHED: + case ENCRYPTION_REESTABLISHED: + // 1-rtt setup triggers 'ENCRYPTION_REESTABLISHED' (after REJ, when the + // CHLO is sent). + DCHECK(IsEncryptionEstablished()); + DCHECK(session_delegate_); + session_delegate_->OnConnectionWritable(); + break; + case HANDSHAKE_CONFIRMED: + // On the server, handshake confirmed is the first time when you can start + // writing packets. + DCHECK(IsEncryptionEstablished()); + DCHECK(IsCryptoHandshakeConfirmed()); + + DCHECK(session_delegate_); + session_delegate_->OnConnectionWritable(); + session_delegate_->OnCryptoHandshakeComplete(); + break; + } +} + +void QuartcSession::CancelStream(QuicStreamId stream_id) { + ResetStream(stream_id, QuicRstStreamErrorCode::QUIC_STREAM_CANCELLED); +} + +void QuartcSession::ResetStream(QuicStreamId stream_id, + QuicRstStreamErrorCode error) { + if (!IsOpenStream(stream_id)) { + return; + } + QuicStream* stream = QuicSession::GetOrCreateStream(stream_id); + if (stream) { + stream->Reset(error); + } +} + +void QuartcSession::OnCongestionWindowChange(QuicTime now) { + DCHECK(session_delegate_); + const RttStats* rtt_stats = connection_->sent_packet_manager().GetRttStats(); + + QuicBandwidth bandwidth_estimate = + connection_->sent_packet_manager().BandwidthEstimate(); + + QuicByteCount in_flight = + connection_->sent_packet_manager().GetBytesInFlight(); + QuicBandwidth pacing_rate = + connection_->sent_packet_manager().GetSendAlgorithm()->PacingRate( + in_flight); + + session_delegate_->OnCongestionControlChange(bandwidth_estimate, pacing_rate, + rtt_stats->latest_rtt()); +} + +bool QuartcSession::ShouldKeepConnectionAlive() const { + // TODO(mellem): Quartc may want different keepalive logic than HTTP. + return GetNumOpenDynamicStreams() > 0; +} + +void QuartcSession::OnConnectionClosed(QuicErrorCode error, + const QuicString& error_details, + ConnectionCloseSource source) { + QuicSession::OnConnectionClosed(error, error_details, source); + DCHECK(session_delegate_); + session_delegate_->OnConnectionClosed(error, error_details, source); +} + +void QuartcSession::CloseConnection(const QuicString& details) { + connection_->CloseConnection( + QuicErrorCode::QUIC_CONNECTION_CANCELLED, details, + ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET_WITH_NO_ACK); +} + +void QuartcSession::SetDelegate(Delegate* session_delegate) { + if (session_delegate_) { + LOG(WARNING) << "The delegate for the session has already been set."; + } + session_delegate_ = session_delegate; + DCHECK(session_delegate_); +} + +void QuartcSession::OnTransportCanWrite() { + connection()->writer()->SetWritable(); + if (HasDataToWrite()) { + connection()->OnCanWrite(); + } +} + +void QuartcSession::OnTransportReceived(const char* data, size_t data_len) { + QuicReceivedPacket packet(data, data_len, clock_->Now()); + ProcessUdpPacket(connection()->self_address(), connection()->peer_address(), + packet); +} + +void QuartcSession::OnMessageReceived(QuicStringPiece message) { + session_delegate_->OnMessageReceived(message); +} + +QuicStream* QuartcSession::CreateIncomingStream(QuicStreamId id) { + return ActivateDataStream(CreateDataStream(id, QuicStream::kDefaultPriority)); +} + +QuicStream* QuartcSession::CreateIncomingStream(PendingStream pending) { + return ActivateDataStream( + CreateDataStream(std::move(pending), QuicStream::kDefaultPriority)); +} + +std::unique_ptr<QuartcStream> QuartcSession::CreateDataStream( + QuicStreamId id, + spdy::SpdyPriority priority) { + if (GetCryptoStream() == nullptr || + !GetCryptoStream()->encryption_established()) { + // Encryption not active so no stream created + return nullptr; + } + return InitializeDataStream(QuicMakeUnique<QuartcStream>(id, this), priority); +} + +std::unique_ptr<QuartcStream> QuartcSession::CreateDataStream( + PendingStream pending, + spdy::SpdyPriority priority) { + return InitializeDataStream(QuicMakeUnique<QuartcStream>(std::move(pending)), + priority); +} + +std::unique_ptr<QuartcStream> QuartcSession::InitializeDataStream( + std::unique_ptr<QuartcStream> stream, + spdy::SpdyPriority priority) { + // Register the stream to the QuicWriteBlockedList. |priority| is clamped + // between 0 and 7, with 0 being the highest priority and 7 the lowest + // priority. + write_blocked_streams()->UpdateStreamPriority(stream->id(), priority); + + if (IsIncomingStream(stream->id())) { + DCHECK(session_delegate_); + // Incoming streams need to be registered with the session_delegate_. + session_delegate_->OnIncomingStream(stream.get()); + } + return stream; +} + +QuartcStream* QuartcSession::ActivateDataStream( + std::unique_ptr<QuartcStream> stream) { + // Transfer ownership of the data stream to the session via ActivateStream(). + QuartcStream* raw = stream.release(); + if (raw) { + // Make QuicSession take ownership of the stream. + ActivateStream(std::unique_ptr<QuicStream>(raw)); + } + return raw; +} + +QuartcClientSession::QuartcClientSession( + std::unique_ptr<QuicConnection> connection, + const QuicConfig& config, + const ParsedQuicVersionVector& supported_versions, + const QuicClock* clock, + std::unique_ptr<QuartcPacketWriter> packet_writer, + std::unique_ptr<QuicCryptoClientConfig> client_crypto_config, + QuicStringPiece server_crypto_config) + : QuartcSession(std::move(connection), + /*visitor=*/nullptr, + config, + supported_versions, + clock), + packet_writer_(std::move(packet_writer)), + client_crypto_config_(std::move(client_crypto_config)), + server_config_(server_crypto_config) { + DCHECK_EQ(QuartcSession::connection()->perspective(), Perspective::IS_CLIENT); +} + +QuartcClientSession::~QuartcClientSession() { + // The client session is the packet transport delegate, so it must be unset + // before the session is deleted. + packet_writer_->SetPacketTransportDelegate(nullptr); +} + +void QuartcClientSession::Initialize() { + DCHECK(crypto_stream_) << "Do not call QuartcSession::Initialize(), call " + "StartCryptoHandshake() instead."; + QuartcSession::Initialize(); + + // QUIC is ready to process incoming packets after Initialize(). + // Set the packet transport delegate to begin receiving packets. + packet_writer_->SetPacketTransportDelegate(this); +} + +const QuicCryptoStream* QuartcClientSession::GetCryptoStream() const { + return crypto_stream_.get(); +} + +QuicCryptoStream* QuartcClientSession::GetMutableCryptoStream() { + return crypto_stream_.get(); +} + +void QuartcClientSession::StartCryptoHandshake() { + QuicServerId server_id(/*host=*/"", kQuicServerPort, + /*privacy_mode_enabled=*/false); + + if (!server_config_.empty()) { + QuicCryptoServerConfig::ConfigOptions options; + + QuicString error; + QuicWallTime now = clock()->WallNow(); + QuicCryptoClientConfig::CachedState::ServerConfigState result = + client_crypto_config_->LookupOrCreate(server_id)->SetServerConfig( + server_config_, now, + /*expiry_time=*/now.Add(QuicTime::Delta::Infinite()), &error); + + if (result == QuicCryptoClientConfig::CachedState::SERVER_CONFIG_VALID) { + DCHECK_EQ(error, ""); + client_crypto_config_->LookupOrCreate(server_id)->SetProof( + std::vector<QuicString>{kDummyCertName}, /*cert_sct=*/"", + /*chlo_hash=*/"", /*signature=*/"anything"); + } else { + LOG(DFATAL) << "Unable to set server config, error=" << error; + } + } + + crypto_stream_ = QuicMakeUnique<QuicCryptoClientStream>( + server_id, this, + client_crypto_config_->proof_verifier()->CreateDefaultContext(), + client_crypto_config_.get(), this); + Initialize(); + crypto_stream_->CryptoConnect(); +} + +void QuartcClientSession::OnProofValid( + const QuicCryptoClientConfig::CachedState& cached) { + // TODO(zhihuang): Handle the proof verification. +} + +void QuartcClientSession::OnProofVerifyDetailsAvailable( + const ProofVerifyDetails& verify_details) { + // TODO(zhihuang): Handle the proof verification. +} + +QuartcServerSession::QuartcServerSession( + std::unique_ptr<QuicConnection> connection, + Visitor* visitor, + const QuicConfig& config, + const ParsedQuicVersionVector& supported_versions, + const QuicClock* clock, + const QuicCryptoServerConfig* server_crypto_config, + QuicCompressedCertsCache* const compressed_certs_cache, + QuicCryptoServerStream::Helper* const stream_helper) + : QuartcSession(std::move(connection), + visitor, + config, + supported_versions, + clock), + server_crypto_config_(server_crypto_config), + compressed_certs_cache_(compressed_certs_cache), + stream_helper_(stream_helper) { + DCHECK_EQ(QuartcSession::connection()->perspective(), Perspective::IS_SERVER); +} + +const QuicCryptoStream* QuartcServerSession::GetCryptoStream() const { + return crypto_stream_.get(); +} + +QuicCryptoStream* QuartcServerSession::GetMutableCryptoStream() { + return crypto_stream_.get(); +} + +void QuartcServerSession::StartCryptoHandshake() { + crypto_stream_ = QuicMakeUnique<QuicCryptoServerStream>( + server_crypto_config_, compressed_certs_cache_, + /*use_stateless_rejects_if_peer_supported=*/false, this, stream_helper_); + Initialize(); +} + +} // namespace quic
diff --git a/quic/quartc/quartc_session.h b/quic/quartc/quartc_session.h new file mode 100644 index 0000000..7efa186 --- /dev/null +++ b/quic/quartc/quartc_session.h
@@ -0,0 +1,284 @@ +// Copyright (c) 2017 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. + +#ifndef QUICHE_QUIC_QUARTC_QUARTC_SESSION_H_ +#define QUICHE_QUIC_QUARTC_QUARTC_SESSION_H_ + +#include <memory> +#include <string> + +#include "net/third_party/quiche/src/quic/core/quic_crypto_client_stream.h" +#include "net/third_party/quiche/src/quic/core/quic_crypto_server_stream.h" +#include "net/third_party/quiche/src/quic/core/quic_crypto_stream.h" +#include "net/third_party/quiche/src/quic/core/quic_error_codes.h" +#include "net/third_party/quiche/src/quic/core/quic_session.h" +#include "net/third_party/quiche/src/quic/core/quic_types.h" +#include "net/third_party/quiche/src/quic/quartc/quartc_packet_writer.h" +#include "net/third_party/quiche/src/quic/quartc/quartc_stream.h" + +namespace quic { + +// QuartcSession owns and manages a QUIC connection. +class QuartcSession : public QuicSession, + public QuartcPacketTransport::Delegate { + public: + QuartcSession(std::unique_ptr<QuicConnection> connection, + Visitor* visitor, + const QuicConfig& config, + const ParsedQuicVersionVector& supported_versions, + const QuicClock* clock); + QuartcSession(const QuartcSession&) = delete; + QuartcSession& operator=(const QuartcSession&) = delete; + ~QuartcSession() override; + + // QuicSession overrides. + QuartcStream* CreateOutgoingBidirectionalStream(); + + // Sends short unreliable message using quic message frame (message must fit + // in one quic packet). If connection is blocked by congestion control, + // message will be queued and resent later after receiving an OnCanWrite + // notification. + // + // Message size must be <= GetLargestMessagePayload(). + // + // Supported in quic version 45 or later. + // + // Returns false and logs error if message is too long or session does not + // support SendMessage API. Other unexpected errors during send will not be + // returned, because messages can be sent later if connection is congestion + // controlled. + bool SendOrQueueMessage(QuicString message); + + // Returns largest message payload acceptable in SendQuartcMessage. + QuicPacketLength GetLargestMessagePayload() const { + return connection()->GetLargestMessagePayload(); + } + + // Return true if transport support message frame. + bool CanSendMessage() const { + return connection()->transport_version() > QUIC_VERSION_44; + } + + void OnCryptoHandshakeEvent(CryptoHandshakeEvent event) override; + + // QuicConnectionVisitorInterface overrides. + void OnCongestionWindowChange(QuicTime now) override; + bool ShouldKeepConnectionAlive() const override; + + void OnCanWrite() override; + + void OnConnectionClosed(QuicErrorCode error, + const QuicString& error_details, + ConnectionCloseSource source) override; + + // QuartcSession methods. + virtual void StartCryptoHandshake() = 0; + + // Closes the connection with the given human-readable error details. + // The connection closes with the QUIC_CONNECTION_CANCELLED error code to + // indicate the application closed it. + // + // Informs the peer that the connection has been closed. This prevents the + // peer from waiting until the connection times out. + // + // Cleans up the underlying QuicConnection's state. Closing the connection + // makes it safe to delete the QuartcSession. + void CloseConnection(const QuicString& details); + + // If the given stream is still open, sends a reset frame to cancel it. + // Note: This method cancels a stream by QuicStreamId rather than by pointer + // (or by a method on QuartcStream) because QuartcSession (and not + // the caller) owns the streams. Streams may finish and be deleted before the + // caller tries to cancel them, rendering the caller's pointers invalid. + void CancelStream(QuicStreamId stream_id); + + // Callbacks called by the QuartcSession to notify the user of the + // QuartcSession of certain events. + class Delegate { + public: + virtual ~Delegate() {} + + // Called when the crypto handshake is complete. Crypto handshake on the + // client is only completed _after_ SHLO is received, but we can actually + // start sending media data right after CHLO is sent. + virtual void OnCryptoHandshakeComplete() = 0; + + // Connection can be writable even before crypto handshake is complete. + // In particular, on the client, we can start sending data after sending + // full CHLO, without waiting for SHLO. This reduces a send delay by 1-rtt. + // + // This may be called multiple times. + virtual void OnConnectionWritable() = 0; + + // Called when a new stream is received from the remote endpoint. + virtual void OnIncomingStream(QuartcStream* stream) = 0; + + // Called when network parameters change in response to an ack frame. + virtual void OnCongestionControlChange(QuicBandwidth bandwidth_estimate, + QuicBandwidth pacing_rate, + QuicTime::Delta latest_rtt) = 0; + + // Called when the connection is closed. This means all of the streams will + // be closed and no new streams can be created. + virtual void OnConnectionClosed(QuicErrorCode error_code, + const QuicString& error_details, + ConnectionCloseSource source) = 0; + + // Called when message (sent as SendMessage) is received. + virtual void OnMessageReceived(QuicStringPiece message) = 0; + + // TODO(zhihuang): Add proof verification. + }; + + // The |delegate| is not owned by QuartcSession. + void SetDelegate(Delegate* session_delegate); + + // Called when CanWrite() changes from false to true. + void OnTransportCanWrite() override; + + // Called when a packet has been received and should be handled by the + // QuicConnection. + void OnTransportReceived(const char* data, size_t data_len) override; + + void OnMessageReceived(QuicStringPiece message) override; + + // Returns number of queued (not sent) messages submitted by + // SendOrQueueMessage. Messages are queued if connection is congestion + // controlled. + size_t send_message_queue_size() const { return send_message_queue_.size(); } + + protected: + // QuicSession override. + QuicStream* CreateIncomingStream(QuicStreamId id) override; + QuicStream* CreateIncomingStream(PendingStream pending) override; + + std::unique_ptr<QuartcStream> CreateDataStream(QuicStreamId id, + spdy::SpdyPriority priority); + std::unique_ptr<QuartcStream> CreateDataStream(PendingStream pending, + spdy::SpdyPriority priority); + // Activates a QuartcStream. The session takes ownership of the stream, but + // returns an unowned pointer to the stream for convenience. + QuartcStream* ActivateDataStream(std::unique_ptr<QuartcStream> stream); + + void ResetStream(QuicStreamId stream_id, QuicRstStreamErrorCode error); + + const QuicClock* clock() { return clock_; } + + private: + std::unique_ptr<QuartcStream> InitializeDataStream( + std::unique_ptr<QuartcStream> stream, + spdy::SpdyPriority priority); + + void ProcessSendMessageQueue(); + + // Take ownership of the QuicConnection. Note: if |connection_| changes, + // the new value of |connection_| must be given to |packet_writer_| before any + // packets are written. Otherwise, |packet_writer_| will crash. + std::unique_ptr<QuicConnection> connection_; + + // For recording packet receipt time + const QuicClock* clock_; + + // Not owned by QuartcSession. + Delegate* session_delegate_ = nullptr; + + // Options passed to the packet writer for each packet. + std::unique_ptr<QuartcPerPacketOptions> per_packet_options_; + + // Queue of pending messages sent by SendQuartcMessage that were not sent + // yet or blocked by congestion control. Messages are queued in the order + // of sent by SendOrQueueMessage(). + QuicDeque<QuicString> send_message_queue_; +}; + +class QuartcClientSession : public QuartcSession, + public QuicCryptoClientStream::ProofHandler { + public: + QuartcClientSession( + std::unique_ptr<QuicConnection> connection, + const QuicConfig& config, + const ParsedQuicVersionVector& supported_versions, + const QuicClock* clock, + std::unique_ptr<QuartcPacketWriter> packet_writer, + std::unique_ptr<QuicCryptoClientConfig> client_crypto_config, + QuicStringPiece server_crypto_config); + QuartcClientSession(const QuartcClientSession&) = delete; + QuartcClientSession& operator=(const QuartcClientSession&) = delete; + + ~QuartcClientSession() override; + + // Initialize should not be called on a QuartcSession. Instead, call + // StartCryptoHandshake(). + // TODO(mellem): Move creation of the crypto stream into Initialize() and + // remove StartCryptoHandshake() to bring QuartcSession in line with other + // implementations of QuicSession, which can be started by calling + // Initialize(). + void Initialize() override; + + // Accessors for the client crypto stream. + QuicCryptoStream* GetMutableCryptoStream() override; + const QuicCryptoStream* GetCryptoStream() const override; + + // Initializes the session and sends a handshake. + void StartCryptoHandshake() override; + + // ProofHandler overrides. + void OnProofValid(const QuicCryptoClientConfig::CachedState& cached) override; + + // Called by the client crypto handshake when proof verification details + // become available, either because proof verification is complete, or when + // cached details are used. + void OnProofVerifyDetailsAvailable( + const ProofVerifyDetails& verify_details) override; + + private: + // Packet writer used by |connection_|. + std::unique_ptr<QuartcPacketWriter> packet_writer_; + + // Config for QUIC crypto stream. + std::unique_ptr<QuicCryptoClientConfig> client_crypto_config_; + + // Client perspective crypto stream. + std::unique_ptr<QuicCryptoClientStream> crypto_stream_; + + const QuicString server_config_; +}; + +class QuartcServerSession : public QuartcSession { + public: + QuartcServerSession(std::unique_ptr<QuicConnection> connection, + Visitor* visitor, + const QuicConfig& config, + const ParsedQuicVersionVector& supported_versions, + const QuicClock* clock, + const QuicCryptoServerConfig* server_crypto_config, + QuicCompressedCertsCache* const compressed_certs_cache, + QuicCryptoServerStream::Helper* const stream_helper); + QuartcServerSession(const QuartcServerSession&) = delete; + QuartcServerSession& operator=(const QuartcServerSession&) = delete; + + // Accessors for the server crypto stream. + QuicCryptoStream* GetMutableCryptoStream() override; + const QuicCryptoStream* GetCryptoStream() const override; + + // Initializes the session and prepares to receive a handshake. + void StartCryptoHandshake() override; + + private: + // Config for QUIC crypto stream. + const QuicCryptoServerConfig* server_crypto_config_; + + // Used by QUIC crypto server stream to track most recently compressed certs. + QuicCompressedCertsCache* const compressed_certs_cache_; + + // This helper is needed to create QuicCryptoServerStream. + QuicCryptoServerStream::Helper* const stream_helper_; + + // Server perspective crypto stream. + std::unique_ptr<QuicCryptoServerStream> crypto_stream_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_QUARTC_QUARTC_SESSION_H_
diff --git a/quic/quartc/quartc_session_test.cc b/quic/quartc/quartc_session_test.cc new file mode 100644 index 0000000..fad4a8f --- /dev/null +++ b/quic/quartc/quartc_session_test.cc
@@ -0,0 +1,559 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/quic/quartc/quartc_session.h" + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/quic/core/quic_simple_buffer_allocator.h" +#include "net/third_party/quiche/src/quic/core/quic_types.h" +#include "net/third_party/quiche/src/quic/core/tls_client_handshaker.h" +#include "net/third_party/quiche/src/quic/core/tls_server_handshaker.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_clock.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_string_utils.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_test.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_test_mem_slice_vector.h" +#include "net/third_party/quiche/src/quic/quartc/counting_packet_filter.h" +#include "net/third_party/quiche/src/quic/quartc/quartc_endpoint.h" +#include "net/third_party/quiche/src/quic/quartc/quartc_fakes.h" +#include "net/third_party/quiche/src/quic/quartc/quartc_packet_writer.h" +#include "net/third_party/quiche/src/quic/quartc/simulated_packet_transport.h" +#include "net/third_party/quiche/src/quic/test_tools/mock_clock.h" +#include "net/third_party/quiche/src/quic/test_tools/simulator/packet_filter.h" +#include "net/third_party/quiche/src/quic/test_tools/simulator/simulator.h" + +namespace quic { + +namespace { + +constexpr QuicTime::Delta kPropagationDelay = + QuicTime::Delta::FromMilliseconds(10); +// Propagation delay and a bit, but no more than full RTT. +constexpr QuicTime::Delta kPropagationDelayAndABit = + QuicTime::Delta::FromMilliseconds(12); + +static QuicByteCount kDefaultMaxPacketSize = 1200; + +class QuartcSessionTest : public QuicTest { + public: + ~QuartcSessionTest() override {} + + void Init(bool create_client_endpoint = true) { + client_transport_ = + QuicMakeUnique<simulator::SimulatedQuartcPacketTransport>( + &simulator_, "client_transport", "server_transport", + 10 * kDefaultMaxPacketSize); + server_transport_ = + QuicMakeUnique<simulator::SimulatedQuartcPacketTransport>( + &simulator_, "server_transport", "client_transport", + 10 * kDefaultMaxPacketSize); + + client_filter_ = QuicMakeUnique<simulator::CountingPacketFilter>( + &simulator_, "client_filter", client_transport_.get()); + + client_server_link_ = QuicMakeUnique<simulator::SymmetricLink>( + client_filter_.get(), server_transport_.get(), + QuicBandwidth::FromKBitsPerSecond(10 * 1000), kPropagationDelay); + + client_stream_delegate_ = QuicMakeUnique<FakeQuartcStreamDelegate>(); + client_session_delegate_ = QuicMakeUnique<FakeQuartcSessionDelegate>( + client_stream_delegate_.get(), simulator_.GetClock()); + client_endpoint_delegate_ = QuicMakeUnique<FakeQuartcEndpointDelegate>( + client_session_delegate_.get()); + + server_stream_delegate_ = QuicMakeUnique<FakeQuartcStreamDelegate>(); + server_session_delegate_ = QuicMakeUnique<FakeQuartcSessionDelegate>( + server_stream_delegate_.get(), simulator_.GetClock()); + server_endpoint_delegate_ = QuicMakeUnique<FakeQuartcEndpointDelegate>( + server_session_delegate_.get()); + + // No 0-rtt setup, because server config is empty. + // CannotCreateDataStreamBeforeHandshake depends on 1-rtt setup. + if (create_client_endpoint) { + client_endpoint_ = QuicMakeUnique<QuartcClientEndpoint>( + simulator_.GetAlarmFactory(), simulator_.GetClock(), + client_endpoint_delegate_.get(), quic::QuartcSessionConfig(), + /*serialized_server_config=*/""); + } + server_endpoint_ = QuicMakeUnique<QuartcServerEndpoint>( + simulator_.GetAlarmFactory(), simulator_.GetClock(), + server_endpoint_delegate_.get(), quic::QuartcSessionConfig()); + } + + // Note that input session config will apply to both server and client. + // Perspective and packet_transport will be overwritten. + void CreateClientAndServerSessions(const QuartcSessionConfig& session_config, + bool init = true) { + if (init) { + Init(); + } + + server_endpoint_->Connect(server_transport_.get()); + client_endpoint_->Connect(client_transport_.get()); + + CHECK(simulator_.RunUntil([this] { + return client_endpoint_delegate_->session() != nullptr && + server_endpoint_delegate_->session() != nullptr; + })); + + client_peer_ = client_endpoint_delegate_->session(); + server_peer_ = server_endpoint_delegate_->session(); + } + + // Runs all tasks scheduled in the next 200 ms. + void RunTasks() { simulator_.RunFor(QuicTime::Delta::FromMilliseconds(200)); } + + void AwaitHandshake() { + simulator_.RunUntil([this] { + return client_peer_->IsCryptoHandshakeConfirmed() && + server_peer_->IsCryptoHandshakeConfirmed(); + }); + } + + // Test handshake establishment and sending/receiving of data for two + // directions. + void TestSendReceiveStreams() { + ASSERT_TRUE(server_peer_->IsCryptoHandshakeConfirmed()); + ASSERT_TRUE(client_peer_->IsCryptoHandshakeConfirmed()); + ASSERT_TRUE(server_peer_->IsEncryptionEstablished()); + ASSERT_TRUE(client_peer_->IsEncryptionEstablished()); + + // Now we can establish encrypted outgoing stream. + QuartcStream* outgoing_stream = + server_peer_->CreateOutgoingBidirectionalStream(); + QuicStreamId stream_id = outgoing_stream->id(); + ASSERT_NE(nullptr, outgoing_stream); + EXPECT_TRUE(server_peer_->ShouldKeepConnectionAlive()); + + outgoing_stream->SetDelegate(server_stream_delegate_.get()); + + // Send a test message from peer 1 to peer 2. + char kTestMessage[] = "Hello"; + test::QuicTestMemSliceVector data( + {std::make_pair(kTestMessage, strlen(kTestMessage))}); + outgoing_stream->WriteMemSlices(data.span(), /*fin=*/false); + RunTasks(); + + // Wait for peer 2 to receive messages. + ASSERT_TRUE(client_stream_delegate_->has_data()); + + QuartcStream* incoming = client_session_delegate_->last_incoming_stream(); + ASSERT_TRUE(incoming); + EXPECT_EQ(incoming->id(), stream_id); + EXPECT_TRUE(client_peer_->ShouldKeepConnectionAlive()); + + EXPECT_EQ(client_stream_delegate_->data()[stream_id], kTestMessage); + // Send a test message from peer 2 to peer 1. + char kTestResponse[] = "Response"; + test::QuicTestMemSliceVector response( + {std::make_pair(kTestResponse, strlen(kTestResponse))}); + incoming->WriteMemSlices(response.span(), /*fin=*/false); + RunTasks(); + // Wait for peer 1 to receive messages. + ASSERT_TRUE(server_stream_delegate_->has_data()); + + EXPECT_EQ(server_stream_delegate_->data()[stream_id], kTestResponse); + } + + // Test sending/receiving of messages for two directions. + void TestSendReceiveMessage() { + ASSERT_TRUE(server_peer_->CanSendMessage()); + ASSERT_TRUE(client_peer_->CanSendMessage()); + + // Send message from peer 1 to peer 2. + ASSERT_TRUE(server_peer_->SendOrQueueMessage("Message from server")); + + // First message in each direction should not be queued. + EXPECT_EQ(server_peer_->send_message_queue_size(), 0u); + + // Wait for peer 2 to receive message. + RunTasks(); + + EXPECT_THAT(client_session_delegate_->incoming_messages(), + testing::ElementsAre("Message from server")); + + // Send message from peer 2 to peer 1. + ASSERT_TRUE(client_peer_->SendOrQueueMessage("Message from client")); + + // First message in each direction should not be queued. + EXPECT_EQ(client_peer_->send_message_queue_size(), 0u); + + // Wait for peer 1 to receive message. + RunTasks(); + + EXPECT_THAT(server_session_delegate_->incoming_messages(), + testing::ElementsAre("Message from client")); + } + + // Test for sending multiple messages that also result in queueing. + // This is one-way test, which is run in given direction. + void TestSendReceiveQueuedMessages(bool direction_from_server) { + // Send until queue_size number of messages are queued. + constexpr size_t queue_size = 10; + + ASSERT_TRUE(server_peer_->CanSendMessage()); + ASSERT_TRUE(client_peer_->CanSendMessage()); + + QuartcSession* const peer_sending = + direction_from_server ? server_peer_ : client_peer_; + + FakeQuartcSessionDelegate* const delegate_receiving = + direction_from_server ? client_session_delegate_.get() + : server_session_delegate_.get(); + + // There should be no messages in the queue before we start sending. + EXPECT_EQ(peer_sending->send_message_queue_size(), 0u); + + // Send messages from peer 1 to peer 2 until required number of messages + // are queued in unsent message queue. + std::vector<QuicString> sent_messages; + while (peer_sending->send_message_queue_size() < queue_size) { + sent_messages.push_back( + QuicStrCat("Sending message, index=", sent_messages.size())); + ASSERT_TRUE(peer_sending->SendOrQueueMessage(sent_messages.back())); + } + + // Wait for peer 2 to receive all messages. + RunTasks(); + + EXPECT_EQ(delegate_receiving->incoming_messages(), sent_messages); + } + + // Test sending long messages: + // - message of maximum allowed length should succeed + // - message of > maximum allowed length should fail. + void TestSendLongMessage() { + ASSERT_TRUE(server_peer_->CanSendMessage()); + ASSERT_TRUE(client_peer_->CanSendMessage()); + + // Send message of maximum allowed length. + QuicString message_max_long = + QuicString(server_peer_->GetLargestMessagePayload(), 'A'); + ASSERT_TRUE(server_peer_->SendOrQueueMessage(message_max_long)); + + // Send long message which should fail. + QuicString message_too_long = + QuicString(server_peer_->GetLargestMessagePayload() + 1, 'B'); + ASSERT_FALSE(server_peer_->SendOrQueueMessage(message_too_long)); + + // Wait for peer 2 to receive message. + RunTasks(); + + // Client should only receive one message of allowed length. + EXPECT_THAT(client_session_delegate_->incoming_messages(), + testing::ElementsAre(message_max_long)); + } + + // Test that client and server are not connected after handshake failure. + void TestDisconnectAfterFailedHandshake() { + EXPECT_TRUE(!client_session_delegate_->connected()); + EXPECT_TRUE(!server_session_delegate_->connected()); + + EXPECT_FALSE(client_peer_->IsEncryptionEstablished()); + EXPECT_FALSE(client_peer_->IsCryptoHandshakeConfirmed()); + + EXPECT_FALSE(server_peer_->IsEncryptionEstablished()); + EXPECT_FALSE(server_peer_->IsCryptoHandshakeConfirmed()); + } + + protected: + simulator::Simulator simulator_; + + std::unique_ptr<simulator::SimulatedQuartcPacketTransport> client_transport_; + std::unique_ptr<simulator::SimulatedQuartcPacketTransport> server_transport_; + std::unique_ptr<simulator::CountingPacketFilter> client_filter_; + std::unique_ptr<simulator::SymmetricLink> client_server_link_; + + std::unique_ptr<FakeQuartcStreamDelegate> client_stream_delegate_; + std::unique_ptr<FakeQuartcSessionDelegate> client_session_delegate_; + std::unique_ptr<FakeQuartcEndpointDelegate> client_endpoint_delegate_; + std::unique_ptr<FakeQuartcStreamDelegate> server_stream_delegate_; + std::unique_ptr<FakeQuartcSessionDelegate> server_session_delegate_; + std::unique_ptr<FakeQuartcEndpointDelegate> server_endpoint_delegate_; + + std::unique_ptr<QuartcClientEndpoint> client_endpoint_; + std::unique_ptr<QuartcServerEndpoint> server_endpoint_; + + QuartcSession* client_peer_ = nullptr; + QuartcSession* server_peer_ = nullptr; +}; + +TEST_F(QuartcSessionTest, SendReceiveStreams) { + CreateClientAndServerSessions(QuartcSessionConfig()); + AwaitHandshake(); + TestSendReceiveStreams(); +} + +TEST_F(QuartcSessionTest, SendReceiveMessages) { + CreateClientAndServerSessions(QuartcSessionConfig()); + AwaitHandshake(); + TestSendReceiveMessage(); +} + +TEST_F(QuartcSessionTest, SendReceiveQueuedMessages) { + CreateClientAndServerSessions(QuartcSessionConfig()); + AwaitHandshake(); + TestSendReceiveQueuedMessages(/*direction_from_server=*/true); + TestSendReceiveQueuedMessages(/*direction_from_server=*/false); +} + +TEST_F(QuartcSessionTest, SendMessageFails) { + CreateClientAndServerSessions(QuartcSessionConfig()); + AwaitHandshake(); + TestSendLongMessage(); +} + +TEST_F(QuartcSessionTest, TestCryptoHandshakeCanWriteTriggers) { + CreateClientAndServerSessions(QuartcSessionConfig()); + + AwaitHandshake(); + + RunTasks(); + + ASSERT_TRUE(client_session_delegate_->writable_time().IsInitialized()); + ASSERT_TRUE( + client_session_delegate_->crypto_handshake_time().IsInitialized()); + // On client, we are writable 1-rtt before crypto handshake is complete. + ASSERT_LT(client_session_delegate_->writable_time(), + client_session_delegate_->crypto_handshake_time()); + + ASSERT_TRUE(server_session_delegate_->writable_time().IsInitialized()); + ASSERT_TRUE( + server_session_delegate_->crypto_handshake_time().IsInitialized()); + // On server, the writable time and crypto handshake are the same. (when SHLO + // is sent). + ASSERT_EQ(server_session_delegate_->writable_time(), + server_session_delegate_->crypto_handshake_time()); +} + +TEST_F(QuartcSessionTest, PreSharedKeyHandshake) { + QuartcSessionConfig config; + config.pre_shared_key = "foo"; + CreateClientAndServerSessions(config); + AwaitHandshake(); + TestSendReceiveStreams(); + TestSendReceiveMessage(); +} + +// Test that data streams are not created before handshake. +TEST_F(QuartcSessionTest, CannotCreateDataStreamBeforeHandshake) { + CreateClientAndServerSessions(QuartcSessionConfig()); + EXPECT_EQ(nullptr, server_peer_->CreateOutgoingBidirectionalStream()); + EXPECT_EQ(nullptr, client_peer_->CreateOutgoingBidirectionalStream()); +} + +TEST_F(QuartcSessionTest, CancelQuartcStream) { + CreateClientAndServerSessions(QuartcSessionConfig()); + AwaitHandshake(); + ASSERT_TRUE(client_peer_->IsCryptoHandshakeConfirmed()); + ASSERT_TRUE(server_peer_->IsCryptoHandshakeConfirmed()); + + QuartcStream* stream = client_peer_->CreateOutgoingBidirectionalStream(); + ASSERT_NE(nullptr, stream); + + uint32_t id = stream->id(); + EXPECT_FALSE(client_peer_->IsClosedStream(id)); + stream->SetDelegate(client_stream_delegate_.get()); + client_peer_->CancelStream(id); + EXPECT_EQ(stream->stream_error(), + QuicRstStreamErrorCode::QUIC_STREAM_CANCELLED); + EXPECT_TRUE(client_peer_->IsClosedStream(id)); +} + +// TODO(b/112561077): This is the wrong layer for this test. We should write a +// test specifically for QuartcPacketWriter with a stubbed-out +// QuartcPacketTransport and remove +// SimulatedQuartcPacketTransport::last_packet_number(). +TEST_F(QuartcSessionTest, WriterGivesPacketNumberToTransport) { + CreateClientAndServerSessions(QuartcSessionConfig()); + AwaitHandshake(); + ASSERT_TRUE(client_peer_->IsCryptoHandshakeConfirmed()); + ASSERT_TRUE(server_peer_->IsCryptoHandshakeConfirmed()); + + QuartcStream* stream = client_peer_->CreateOutgoingBidirectionalStream(); + stream->SetDelegate(client_stream_delegate_.get()); + + char kClientMessage[] = "Hello"; + test::QuicTestMemSliceVector stream_data( + {std::make_pair(kClientMessage, strlen(kClientMessage))}); + stream->WriteMemSlices(stream_data.span(), /*fin=*/false); + RunTasks(); + + // The transport should see the latest packet number sent by QUIC. + EXPECT_EQ( + client_transport_->last_packet_number(), + client_peer_->connection()->sent_packet_manager().GetLargestSentPacket()); +} + +TEST_F(QuartcSessionTest, CloseConnection) { + CreateClientAndServerSessions(QuartcSessionConfig()); + AwaitHandshake(); + ASSERT_TRUE(client_peer_->IsCryptoHandshakeConfirmed()); + ASSERT_TRUE(server_peer_->IsCryptoHandshakeConfirmed()); + + client_peer_->CloseConnection("Connection closed by client"); + EXPECT_FALSE(client_session_delegate_->connected()); + RunTasks(); + EXPECT_FALSE(server_session_delegate_->connected()); +} + +TEST_F(QuartcSessionTest, StreamRetransmissionEnabled) { + CreateClientAndServerSessions(QuartcSessionConfig()); + AwaitHandshake(); + ASSERT_TRUE(client_peer_->IsCryptoHandshakeConfirmed()); + ASSERT_TRUE(server_peer_->IsCryptoHandshakeConfirmed()); + + QuartcStream* stream = client_peer_->CreateOutgoingBidirectionalStream(); + QuicStreamId stream_id = stream->id(); + stream->SetDelegate(client_stream_delegate_.get()); + stream->set_cancel_on_loss(false); + + client_filter_->set_packets_to_drop(1); + + char kClientMessage[] = "Hello"; + test::QuicTestMemSliceVector stream_data( + {std::make_pair(kClientMessage, strlen(kClientMessage))}); + stream->WriteMemSlices(stream_data.span(), /*fin=*/false); + RunTasks(); + + // Stream data should make it despite packet loss. + ASSERT_TRUE(server_stream_delegate_->has_data()); + EXPECT_EQ(server_stream_delegate_->data()[stream_id], kClientMessage); +} + +TEST_F(QuartcSessionTest, StreamRetransmissionDisabled) { + // Disable tail loss probe, otherwise test maybe flaky because dropped + // message will be retransmitted to detect tail loss. + QuartcSessionConfig session_config; + session_config.enable_tail_loss_probe = false; + CreateClientAndServerSessions(session_config); + + // Disable probing retransmissions, otherwise test maybe flaky because dropped + // message will be retransmitted to to probe for more bandwidth. + client_peer_->connection()->set_fill_up_link_during_probing(false); + + AwaitHandshake(); + ASSERT_TRUE(client_peer_->IsCryptoHandshakeConfirmed()); + ASSERT_TRUE(server_peer_->IsCryptoHandshakeConfirmed()); + + // The client sends an ACK for the crypto handshake next. This must be + // flushed before we set the filter to drop the next packet, in order to + // ensure that the filter drops a data-bearing packet instead of just an ack. + RunTasks(); + + QuartcStream* stream = client_peer_->CreateOutgoingBidirectionalStream(); + QuicStreamId stream_id = stream->id(); + stream->SetDelegate(client_stream_delegate_.get()); + stream->set_cancel_on_loss(true); + + client_filter_->set_packets_to_drop(1); + + char kMessage[] = "Hello"; + test::QuicTestMemSliceVector stream_data( + {std::make_pair(kMessage, strlen(kMessage))}); + stream->WriteMemSlices(stream_data.span(), /*fin=*/false); + simulator_.RunFor(QuicTime::Delta::FromMilliseconds(1)); + + // Send another packet to trigger loss detection. + QuartcStream* stream_1 = client_peer_->CreateOutgoingBidirectionalStream(); + stream_1->SetDelegate(client_stream_delegate_.get()); + + char kMessage1[] = "Second message"; + test::QuicTestMemSliceVector stream_data_1( + {std::make_pair(kMessage1, strlen(kMessage1))}); + stream_1->WriteMemSlices(stream_data_1.span(), /*fin=*/false); + RunTasks(); + + // QUIC should try to retransmit the first stream by loss detection. Instead, + // it will cancel itself. + EXPECT_THAT(server_stream_delegate_->data()[stream_id], testing::IsEmpty()); + + EXPECT_TRUE(client_peer_->IsClosedStream(stream_id)); + EXPECT_TRUE(server_peer_->IsClosedStream(stream_id)); + EXPECT_EQ(client_stream_delegate_->stream_error(stream_id), + QUIC_STREAM_CANCELLED); + EXPECT_EQ(server_stream_delegate_->stream_error(stream_id), + QUIC_STREAM_CANCELLED); +} + +TEST_F(QuartcSessionTest, ServerRegistersAsWriteBlocked) { + // Initialize client and server session, but with the server write-blocked. + Init(); + server_transport_->SetWritable(false); + CreateClientAndServerSessions(QuartcSessionConfig(), /*init=*/false); + + // Let the client send a few copies of the CHLO. The server can't respond, as + // it's still write-blocked. + RunTasks(); + + // Making the server's transport writable should trigger a callback that + // reaches the server session, allowing it to write packets. + server_transport_->SetWritable(true); + + // Now the server should respond with the SHLO, encryption should be + // established, and data should flow normally. + // Note that if the server is *not* correctly registered as write-blocked, + // it will crash here (see b/124527328 for details). + AwaitHandshake(); + TestSendReceiveStreams(); +} + +TEST_F(QuartcSessionTest, PreSharedKeyHandshakeIs0RTT) { + QuartcSessionConfig session_config; + session_config.pre_shared_key = "foo"; + + // Client endpoint is created below. Destructing client endpoint + // causes issues with the simulator. + Init(/*create_client_endpoint=*/false); + + server_endpoint_->Connect(server_transport_.get()); + + client_endpoint_ = QuicMakeUnique<QuartcClientEndpoint>( + simulator_.GetAlarmFactory(), simulator_.GetClock(), + client_endpoint_delegate_.get(), QuartcSessionConfig(), + // This is the key line here. It passes through the server config + // from the server to the client. + server_endpoint_->server_crypto_config()); + + client_endpoint_->Connect(client_transport_.get()); + + // Running for 1ms. This is shorter than the RTT, so the + // client session should be created, but server won't be created yet. + simulator_.RunFor(QuicTime::Delta::FromMilliseconds(1)); + + client_peer_ = client_endpoint_delegate_->session(); + server_peer_ = server_endpoint_delegate_->session(); + + ASSERT_NE(client_peer_, nullptr); + ASSERT_EQ(server_peer_, nullptr); + + // Write data to the client before running tasks. This should be sent by the + // client and received by the server if the handshake is 0RTT. + // If this test fails, add 'RunTasks()' above, and see what error is sent + // by the server in the rejection message. + QuartcStream* stream = client_peer_->CreateOutgoingBidirectionalStream(); + ASSERT_NE(stream, nullptr); + QuicStreamId stream_id = stream->id(); + stream->SetDelegate(client_stream_delegate_.get()); + + char message[] = "Hello in 0RTTs!"; + test::QuicTestMemSliceVector data({std::make_pair(message, strlen(message))}); + stream->WriteMemSlices(data.span(), /*fin=*/false); + + // This will now run the rest of the connection. But the + // Server peer will receive the CHLO and message after 1 delay. + simulator_.RunFor(kPropagationDelayAndABit); + + // If we can decrypt the data, it means that 0 rtt was successful. + // This is because we waited only a propagation delay. So if the decryption + // failed, we would send sREJ instead of SHLO, but it wouldn't be delivered to + // the client yet. + ASSERT_TRUE(server_stream_delegate_->has_data()); + EXPECT_EQ(server_stream_delegate_->data()[stream_id], message); +} + +} // namespace + +} // namespace quic
diff --git a/quic/quartc/quartc_stream.cc b/quic/quartc/quartc_stream.cc new file mode 100644 index 0000000..49ade23 --- /dev/null +++ b/quic/quartc/quartc_stream.cc
@@ -0,0 +1,171 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/quic/quartc/quartc_stream.h" + +#include <memory> +#include <utility> + +#include "net/third_party/quiche/src/quic/core/quic_ack_listener_interface.h" +#include "net/third_party/quiche/src/quic/core/quic_error_codes.h" +#include "net/third_party/quiche/src/quic/core/quic_stream_send_buffer.h" +#include "net/third_party/quiche/src/quic/core/quic_stream_sequencer.h" +#include "net/third_party/quiche/src/quic/core/quic_stream_sequencer_buffer.h" +#include "net/third_party/quiche/src/quic/core/quic_types.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_reference_counted.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h" + +namespace quic { + +QuartcStream::QuartcStream(QuicStreamId id, QuicSession* session) + : QuicStream(id, session, /*is_static=*/false, BIDIRECTIONAL) { + sequencer()->set_level_triggered(true); +} + +QuartcStream::QuartcStream(PendingStream pending) + : QuicStream(std::move(pending), BIDIRECTIONAL) { + sequencer()->set_level_triggered(true); +} + +QuartcStream::~QuartcStream() {} + +void QuartcStream::OnDataAvailable() { + bool fin = sequencer()->ReadableBytes() + sequencer()->NumBytesConsumed() == + sequencer()->close_offset(); + + // Upper bound on number of readable regions. Each complete block's worth of + // data crosses at most one region boundary. The remainder may cross one more + // boundary. Number of regions is one more than the number of region + // boundaries crossed. + size_t iov_length = sequencer()->ReadableBytes() / + QuicStreamSequencerBuffer::kBlockSizeBytes + + 2; + std::unique_ptr<iovec[]> iovecs = QuicMakeUnique<iovec[]>(iov_length); + iov_length = sequencer()->GetReadableRegions(iovecs.get(), iov_length); + + sequencer()->MarkConsumed( + delegate_->OnReceived(this, iovecs.get(), iov_length, fin)); + if (sequencer()->IsClosed()) { + OnFinRead(); + } +} + +void QuartcStream::OnClose() { + QuicStream::OnClose(); + DCHECK(delegate_); + delegate_->OnClose(this); +} + +void QuartcStream::OnStreamDataConsumed(size_t bytes_consumed) { + QuicStream::OnStreamDataConsumed(bytes_consumed); + + DCHECK(delegate_); + delegate_->OnBufferChanged(this); +} + +void QuartcStream::OnDataBuffered( + QuicStreamOffset offset, + QuicByteCount data_length, + const QuicReferenceCountedPointer<QuicAckListenerInterface>& ack_listener) { + DCHECK(delegate_); + delegate_->OnBufferChanged(this); +} + +bool QuartcStream::OnStreamFrameAcked(QuicStreamOffset offset, + QuicByteCount data_length, + bool fin_acked, + QuicTime::Delta ack_delay_time, + QuicByteCount* newly_acked_length) { + // Previous losses of acked data are no longer relevant to the retransmission + // count. Once data is acked, it will never be retransmitted. + lost_frame_counter_.RemoveInterval( + QuicInterval<QuicStreamOffset>(offset, offset + data_length)); + + return QuicStream::OnStreamFrameAcked(offset, data_length, fin_acked, + ack_delay_time, newly_acked_length); +} + +void QuartcStream::OnStreamFrameRetransmitted(QuicStreamOffset offset, + QuicByteCount data_length, + bool fin_retransmitted) { + QuicStream::OnStreamFrameRetransmitted(offset, data_length, + fin_retransmitted); + + DCHECK(delegate_); + delegate_->OnBufferChanged(this); +} + +void QuartcStream::OnStreamFrameLost(QuicStreamOffset offset, + QuicByteCount data_length, + bool fin_lost) { + QuicStream::OnStreamFrameLost(offset, data_length, fin_lost); + + lost_frame_counter_.AddInterval( + QuicInterval<QuicStreamOffset>(offset, offset + data_length)); + + DCHECK(delegate_); + delegate_->OnBufferChanged(this); +} + +void QuartcStream::OnCanWrite() { + if (lost_frame_counter_.MaxCount() > + static_cast<size_t>(max_retransmission_count_) && + HasPendingRetransmission()) { + Reset(QUIC_STREAM_CANCELLED); + return; + } + QuicStream::OnCanWrite(); +} + +bool QuartcStream::cancel_on_loss() { + return max_retransmission_count_ == 0; +} + +void QuartcStream::set_cancel_on_loss(bool cancel_on_loss) { + if (cancel_on_loss) { + max_retransmission_count_ = 0; + } else { + max_retransmission_count_ = std::numeric_limits<int>::max(); + } +} + +int QuartcStream::max_retransmission_count() const { + return max_retransmission_count_; +} + +void QuartcStream::set_max_retransmission_count(int max_retransmission_count) { + max_retransmission_count_ = max_retransmission_count; +} + +QuicByteCount QuartcStream::BytesPendingRetransmission() { + if (lost_frame_counter_.MaxCount() > + static_cast<size_t>(max_retransmission_count_)) { + return 0; // Lost bytes will never be retransmitted. + } + QuicByteCount bytes = 0; + for (const auto& interval : send_buffer().pending_retransmissions()) { + bytes += interval.Length(); + } + return bytes; +} + +QuicStreamOffset QuartcStream::ReadOffset() { + return sequencer()->NumBytesConsumed(); +} + +void QuartcStream::FinishWriting() { + WriteOrBufferData(QuicStringPiece(nullptr, 0), true, nullptr); +} + +void QuartcStream::SetDelegate(Delegate* delegate) { + if (delegate_) { + LOG(WARNING) << "The delegate for Stream " << id() + << " has already been set."; + } + delegate_ = delegate; + DCHECK(delegate_); +} + +} // namespace quic
diff --git a/quic/quartc/quartc_stream.h b/quic/quartc/quartc_stream.h new file mode 100644 index 0000000..bf15a68 --- /dev/null +++ b/quic/quartc/quartc_stream.h
@@ -0,0 +1,142 @@ +// Copyright (c) 2017 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. + +#ifndef QUICHE_QUIC_QUARTC_QUARTC_STREAM_H_ +#define QUICHE_QUIC_QUARTC_QUARTC_STREAM_H_ + +#include <stddef.h> +#include <limits> + +#include "net/third_party/quiche/src/quic/core/quic_ack_listener_interface.h" +#include "net/third_party/quiche/src/quic/core/quic_session.h" +#include "net/third_party/quiche/src/quic/core/quic_stream.h" +#include "net/third_party/quiche/src/quic/core/quic_types.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_mem_slice_span.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_reference_counted.h" +#include "net/quic/platform/impl/quic_export_impl.h" +#include "net/third_party/quiche/src/quic/quartc/quartc_interval_counter.h" + +namespace quic { + +// Sends and receives data with a particular QUIC stream ID, reliably and +// in-order. To send/receive data out of order, use separate streams. To +// send/receive unreliably, close a stream after reliability is no longer +// needed. +class QuartcStream : public QuicStream { + public: + QuartcStream(QuicStreamId id, QuicSession* session); + explicit QuartcStream(PendingStream pending); + + ~QuartcStream() override; + + // QuicStream overrides. + void OnDataAvailable() override; + + void OnClose() override; + + void OnStreamDataConsumed(size_t bytes_consumed) override; + + void OnDataBuffered( + QuicStreamOffset offset, + QuicByteCount data_length, + const QuicReferenceCountedPointer<QuicAckListenerInterface>& ack_listener) + override; + + bool OnStreamFrameAcked(QuicStreamOffset offset, + QuicByteCount data_length, + bool fin_acked, + QuicTime::Delta ack_delay_time, + QuicByteCount* newly_acked_length) override; + + void OnStreamFrameRetransmitted(QuicStreamOffset offset, + QuicByteCount data_length, + bool fin_retransmitted) override; + + void OnStreamFrameLost(QuicStreamOffset offset, + QuicByteCount data_length, + bool fin_lost) override; + + void OnCanWrite() override; + + // QuartcStream interface methods. + + // Whether the stream should be cancelled instead of retransmitted on loss. + // If set to true, the stream will reset itself instead of retransmitting lost + // stream frames. Defaults to false. Setting it to true is equivalent to + // setting |max_retransmission_count| to zero. + bool cancel_on_loss(); + void set_cancel_on_loss(bool cancel_on_loss); + + // Maximum number of times this stream's data may be retransmitted. Each byte + // of stream data may be retransmitted this many times. If any byte (or range + // of bytes) is lost and would be retransmitted more than this number of + // times, the stream resets itself instead of retransmitting the data again. + // Setting this value to zero disables retransmissions. + // + // Note that this limit applies only to stream data, not to the FIN bit. If + // only the FIN bit needs to be retransmitted, there is no benefit to + // cancelling the stream and sending a reset frame instead. + int max_retransmission_count() const; + void set_max_retransmission_count(int max_retransmission_count); + + QuicByteCount BytesPendingRetransmission(); + + // Returns the current read offset for this stream. During a call to + // Delegate::OnReceived, this value is the offset of the first byte read. + QuicStreamOffset ReadOffset(); + + // Marks this stream as finished writing. Asynchronously sends a FIN and + // closes the write-side. It is not necessary to call FinishWriting() if the + // last call to Write() sends a FIN. + void FinishWriting(); + + // Implemented by the user of the QuartcStream to receive incoming + // data and be notified of state changes. + class Delegate { + public: + virtual ~Delegate() {} + + // Called when the stream receives data. |iov| is a pointer to the first of + // |iov_length| readable regions. |iov| points to readable data within + // |stream|'s sequencer buffer. QUIC may modify or delete this data after + // the application consumes it. |fin| indicates the end of stream data. + // Returns the number of bytes consumed. May return 0 if the delegate is + // unable to consume any bytes at this time. + virtual size_t OnReceived(QuartcStream* stream, + iovec* iov, + size_t iov_length, + bool fin) = 0; + + // Called when the stream is closed, either locally or by the remote + // endpoint. Streams close when (a) fin bits are both sent and received, + // (b) Close() is called, or (c) the stream is reset. + // TODO(zhihuang) Creates a map from the integer error_code to WebRTC native + // error code. + virtual void OnClose(QuartcStream* stream) = 0; + + // Called when the contents of the stream's buffer changes. + virtual void OnBufferChanged(QuartcStream* stream) = 0; + }; + + // The |delegate| is not owned by QuartcStream. + void SetDelegate(Delegate* delegate); + + private: + Delegate* delegate_ = nullptr; + + // Maximum number of times this stream's data may be retransmitted. + int max_retransmission_count_ = std::numeric_limits<int>::max(); + + // Counter which tracks the number of times each frame has been lost + // (accounting for the possibility of overlapping frames). + // + // If the maximum count of any lost frame exceeds |max_retransmission_count_|, + // the stream will cancel itself on the next attempt to retransmit data (the + // next call to |OnCanWrite|). + QuartcIntervalCounter<QuicStreamOffset> lost_frame_counter_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_QUARTC_QUARTC_STREAM_H_
diff --git a/quic/quartc/quartc_stream_test.cc b/quic/quartc/quartc_stream_test.cc new file mode 100644 index 0000000..edb0ee0 --- /dev/null +++ b/quic/quartc/quartc_stream_test.cc
@@ -0,0 +1,640 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/quic/quartc/quartc_stream.h" + +#include <memory> +#include <type_traits> +#include <utility> + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/quic/core/crypto/quic_random.h" +#include "net/third_party/quiche/src/quic/core/frames/quic_stream_frame.h" +#include "net/third_party/quiche/src/quic/core/quic_alarm_factory.h" +#include "net/third_party/quiche/src/quic/core/quic_buffer_allocator.h" +#include "net/third_party/quiche/src/quic/core/quic_config.h" +#include "net/third_party/quiche/src/quic/core/quic_connection.h" +#include "net/third_party/quiche/src/quic/core/quic_crypto_stream.h" +#include "net/third_party/quiche/src/quic/core/quic_data_writer.h" +#include "net/third_party/quiche/src/quic/core/quic_error_codes.h" +#include "net/third_party/quiche/src/quic/core/quic_packet_writer.h" +#include "net/third_party/quiche/src/quic/core/quic_session.h" +#include "net/third_party/quiche/src/quic/core/quic_simple_buffer_allocator.h" +#include "net/third_party/quiche/src/quic/core/quic_time.h" +#include "net/third_party/quiche/src/quic/core/quic_types.h" +#include "net/third_party/quiche/src/quic/core/quic_utils.h" +#include "net/third_party/quiche/src/quic/core/quic_versions.h" +#include "net/third_party/quiche/src/quic/core/quic_write_blocked_list.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_clock.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_endian.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_ip_address.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_string.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_test.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_test_mem_slice_vector.h" +#include "net/third_party/quiche/src/quic/quartc/quartc_factory.h" +#include "net/third_party/quiche/src/quic/test_tools/mock_clock.h" +#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h" +#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h" + +namespace quic { + +namespace { + +static const QuicStreamId kStreamId = 5; + +// MockQuicSession that does not create streams and writes data from +// QuicStream to a string. +class MockQuicSession : public QuicSession { + public: + MockQuicSession(QuicConnection* connection, + const QuicConfig& config, + QuicString* write_buffer) + : QuicSession(connection, + nullptr /*visitor*/, + config, + CurrentSupportedVersions()), + write_buffer_(write_buffer) {} + + ~MockQuicSession() override {} + + // Writes outgoing data from QuicStream to a string. + QuicConsumedData WritevData(QuicStream* stream, + QuicStreamId id, + size_t write_length, + QuicStreamOffset offset, + StreamSendingState state) override { + if (!writable_) { + return QuicConsumedData(0, false); + } + + // WritevData does not pass down a iovec, data is saved in stream before + // data is consumed. Retrieve data from stream. + char* buf = new char[write_length]; + QuicDataWriter writer(write_length, buf, NETWORK_BYTE_ORDER); + if (write_length > 0) { + stream->WriteStreamData(offset, write_length, &writer); + } + write_buffer_->append(buf, write_length); + delete[] buf; + return QuicConsumedData(write_length, state != StreamSendingState::NO_FIN); + } + + QuartcStream* CreateIncomingStream(QuicStreamId id) override { + return nullptr; + } + + QuartcStream* CreateIncomingStream(PendingStream pending) override { + return nullptr; + } + + const QuicCryptoStream* GetCryptoStream() const override { return nullptr; } + QuicCryptoStream* GetMutableCryptoStream() override { return nullptr; } + bool ShouldKeepConnectionAlive() const override { + return GetNumOpenDynamicStreams() > 0; + } + + // Called by QuicStream when they want to close stream. + void SendRstStream(QuicStreamId id, + QuicRstStreamErrorCode error, + QuicStreamOffset bytes_written) override {} + + // Sets whether data is written to buffer, or else if this is write blocked. + void set_writable(bool writable) { writable_ = writable; } + + // Tracks whether the stream is write blocked and its priority. + void RegisterReliableStream(QuicStreamId stream_id, + spdy::SpdyPriority priority) { + write_blocked_streams()->RegisterStream(stream_id, + /*is_static_stream=*/false, + priority); + } + + // The session take ownership of the stream. + void ActivateReliableStream(std::unique_ptr<QuicStream> stream) { + ActivateStream(std::move(stream)); + } + + private: + // Stores written data from ReliableQuicStreamAdapter. + QuicString* write_buffer_; + // Whether data is written to write_buffer_. + bool writable_ = true; +}; + +// Packet writer that does nothing. This is required for QuicConnection but +// isn't used for writing data. +class DummyPacketWriter : public QuicPacketWriter { + public: + DummyPacketWriter() {} + + // QuicPacketWriter overrides. + WriteResult WritePacket(const char* buffer, + size_t buf_len, + const QuicIpAddress& self_address, + const QuicSocketAddress& peer_address, + PerPacketOptions* options) override { + return WriteResult(WRITE_STATUS_ERROR, 0); + } + + bool IsWriteBlocked() const override { return false; } + + void SetWritable() override {} + + QuicByteCount GetMaxPacketSize( + const QuicSocketAddress& peer_address) const override { + return 0; + } + + bool SupportsReleaseTime() const override { return false; } + + bool IsBatchMode() const override { return false; } + + char* GetNextWriteLocation(const QuicIpAddress& self_address, + const QuicSocketAddress& peer_address) override { + return nullptr; + } + + WriteResult Flush() override { return WriteResult(WRITE_STATUS_OK, 0); } +}; + +class MockQuartcStreamDelegate : public QuartcStream::Delegate { + public: + MockQuartcStreamDelegate(QuicStreamId id, QuicString* read_buffer) + : id_(id), read_buffer_(read_buffer) {} + + void OnBufferChanged(QuartcStream* stream) override { + last_bytes_buffered_ = stream->BufferedDataBytes(); + last_bytes_pending_retransmission_ = stream->BytesPendingRetransmission(); + } + + size_t OnReceived(QuartcStream* stream, + iovec* iov, + size_t iov_length, + bool fin) override { + EXPECT_EQ(id_, stream->id()); + EXPECT_EQ(stream->ReadOffset(), read_buffer_->size()); + size_t bytes_consumed = 0; + for (size_t i = 0; i < iov_length; ++i) { + read_buffer_->append(static_cast<const char*>(iov[i].iov_base), + iov[i].iov_len); + bytes_consumed += iov[i].iov_len; + } + return bytes_consumed; + } + + void OnClose(QuartcStream* stream) override { closed_ = true; } + + bool closed() { return closed_; } + + QuicByteCount last_bytes_buffered() { return last_bytes_buffered_; } + QuicByteCount last_bytes_pending_retransmission() { + return last_bytes_pending_retransmission_; + } + + protected: + QuicStreamId id_; + // Data read by the QuicStream. + QuicString* read_buffer_; + // Whether the QuicStream is closed. + bool closed_ = false; + + // Last amount of data observed as buffered. + QuicByteCount last_bytes_buffered_ = 0; + QuicByteCount last_bytes_pending_retransmission_ = 0; +}; + +class QuartcStreamTest : public QuicTest, public QuicConnectionHelperInterface { + public: + QuartcStreamTest() { + // Required to correctly handle StopReading(). + SetQuicReloadableFlag(quic_stop_reading_when_level_triggered, true); + } + + ~QuartcStreamTest() override = default; + + void CreateReliableQuicStream() { + // Arbitrary values for QuicConnection. + Perspective perspective = Perspective::IS_SERVER; + QuicIpAddress ip; + ip.FromString("0.0.0.0"); + bool owns_writer = true; + + alarm_factory_ = QuicMakeUnique<test::MockAlarmFactory>(); + + connection_ = QuicMakeUnique<QuicConnection>( + QuicUtils::CreateZeroConnectionId( + CurrentSupportedVersions()[0].transport_version), + QuicSocketAddress(ip, 0), this /*QuicConnectionHelperInterface*/, + alarm_factory_.get(), new DummyPacketWriter(), owns_writer, perspective, + ParsedVersionOfIndex(CurrentSupportedVersions(), 0)); + clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1)); + session_ = QuicMakeUnique<MockQuicSession>(connection_.get(), QuicConfig(), + &write_buffer_); + mock_stream_delegate_ = + QuicMakeUnique<MockQuartcStreamDelegate>(kStreamId, &read_buffer_); + stream_ = new QuartcStream(kStreamId, session_.get()); + stream_->SetDelegate(mock_stream_delegate_.get()); + session_->ActivateReliableStream(std::unique_ptr<QuartcStream>(stream_)); + } + + const QuicClock* GetClock() const override { return &clock_; } + + QuicRandom* GetRandomGenerator() override { + return QuicRandom::GetInstance(); + } + + QuicBufferAllocator* GetStreamSendBufferAllocator() override { + return &buffer_allocator_; + } + + protected: + // The QuicSession will take the ownership. + QuartcStream* stream_; + std::unique_ptr<MockQuartcStreamDelegate> mock_stream_delegate_; + std::unique_ptr<MockQuicSession> session_; + // Data written by the ReliableQuicStreamAdapterTest. + QuicString write_buffer_; + // Data read by the ReliableQuicStreamAdapterTest. + QuicString read_buffer_; + std::unique_ptr<QuicAlarmFactory> alarm_factory_; + std::unique_ptr<QuicConnection> connection_; + // Used to implement the QuicConnectionHelperInterface. + SimpleBufferAllocator buffer_allocator_; + MockClock clock_; +}; + +// Write an entire string. +TEST_F(QuartcStreamTest, WriteDataWhole) { + CreateReliableQuicStream(); + char message[] = "Foo bar"; + test::QuicTestMemSliceVector data({std::make_pair(message, 7)}); + stream_->WriteMemSlices(data.span(), /*fin=*/false); + EXPECT_EQ("Foo bar", write_buffer_); +} + +// Write part of a string. +TEST_F(QuartcStreamTest, WriteDataPartial) { + CreateReliableQuicStream(); + char message[] = "Foo bar"; + test::QuicTestMemSliceVector data({std::make_pair(message, 5)}); + stream_->WriteMemSlices(data.span(), /*fin=*/false); + EXPECT_EQ("Foo b", write_buffer_); +} + +// Test that a QuartcStream buffers writes correctly. +TEST_F(QuartcStreamTest, StreamBuffersData) { + CreateReliableQuicStream(); + + char message[] = "Foo bar"; + test::QuicTestMemSliceVector data({std::make_pair(message, 7)}); + + // The stream is not yet writable, so data will be buffered. + session_->set_writable(false); + stream_->WriteMemSlices(data.span(), /*fin=*/false); + + // Check that data is buffered. + EXPECT_TRUE(stream_->HasBufferedData()); + EXPECT_EQ(7u, stream_->BufferedDataBytes()); + + // Check that the stream told its delegate about the buffer change. + EXPECT_EQ(7u, mock_stream_delegate_->last_bytes_buffered()); + + // Check that none of the data was written yet. + // Note that |write_buffer_| actually holds data written by the QuicSession + // (not data buffered by the stream). + EXPECT_EQ(0ul, write_buffer_.size()); + + char message1[] = "xyzzy"; + test::QuicTestMemSliceVector data1({std::make_pair(message1, 5)}); + + // More writes go into the buffer. + stream_->WriteMemSlices(data1.span(), /*fin=*/false); + + EXPECT_TRUE(stream_->HasBufferedData()); + EXPECT_EQ(12u, stream_->BufferedDataBytes()); + EXPECT_EQ(12u, mock_stream_delegate_->last_bytes_buffered()); + EXPECT_EQ(0ul, write_buffer_.size()); + + // The stream becomes writable, so it sends the buffered data. + session_->set_writable(true); + stream_->OnCanWrite(); + + EXPECT_FALSE(stream_->HasBufferedData()); + EXPECT_EQ(0u, stream_->BufferedDataBytes()); + EXPECT_EQ(0u, mock_stream_delegate_->last_bytes_buffered()); + EXPECT_EQ("Foo barxyzzy", write_buffer_); +} + +// Finish writing to a stream. +// It delivers the fin bit and closes the write-side as soon as possible. +TEST_F(QuartcStreamTest, FinishWriting) { + CreateReliableQuicStream(); + + session_->set_writable(false); + stream_->FinishWriting(); + EXPECT_FALSE(stream_->fin_sent()); + + // Fin is sent as soon as the stream becomes writable. + session_->set_writable(true); + stream_->OnCanWrite(); + EXPECT_TRUE(stream_->fin_sent()); + EXPECT_TRUE(stream_->write_side_closed()); +} + +// Read an entire string. +TEST_F(QuartcStreamTest, ReadDataWhole) { + CreateReliableQuicStream(); + QuicStreamFrame frame(kStreamId, false, 0, "Hello, World!"); + stream_->OnStreamFrame(frame); + + EXPECT_EQ("Hello, World!", read_buffer_); +} + +// Read part of a string. +TEST_F(QuartcStreamTest, ReadDataPartial) { + CreateReliableQuicStream(); + QuicStreamFrame frame(kStreamId, false, 0, "Hello, World!"); + frame.data_length = 5; + stream_->OnStreamFrame(frame); + + EXPECT_EQ("Hello", read_buffer_); +} + +// Streams do not call OnReceived() after StopReading(). +// Note: this is tested here because Quartc relies on this behavior. +TEST_F(QuartcStreamTest, StopReading) { + CreateReliableQuicStream(); + stream_->StopReading(); + + QuicStreamFrame frame(kStreamId, false, 0, "Hello, World!"); + stream_->OnStreamFrame(frame); + + EXPECT_EQ(0ul, read_buffer_.size()); + + QuicStreamFrame frame2(kStreamId, true, 0, "Hello, World!"); + stream_->OnStreamFrame(frame2); + + EXPECT_EQ(0ul, read_buffer_.size()); + EXPECT_TRUE(stream_->fin_received()); +} + +// Test that closing the stream results in a callback. +TEST_F(QuartcStreamTest, CloseStream) { + CreateReliableQuicStream(); + EXPECT_FALSE(mock_stream_delegate_->closed()); + stream_->OnClose(); + EXPECT_TRUE(mock_stream_delegate_->closed()); +} + +// Both sending and receiving fin automatically closes a stream. +TEST_F(QuartcStreamTest, CloseOnFins) { + CreateReliableQuicStream(); + QuicStreamFrame frame(kStreamId, true, 0, 0); + stream_->OnStreamFrame(frame); + + test::QuicTestMemSliceVector data({}); + stream_->WriteMemSlices(data.span(), /*fin=*/true); + + // Check that the OnClose() callback occurred. + EXPECT_TRUE(mock_stream_delegate_->closed()); +} + +TEST_F(QuartcStreamTest, TestCancelOnLossDisabled) { + CreateReliableQuicStream(); + + // This should be the default state. + EXPECT_FALSE(stream_->cancel_on_loss()); + + char message[] = "Foo bar"; + test::QuicTestMemSliceVector data({std::make_pair(message, 7)}); + stream_->WriteMemSlices(data.span(), /*fin=*/false); + + EXPECT_EQ("Foo bar", write_buffer_); + + stream_->OnStreamFrameLost(0, 7, false); + stream_->OnCanWrite(); + + EXPECT_EQ("Foo barFoo bar", write_buffer_); + EXPECT_EQ(stream_->stream_error(), QUIC_STREAM_NO_ERROR); +} + +TEST_F(QuartcStreamTest, TestCancelOnLossEnabled) { + CreateReliableQuicStream(); + stream_->set_cancel_on_loss(true); + + char message[] = "Foo bar"; + test::QuicTestMemSliceVector data({std::make_pair(message, 7)}); + stream_->WriteMemSlices(data.span(), /*fin=*/false); + + EXPECT_EQ("Foo bar", write_buffer_); + + stream_->OnStreamFrameLost(0, 7, false); + stream_->OnCanWrite(); + + EXPECT_EQ("Foo bar", write_buffer_); + EXPECT_EQ(stream_->stream_error(), QUIC_STREAM_CANCELLED); +} + +TEST_F(QuartcStreamTest, MaxRetransmissionsAbsent) { + CreateReliableQuicStream(); + + // This should be the default state. + EXPECT_EQ(stream_->max_retransmission_count(), + std::numeric_limits<int>::max()); + + char message[] = "Foo bar"; + test::QuicTestMemSliceVector data({std::make_pair(message, 7)}); + stream_->WriteMemSlices(data.span(), /*fin=*/false); + + EXPECT_EQ("Foo bar", write_buffer_); + + stream_->OnStreamFrameLost(0, 7, false); + stream_->OnCanWrite(); + + EXPECT_EQ("Foo barFoo bar", write_buffer_); + EXPECT_EQ(stream_->stream_error(), QUIC_STREAM_NO_ERROR); +} + +TEST_F(QuartcStreamTest, MaxRetransmissionsSet) { + CreateReliableQuicStream(); + stream_->set_max_retransmission_count(2); + + char message[] = "Foo bar"; + test::QuicTestMemSliceVector data({std::make_pair(message, 7)}); + stream_->WriteMemSlices(data.span(), /*fin=*/false); + + EXPECT_EQ("Foo bar", write_buffer_); + + stream_->OnStreamFrameLost(0, 7, false); + stream_->OnCanWrite(); + + EXPECT_EQ("Foo barFoo bar", write_buffer_); + + stream_->OnStreamFrameLost(0, 7, false); + stream_->OnCanWrite(); + + EXPECT_EQ("Foo barFoo barFoo bar", write_buffer_); + + stream_->OnStreamFrameLost(0, 7, false); + stream_->OnCanWrite(); + + EXPECT_EQ("Foo barFoo barFoo bar", write_buffer_); + EXPECT_EQ(stream_->stream_error(), QUIC_STREAM_CANCELLED); +} + +TEST_F(QuartcStreamTest, MaxRetransmissionsDisjointFrames) { + CreateReliableQuicStream(); + stream_->set_max_retransmission_count(2); + + char message[] = "Foo bar"; + test::QuicTestMemSliceVector data({std::make_pair(message, 7)}); + stream_->WriteMemSlices(data.span(), /*fin=*/false); + + EXPECT_EQ("Foo bar", write_buffer_); + + // Retransmit bytes [0, 3]. + stream_->OnStreamFrameLost(0, 4, false); + stream_->OnCanWrite(); + + EXPECT_EQ("Foo barFoo ", write_buffer_); + + // Retransmit bytes [4, 6]. Everything has been retransmitted once. + stream_->OnStreamFrameLost(4, 3, false); + stream_->OnCanWrite(); + + EXPECT_EQ("Foo barFoo bar", write_buffer_); + + // Retransmit bytes [0, 6]. Everything can be retransmitted a second time. + stream_->OnStreamFrameLost(0, 7, false); + stream_->OnCanWrite(); + + EXPECT_EQ("Foo barFoo barFoo bar", write_buffer_); +} + +TEST_F(QuartcStreamTest, MaxRetransmissionsOverlappingFrames) { + CreateReliableQuicStream(); + stream_->set_max_retransmission_count(2); + + char message[] = "Foo bar"; + test::QuicTestMemSliceVector data({std::make_pair(message, 7)}); + stream_->WriteMemSlices(data.span(), /*fin=*/false); + + EXPECT_EQ("Foo bar", write_buffer_); + + // Retransmit bytes 0 to 3. + stream_->OnStreamFrameLost(0, 4, false); + stream_->OnCanWrite(); + + EXPECT_EQ("Foo barFoo ", write_buffer_); + + // Retransmit bytes 3 to 6. Byte 3 has been retransmitted twice. + stream_->OnStreamFrameLost(3, 4, false); + stream_->OnCanWrite(); + + EXPECT_EQ("Foo barFoo bar", write_buffer_); + + // Retransmit byte 3 a third time. This should cause cancellation. + stream_->OnStreamFrameLost(3, 1, false); + stream_->OnCanWrite(); + + EXPECT_EQ("Foo barFoo bar", write_buffer_); + EXPECT_EQ(stream_->stream_error(), QUIC_STREAM_CANCELLED); +} + +TEST_F(QuartcStreamTest, MaxRetransmissionsWithAckedFrame) { + CreateReliableQuicStream(); + stream_->set_max_retransmission_count(1); + + char message[] = "Foo bar"; + test::QuicTestMemSliceVector data({std::make_pair(message, 7)}); + stream_->WriteMemSlices(data.span(), /*fin=*/false); + + EXPECT_EQ("Foo bar", write_buffer_); + + // Retransmit bytes [0, 7). + stream_->OnStreamFrameLost(0, 7, false); + stream_->OnCanWrite(); + + EXPECT_EQ("Foo barFoo bar", write_buffer_); + + // Ack bytes [0, 7). These bytes should be pruned from the data tracked by + // the stream. + QuicByteCount newly_acked_length = 0; + stream_->OnStreamFrameAcked(0, 7, false, QuicTime::Delta::FromMilliseconds(1), + &newly_acked_length); + EXPECT_EQ(7u, newly_acked_length); + stream_->OnCanWrite(); + + EXPECT_EQ("Foo barFoo bar", write_buffer_); + + // Retransmit bytes [0, 7) again. + // QUIC will never mark frames as lost after they've been acked, but this lets + // us test that QuartcStream stopped tracking these bytes after the acked. + stream_->OnStreamFrameLost(0, 7, false); + stream_->OnCanWrite(); + + // QuartcStream should be cancelled, but it stopped tracking the lost bytes + // after they were acked, so it's not. + EXPECT_EQ(stream_->stream_error(), QUIC_STREAM_NO_ERROR); +} + +TEST_F(QuartcStreamTest, TestBytesPendingRetransmission) { + CreateReliableQuicStream(); + stream_->set_cancel_on_loss(false); + + char message[] = "Foo bar"; + test::QuicTestMemSliceVector data({std::make_pair(message, 7)}); + stream_->WriteMemSlices(data.span(), /*fin=*/false); + + EXPECT_EQ("Foo bar", write_buffer_); + + stream_->OnStreamFrameLost(0, 4, false); + EXPECT_EQ(stream_->BytesPendingRetransmission(), 4u); + EXPECT_EQ(mock_stream_delegate_->last_bytes_pending_retransmission(), 4u); + + stream_->OnStreamFrameLost(4, 3, false); + EXPECT_EQ(stream_->BytesPendingRetransmission(), 7u); + EXPECT_EQ(mock_stream_delegate_->last_bytes_pending_retransmission(), 7u); + + stream_->OnCanWrite(); + EXPECT_EQ(stream_->BytesPendingRetransmission(), 0u); + EXPECT_EQ(mock_stream_delegate_->last_bytes_pending_retransmission(), 0u); + + EXPECT_EQ("Foo barFoo bar", write_buffer_); + EXPECT_EQ(stream_->stream_error(), QUIC_STREAM_NO_ERROR); +} + +TEST_F(QuartcStreamTest, TestBytesPendingRetransmissionWithCancelOnLoss) { + CreateReliableQuicStream(); + stream_->set_cancel_on_loss(true); + + char message[] = "Foo bar"; + test::QuicTestMemSliceVector data({std::make_pair(message, 7)}); + stream_->WriteMemSlices(data.span(), /*fin=*/false); + + EXPECT_EQ("Foo bar", write_buffer_); + + stream_->OnStreamFrameLost(0, 4, false); + EXPECT_EQ(stream_->BytesPendingRetransmission(), 0u); + EXPECT_EQ(mock_stream_delegate_->last_bytes_pending_retransmission(), 0u); + + stream_->OnStreamFrameLost(4, 3, false); + EXPECT_EQ(stream_->BytesPendingRetransmission(), 0u); + EXPECT_EQ(mock_stream_delegate_->last_bytes_pending_retransmission(), 0u); + + stream_->OnCanWrite(); + EXPECT_EQ(stream_->BytesPendingRetransmission(), 0u); + EXPECT_EQ(mock_stream_delegate_->last_bytes_pending_retransmission(), 0u); + + EXPECT_EQ("Foo bar", write_buffer_); + EXPECT_EQ(stream_->stream_error(), QUIC_STREAM_CANCELLED); +} + +} // namespace + +} // namespace quic
diff --git a/quic/quartc/simulated_packet_transport.cc b/quic/quartc/simulated_packet_transport.cc new file mode 100644 index 0000000..5c0d374 --- /dev/null +++ b/quic/quartc/simulated_packet_transport.cc
@@ -0,0 +1,95 @@ +// Copyright (c) 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/quic/quartc/simulated_packet_transport.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_str_cat.h" + +namespace quic { +namespace simulator { + +SimulatedQuartcPacketTransport::SimulatedQuartcPacketTransport( + Simulator* simulator, + const QuicString& name, + const QuicString& peer_name, + QuicByteCount queue_capacity) + : Endpoint(simulator, name), + peer_name_(peer_name), + egress_queue_(simulator, + QuicStringPrintf("%s (TX Queue)", name.c_str()), + queue_capacity) { + egress_queue_.set_listener_interface(this); +} + +int SimulatedQuartcPacketTransport::Write(const char* buffer, + size_t buf_len, + const PacketInfo& info) { + if (!writable_) { + return 0; + } + if (egress_queue_.bytes_queued() + buf_len > egress_queue_.capacity()) { + return 0; + } + + last_packet_number_ = info.packet_number; + + auto packet = QuicMakeUnique<Packet>(); + packet->contents = QuicString(buffer, buf_len); + packet->size = buf_len; + packet->tx_timestamp = clock_->Now(); + packet->source = name(); + packet->destination = peer_name_; + + egress_queue_.AcceptPacket(std::move(packet)); + return buf_len; +} + +void SimulatedQuartcPacketTransport::SetDelegate(Delegate* delegate) { + delegate_ = delegate; + Schedule(clock_->Now()); +} + +UnconstrainedPortInterface* SimulatedQuartcPacketTransport::GetRxPort() { + return this; +} + +void SimulatedQuartcPacketTransport::SetTxPort(ConstrainedPortInterface* port) { + egress_queue_.set_tx_port(port); + Schedule(clock_->Now()); +} + +void SimulatedQuartcPacketTransport::AcceptPacket( + std::unique_ptr<Packet> packet) { + // Simulated switches broadcast packets to all ports if the cannot determine + // the recipient, so we need to drop packets that aren't intended for us. + if (packet->destination != name()) { + return; + } + + if (delegate_) { + delegate_->OnTransportReceived(packet->contents.data(), packet->size); + } +} + +void SimulatedQuartcPacketTransport::OnPacketDequeued() { + if (delegate_ && writable_) { + delegate_->OnTransportCanWrite(); + } +} + +void SimulatedQuartcPacketTransport::Act() { + if (delegate_ && writable_) { + delegate_->OnTransportCanWrite(); + } +} + +void SimulatedQuartcPacketTransport::SetWritable(bool writable) { + writable_ = writable; + if (writable_) { + // May need to call |Delegate::OnTransportCanWrite|. + Schedule(clock_->Now()); + } +} + +} // namespace simulator +} // namespace quic
diff --git a/quic/quartc/simulated_packet_transport.h b/quic/quartc/simulated_packet_transport.h new file mode 100644 index 0000000..1b190a0 --- /dev/null +++ b/quic/quartc/simulated_packet_transport.h
@@ -0,0 +1,83 @@ +// Copyright (c) 2018 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. + +#ifndef QUICHE_QUIC_QUARTC_SIMULATED_PACKET_TRANSPORT_H_ +#define QUICHE_QUIC_QUARTC_SIMULATED_PACKET_TRANSPORT_H_ + +#include "net/third_party/quiche/src/quic/platform/api/quic_string.h" +#include "net/third_party/quiche/src/quic/quartc/quartc_packet_writer.h" +#include "net/third_party/quiche/src/quic/test_tools/simulator/port.h" +#include "net/third_party/quiche/src/quic/test_tools/simulator/queue.h" + +namespace quic { +namespace simulator { + +// Simulated implementation of QuartcPacketTransport. This packet transport +// implementation connects Quartc to a QUIC simulator's network fabric. +// Assumes that its caller and delegate run on the same thread as the network +// simulation and therefore require no additional synchronization. +class SimulatedQuartcPacketTransport : public Endpoint, + public QuartcPacketTransport, + public UnconstrainedPortInterface, + public Queue::ListenerInterface { + public: + SimulatedQuartcPacketTransport(Simulator* simulator, + const QuicString& name, + const QuicString& peer_name, + QuicByteCount queue_capacity); + + // QuartcPacketTransport methods. + int Write(const char* buffer, + size_t buf_len, + const PacketInfo& info) override; + void SetDelegate(Delegate* delegate) override; + + // Simulation methods below. These are implementation details. + + // Endpoint methods. Called by the simulation to connect the transport. + UnconstrainedPortInterface* GetRxPort() override; + void SetTxPort(ConstrainedPortInterface* port) override; + + // UnconstrainedPortInterface method. Called by the simulation to deliver a + // packet to this transport. + void AcceptPacket(std::unique_ptr<Packet> packet) override; + + // Queue::ListenerInterface method. Called when the internal egress queue has + // dispatched a packet and may have room for more. + void OnPacketDequeued() override; + + // Actor method. The transport schedules this to run when the delegate is set + // in order to trigger an initial call to |Delegate::OnTransportCanWrite()|. + // (The Quartc packet writer starts in a blocked state and needs an initial + // callback to unblock it.) + void Act() override; + + // Changes whether the transport is writable. If |writable| is false, the + // transport will reject calls to |Write| and will not call + // |Delegate::OnTransportCanWrite|. If |writable| is true, the transport will + // allow calls to |Write| and will call |Delegate::OnTransportCanWrite| + // whenever it is able to write another packet. + void SetWritable(bool writable); + + // Last packet number sent over this simulated transport. + // TODO(b/112561077): Reorganize tests so that this method can be deleted. + // This exists purely for use by quartc_session_test.cc, to test that the + // packet writer passes packet numbers to the transport. + QuicPacketNumber last_packet_number() { return last_packet_number_; } + + private: + QuicString peer_name_; + Delegate* delegate_ = nullptr; + Queue egress_queue_; + QuicPacketNumber last_packet_number_; + + // Controls whether the transport is considered to be writable. Used to + // simulate behavior that arises when the transport is blocked. + bool writable_ = true; +}; + +} // namespace simulator +} // namespace quic + +#endif // QUICHE_QUIC_QUARTC_SIMULATED_PACKET_TRANSPORT_H_
diff --git a/quic/quartc/simulated_packet_transport_test.cc b/quic/quartc/simulated_packet_transport_test.cc new file mode 100644 index 0000000..3fbd18b --- /dev/null +++ b/quic/quartc/simulated_packet_transport_test.cc
@@ -0,0 +1,165 @@ +// Copyright (c) 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/quic/quartc/simulated_packet_transport.h" + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_test.h" +#include "net/third_party/quiche/src/quic/quartc/quartc_packet_writer.h" +#include "net/third_party/quiche/src/quic/test_tools/simulator/simulator.h" +#include "net/third_party/quiche/src/quic/test_tools/simulator/switch.h" + +namespace quic { +namespace simulator { +namespace { + +using ::testing::ElementsAre; + +const QuicBandwidth kDefaultBandwidth = + QuicBandwidth::FromKBitsPerSecond(10 * 1000); +const QuicTime::Delta kDefaultPropagationDelay = + QuicTime::Delta::FromMilliseconds(20); +const QuicByteCount kDefaultBdp = kDefaultBandwidth * kDefaultPropagationDelay; +const QuicByteCount kDefaultPacketSize = 1200; +const QuicPacketCount kDefaultQueueLength = 10; + +class FakeDelegate : public QuartcPacketTransport::Delegate { + public: + explicit FakeDelegate(QuartcPacketTransport* transport) + : transport_(transport) { + transport_->SetDelegate(this); + } + + ~FakeDelegate() { transport_->SetDelegate(nullptr); } + + void OnTransportCanWrite() override { + while (!packets_to_send_.empty()) { + const QuicString& packet = packets_to_send_.front(); + if (transport_->Write(packet.data(), packet.size(), + QuartcPacketTransport::PacketInfo()) < + static_cast<int>(packet.size())) { + ++write_blocked_count_; + return; + } + packets_to_send_.pop(); + } + } + + void OnTransportReceived(const char* data, size_t data_len) override { + packets_received_.emplace_back(data, data_len); + } + + void AddPacketToSend(const QuicString& packet) { + packets_to_send_.push(packet); + } + + size_t packets_to_send() { return packets_to_send_.size(); } + const std::vector<QuicString>& packets_received() { + return packets_received_; + } + int write_blocked_count() { return write_blocked_count_; } + + private: + QuartcPacketTransport* const transport_ = nullptr; + std::queue<QuicString> packets_to_send_; + std::vector<QuicString> packets_received_; + int write_blocked_count_ = 0; +}; + +class SimulatedPacketTransportTest : public QuicTest { + protected: + SimulatedPacketTransportTest() + : switch_(&simulator_, "Switch", /*port_count=*/8, 2 * kDefaultBdp), + client_(&simulator_, + "sender", + "receiver", + kDefaultQueueLength * kDefaultPacketSize), + server_(&simulator_, + "receiver", + "sender", + kDefaultQueueLength * kDefaultPacketSize), + client_link_(&client_, + switch_.port(1), + kDefaultBandwidth, + kDefaultPropagationDelay), + server_link_(&server_, + switch_.port(2), + kDefaultBandwidth, + kDefaultPropagationDelay), + client_delegate_(&client_), + server_delegate_(&server_) {} + + Simulator simulator_; + Switch switch_; + + SimulatedQuartcPacketTransport client_; + SimulatedQuartcPacketTransport server_; + + SymmetricLink client_link_; + SymmetricLink server_link_; + + FakeDelegate client_delegate_; + FakeDelegate server_delegate_; +}; + +TEST_F(SimulatedPacketTransportTest, OneWayTransmission) { + QuicString packet_1(kDefaultPacketSize, 'a'); + QuicString packet_2(kDefaultPacketSize, 'b'); + client_delegate_.AddPacketToSend(packet_1); + client_delegate_.AddPacketToSend(packet_2); + + simulator_.RunUntil( + [this] { return client_delegate_.packets_to_send() == 0; }); + simulator_.RunFor(3 * kDefaultPropagationDelay); + + EXPECT_THAT(server_delegate_.packets_received(), + ElementsAre(packet_1, packet_2)); + EXPECT_THAT(client_delegate_.packets_received(), ElementsAre()); +} + +TEST_F(SimulatedPacketTransportTest, TwoWayTransmission) { + QuicString packet_1(kDefaultPacketSize, 'a'); + QuicString packet_2(kDefaultPacketSize, 'b'); + QuicString packet_3(kDefaultPacketSize, 'c'); + QuicString packet_4(kDefaultPacketSize, 'd'); + + client_delegate_.AddPacketToSend(packet_1); + client_delegate_.AddPacketToSend(packet_2); + server_delegate_.AddPacketToSend(packet_3); + server_delegate_.AddPacketToSend(packet_4); + + simulator_.RunUntil( + [this] { return client_delegate_.packets_to_send() == 0; }); + simulator_.RunUntil( + [this] { return server_delegate_.packets_to_send() == 0; }); + simulator_.RunFor(3 * kDefaultPropagationDelay); + + EXPECT_THAT(server_delegate_.packets_received(), + ElementsAre(packet_1, packet_2)); + EXPECT_THAT(client_delegate_.packets_received(), + ElementsAre(packet_3, packet_4)); +} + +TEST_F(SimulatedPacketTransportTest, TestWriteBlocked) { + // Add 10 packets beyond what fits in the egress queue. + std::vector<QuicString> packets; + for (unsigned int i = 0; i < kDefaultQueueLength + 10; ++i) { + packets.push_back(QuicString(kDefaultPacketSize, 'a' + i)); + client_delegate_.AddPacketToSend(packets.back()); + } + + simulator_.RunUntil( + [this] { return client_delegate_.packets_to_send() == 0; }); + simulator_.RunFor(3 * kDefaultPropagationDelay); + + // Each of the 10 packets in excess of the sender's egress queue length will + // block the first time |client_delegate_| tries to write them. + EXPECT_EQ(client_delegate_.write_blocked_count(), 10); + EXPECT_EQ(server_delegate_.packets_received(), packets); +} + +} // namespace +} // namespace simulator +} // namespace quic