Index: net/quic/quartc/quartc_session_test.cc |
diff --git a/net/quic/quartc/quartc_session_test.cc b/net/quic/quartc/quartc_session_test.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..10a403599e93e4b2cd1cb273e38cccc4a6263e59 |
--- /dev/null |
+++ b/net/quic/quartc/quartc_session_test.cc |
@@ -0,0 +1,509 @@ |
+// Copyright (c) 2016 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/quic/quartc/quartc_session.h" |
+ |
+#include "base/bind.h" |
+#include "base/message_loop/message_loop.h" |
+#include "base/run_loop.h" |
+#include "base/threading/thread_task_runner_handle.h" |
+#include "net/base/ip_endpoint.h" |
+#include "net/quic/core/crypto/crypto_server_config_protobuf.h" |
+#include "net/quic/core/crypto/proof_source.h" |
+#include "net/quic/core/crypto/proof_verifier.h" |
+#include "net/quic/core/crypto/quic_crypto_client_config.h" |
+#include "net/quic/core/crypto/quic_crypto_server_config.h" |
+#include "net/quic/core/crypto/quic_random.h" |
+#include "net/quic/core/quic_crypto_client_stream.h" |
+#include "net/quic/core/quic_crypto_server_stream.h" |
+#include "net/quic/quartc/quartc_alarm_factory.h" |
+#include "net/quic/quartc/quartc_packet_writer.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+namespace net { |
+namespace test { |
+namespace { |
+ |
+// Testing SpdyPriority value for creating outgoing ReliableQuicStream. |
+// static const uint8_t kDefaultPriority = 3; |
+// TExport keying material function |
+static const char kExporterLabel[] = "label"; |
+static const uint8_t kExporterContext[] = "context"; |
+static const size_t kExporterContextLen = sizeof(kExporterContext); |
+static const size_t kOutputKeyLength = 20; |
+static QuartcStreamInterface::WriteParameters kDefaultWriteParam; |
+static QuartcSessionInterface::OutgoingStreamParameters kDefaultStreamParam; |
+ |
+// We use the MessageLoop to simulate the asynchronous P2P communication. The |
+// RunLoop is used for handling the posted tasks. |
+void SetTimedOutAndQuitLoop(const base::WeakPtr<bool> timed_out, |
+ const base::Closure& quit_loop_func) { |
+ if (timed_out) { |
+ *timed_out = true; |
+ quit_loop_func.Run(); |
+ } |
+} |
+ |
+bool RunLoopWithTimeout() { |
+ base::RunLoop rl; |
+ bool timed_out = false; |
+ base::WeakPtrFactory<bool> timed_out_weak_factory(&timed_out); |
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
+ FROM_HERE, |
+ base::Bind(&SetTimedOutAndQuitLoop, timed_out_weak_factory.GetWeakPtr(), |
+ rl.QuitClosure()), |
+ base::TimeDelta::FromMilliseconds(500)); |
+ rl.RunUntilIdle(); |
+ return !timed_out; |
+} |
+ |
+// Used by QuicCryptoServerConfig to provide server credentials, returning a |
+// canned response equal to |success|. |
+class FakeProofSource : public net::ProofSource { |
+ public: |
+ explicit FakeProofSource(bool success) : success_(success) {} |
+ |
+ // ProofSource override. |
+ bool GetProof(const IPAddress& server_ip, |
+ const std::string& hostname, |
+ const std::string& server_config, |
+ net::QuicVersion quic_version, |
+ base::StringPiece chlo_hash, |
+ scoped_refptr<net::ProofSource::Chain>* out_certs, |
+ std::string* out_signature, |
+ std::string* out_leaf_cert_sct) override { |
+ if (success_) { |
+ std::vector<std::string> certs; |
+ certs.push_back("Required to establish handshake"); |
+ *out_certs = new ProofSource::Chain(certs); |
+ *out_signature = "Signature"; |
+ *out_leaf_cert_sct = "Time"; |
+ } |
+ return success_; |
+ } |
+ |
+ void GetProof(const net::IPAddress& server_ip, |
+ const std::string& hostname, |
+ const std::string& server_config, |
+ net::QuicVersion quic_version, |
+ base::StringPiece chlo_hash, |
+ std::unique_ptr<Callback> callback) override { |
+ LOG(INFO) << "GetProof() providing dummy credentials for insecure QUIC"; |
+ } |
+ |
+ private: |
+ // Whether or not obtaining proof source succeeds. |
+ bool success_; |
+}; |
+ |
+// Used by QuicCryptoClientConfig to verify server credentials, returning a |
+// canned response of QUIC_SUCCESS if |success| is true. |
+class FakeProofVerifier : public net::ProofVerifier { |
+ public: |
+ explicit FakeProofVerifier(bool success) : success_(success) {} |
+ |
+ // ProofVerifier override |
+ net::QuicAsyncStatus VerifyProof( |
+ const std::string& hostname, |
+ const uint16_t port, |
+ const std::string& server_config, |
+ net::QuicVersion quic_version, |
+ base::StringPiece chlo_hash, |
+ const std::vector<std::string>& certs, |
+ const std::string& cert_sct, |
+ const std::string& signature, |
+ const ProofVerifyContext* context, |
+ std::string* error_details, |
+ std::unique_ptr<net::ProofVerifyDetails>* verify_details, |
+ std::unique_ptr<net::ProofVerifierCallback> callback) override { |
+ return success_ ? net::QUIC_SUCCESS : net::QUIC_FAILURE; |
+ } |
+ |
+ net::QuicAsyncStatus VerifyCertChain( |
+ const std::string& hostname, |
+ const std::vector<std::string>& certs, |
+ const net::ProofVerifyContext* context, |
+ std::string* error_details, |
+ std::unique_ptr<net::ProofVerifyDetails>* details, |
+ std::unique_ptr<net::ProofVerifierCallback> callback) override { |
+ LOG(INFO) << "VerifyProof() ignoring credentials and returning success"; |
+ return success_ ? net::QUIC_SUCCESS : net::QUIC_FAILURE; |
+ } |
+ |
+ private: |
+ // Whether or not proof verification succeeds. |
+ bool success_; |
+}; |
+ |
+// Used by the FakeTransportChannel. |
+class FakeTransportChannelObserver { |
+ public: |
+ // Called when the other peer is trying to send message. |
+ virtual void OnTransportChannelReadPacket(const std::string& data) = 0; |
+}; |
+ |
+// Simulate the P2P communication transport. Used by the |
+// QuartcSessionInterface::Transport. |
+class FakeTransportChannel { |
+ public: |
+ void SetDestination(FakeTransportChannel* dest) { |
+ if (!dest_) { |
+ dest_ = dest; |
+ dest_->SetDestination(this); |
+ } |
+ } |
+ |
+ int SendPacket(const char* data, size_t len) { |
+ // If the destination is not set. |
+ if (!dest_) { |
+ return -1; |
+ } |
+ if (async_) { |
+ base::ThreadTaskRunnerHandle::Get()->PostTask( |
+ FROM_HERE, |
+ base::Bind(&FakeTransportChannel::send, base::Unretained(this), |
+ std::string(data, len))); |
+ } else { |
+ send(std::string(data, len)); |
+ } |
+ return static_cast<int>(len); |
+ } |
+ |
+ void send(const std::string& data) { |
+ dest_->observer()->OnTransportChannelReadPacket(data); |
+ } |
+ |
+ FakeTransportChannelObserver* observer() { return observer_; } |
+ |
+ void SetObserver(FakeTransportChannelObserver* observer) { |
+ observer_ = observer; |
+ } |
+ |
+ void SetAsync(bool async) { async_ = async; } |
+ |
+ private: |
+ // The writing destination of this channel. |
+ FakeTransportChannel* dest_ = nullptr; |
+ // The observer of this channel. Called when the received the data. |
+ FakeTransportChannelObserver* observer_ = nullptr; |
+ // If async, will send packets by "Post"-ing to message queue instead of |
+ // synchronously "Send"-ing. |
+ bool async_ = false; |
+}; |
+ |
+// Used by the QuartcPacketWriter. |
+class FakeTransport : public QuartcSessionInterface::Transport { |
+ public: |
+ FakeTransport(FakeTransportChannel* channel) : channel_(channel) {} |
+ |
+ bool CanWrite() override { return true; } |
+ |
+ int Write(const char* buffer, size_t buf_len) override { |
+ DCHECK(channel_); |
+ return channel_->SendPacket(buffer, buf_len); |
+ } |
+ |
+ void SetDelegate(Delegate* delegate) override { delegate_ = delegate; } |
+ |
+ private: |
+ FakeTransportChannel* channel_; |
+ // QuartcSessionInterface::Transport::Delegate |
+ Delegate* delegate_; |
+}; |
+ |
+class FakeQuartcSessionDelegate : public QuartcSessionInterface::Delegate { |
+ public: |
+ FakeQuartcSessionDelegate(QuartcStreamInterface::Delegate* stream_delegate) |
+ : stream_delegate_(stream_delegate) {} |
+ // Called when peers have established forward-secure encryption |
+ void OnCryptoHandshakeComplete() override { |
+ LOG(INFO) << "Crypto handshake complete!"; |
+ } |
+ // Called when connection closes locally, or remotely by peer. |
+ void OnConnectionClosed(int error_code, bool from_remote) override { |
+ connected_ = false; |
+ } |
+ // Called when an incoming QUIC stream is created. |
+ void OnIncomingStream(QuartcStreamInterface* quartc_stream) override { |
+ last_incoming_stream_ = quartc_stream; |
+ last_incoming_stream_->SetDelegate(stream_delegate_); |
+ } |
+ |
+ QuartcStreamInterface* incoming_stream() { return last_incoming_stream_; } |
+ |
+ bool connected() { return connected_; } |
+ |
+ private: |
+ QuartcStreamInterface* last_incoming_stream_; |
+ bool connected_ = true; |
+ QuartcStream::Delegate* stream_delegate_; |
+}; |
+ |
+class FakeQuartcStreamDelegate : public QuartcStreamInterface::Delegate { |
+ public: |
+ void OnReceived(QuartcStreamInterface* stream, |
+ const char* data, |
+ size_t size) override { |
+ last_received_data_ = std::string(data, size); |
+ } |
+ |
+ void OnClose(QuartcStreamInterface* stream, int error_code) override {} |
+ |
+ void OnBufferedAmountChanged(QuartcStreamInterface* stream) override {} |
+ |
+ std::string data() { return last_received_data_; } |
+ |
+ private: |
+ std::string last_received_data_; |
+}; |
+ |
+class QuartcSessionForTest : public QuartcSession, |
+ public FakeTransportChannelObserver { |
+ public: |
+ QuartcSessionForTest(std::unique_ptr<QuicConnection> connection, |
+ const QuicConfig& config, |
+ const std::string& remote_fingerprint_value, |
+ Perspective perspective, |
+ QuartcSessionInterface::Transport* session_transport) |
+ : QuartcSession(std::move(connection), |
+ config, |
+ remote_fingerprint_value, |
+ perspective, |
+ session_transport) { |
+ stream_delegate_.reset(new FakeQuartcStreamDelegate); |
+ session_delegate_.reset( |
+ new FakeQuartcSessionDelegate(stream_delegate_.get())); |
+ |
+ SetDelegate(session_delegate_.get()); |
+ } |
+ |
+ // QuartcPacketWriter override. |
+ void OnTransportChannelReadPacket(const std::string& data) override { |
+ OnReceived(data.c_str(), data.length()); |
+ } |
+ |
+ std::string data() { return stream_delegate_->data(); } |
+ |
+ bool has_data() { return !data().empty(); } |
+ |
+ FakeQuartcSessionDelegate* session_delegate() { |
+ return session_delegate_.get(); |
+ } |
+ |
+ FakeQuartcStreamDelegate* stream_delegate() { return stream_delegate_.get(); } |
+ |
+ private: |
+ std::unique_ptr<FakeQuartcStreamDelegate> stream_delegate_; |
+ std::unique_ptr<FakeQuartcSessionDelegate> session_delegate_; |
+}; |
+ |
+class QuartcSessionTest : public ::testing::Test { |
+ public: |
+ ~QuartcSessionTest() override {} |
+ |
+ void Init() { |
+ helper_.reset(new QuartcConnectionHelper); |
+ clock_.reset(new QuicClock); |
+ config_.reset(new QuicConfig); |
+ |
+ client_channel_.reset(new FakeTransportChannel); |
+ server_channel_.reset(new FakeTransportChannel); |
+ // Make the channel asynchronous so that two peer will not keep calling each |
+ // other when they exchange information. |
+ client_channel_->SetAsync(true); |
+ client_channel_->SetDestination(server_channel_.get()); |
+ |
+ client_transport_.reset(new FakeTransport(client_channel_.get())); |
+ server_transport_.reset(new FakeTransport(server_channel_.get())); |
+ |
+ client_writer_.reset(new QuartcPacketWriter(client_transport_.get())); |
+ server_writer_.reset(new QuartcPacketWriter(server_transport_.get())); |
+ } |
+ |
+ // The parameters are used to control whether the handshake will success or |
+ // not. |
+ void CreateClientAndServerSessions(bool client_handshake_success = true, |
+ bool server_handshake_success = true) { |
+ Init(); |
+ client_peer_ = CreateSession(Perspective::IS_CLIENT); |
+ server_peer_ = CreateSession(Perspective::IS_SERVER); |
+ |
+ client_channel_->SetObserver(client_peer_.get()); |
+ server_channel_->SetObserver(server_peer_.get()); |
+ |
+ client_peer_->SetClientCryptoConfig( |
+ new QuicCryptoClientConfig(std::unique_ptr<ProofVerifier>( |
+ new FakeProofVerifier(client_handshake_success)))); |
+ |
+ QuicCryptoServerConfig* server_config = new QuicCryptoServerConfig( |
+ "TESTING", QuicRandom::GetInstance(), |
+ std::unique_ptr<FakeProofSource>( |
+ new FakeProofSource(server_handshake_success))); |
+ // Provide server with serialized config string to prove ownership. |
+ QuicCryptoServerConfig::ConfigOptions options; |
+ QuicServerConfigProtobuf* primary_config = server_config->GenerateConfig( |
+ QuicRandom::GetInstance(), clock_.get(), options); |
+ server_config->AddConfig(primary_config, clock_->WallNow()); |
+ |
+ server_peer_->SetServerCryptoConfig(server_config); |
+ } |
+ |
+ std::unique_ptr<QuartcSessionForTest> CreateSession(Perspective perspective) { |
+ std::unique_ptr<QuicConnection> quic_connection = |
+ CreateConnection(perspective); |
+ QuartcSessionInterface::Transport* transport = |
+ perspective == Perspective::IS_CLIENT ? client_transport_.get() |
+ : server_transport_.get(); |
+ std::string remote_fingerprint_value = "value"; |
+ return std::unique_ptr<QuartcSessionForTest>(new QuartcSessionForTest( |
+ std::move(quic_connection), *(config_.get()), remote_fingerprint_value, |
+ perspective, transport)); |
+ } |
+ |
+ std::unique_ptr<QuicConnection> CreateConnection(Perspective perspective) { |
+ QuartcPacketWriter* writer = perspective == Perspective::IS_CLIENT |
+ ? client_writer_.get() |
+ : server_writer_.get(); |
+ IPAddress ip(0, 0, 0, 0); |
+ bool owns_writer = false; |
+ alarm_factory_.reset(new QuartcAlarmFactory( |
+ base::ThreadTaskRunnerHandle::Get().get(), helper_->GetClock())); |
+ return std::unique_ptr<QuicConnection>(new QuicConnection( |
+ 0, IPEndPoint(ip, 0), helper_.get(), alarm_factory_.get(), writer, |
+ owns_writer, perspective, AllSupportedVersions())); |
+ } |
+ void StartHandshake() { |
+ server_peer_->StartCryptoHandshake(); |
+ client_peer_->StartCryptoHandshake(); |
+ RunLoopWithTimeout(); |
+ } |
+ |
+ // Test handshake establishment and sending/receiving of data for two |
+ // directions. |
+ void TestStreamConnection() { |
+ ASSERT_TRUE(server_peer_->IsCryptoHandshakeConfirmed() && |
+ client_peer_->IsCryptoHandshakeConfirmed()); |
+ ASSERT_TRUE(server_peer_->IsEncryptionEstablished()); |
+ ASSERT_TRUE(client_peer_->IsEncryptionEstablished()); |
+ |
+ uint8_t server_key[kOutputKeyLength]; |
+ uint8_t client_key[kOutputKeyLength]; |
+ bool use_context = true; |
+ bool server_success = server_peer_->ExportKeyingMaterial( |
+ kExporterLabel, kExporterContext, kExporterContextLen, use_context, |
+ server_key, kOutputKeyLength); |
+ ASSERT_TRUE(server_success); |
+ bool client_success = client_peer_->ExportKeyingMaterial( |
+ kExporterLabel, kExporterContext, kExporterContextLen, use_context, |
+ client_key, kOutputKeyLength); |
+ ASSERT_TRUE(client_success); |
+ EXPECT_EQ(0, memcmp(server_key, client_key, sizeof(server_key))); |
+ |
+ // Now we can establish encrypted outgoing stream. |
+ QuartcStreamInterface* outgoing_stream = |
+ server_peer_->CreateOutgoingStream(kDefaultStreamParam); |
+ ASSERT_NE(nullptr, outgoing_stream); |
+ EXPECT_TRUE(server_peer_->HasOpenDynamicStreams()); |
+ |
+ outgoing_stream->SetDelegate(server_peer_->stream_delegate()); |
+ |
+ // Send a test message from peer 1 to peer 2. |
+ const char kTestMessage[] = "Hello"; |
+ outgoing_stream->Write(kTestMessage, strlen(kTestMessage), |
+ kDefaultWriteParam); |
+ RunLoopWithTimeout(); |
+ |
+ // Wait for peer 2 to receive messages. |
+ ASSERT_TRUE(client_peer_->has_data()); |
+ |
+ QuartcStreamInterface* incoming = |
+ client_peer_->session_delegate()->incoming_stream(); |
+ ASSERT_TRUE(incoming); |
+ EXPECT_TRUE(client_peer_->HasOpenDynamicStreams()); |
+ |
+ EXPECT_EQ(client_peer_->data(), kTestMessage); |
+ // Send a test message from peer 2 to peer 1. |
+ const char kTestResponse[] = "Response"; |
+ incoming->Write(kTestResponse, strlen(kTestResponse), kDefaultWriteParam); |
+ RunLoopWithTimeout(); |
+ // Wait for peer 1 to receive messages. |
+ ASSERT_TRUE(server_peer_->has_data()); |
+ |
+ EXPECT_EQ(server_peer_->data(), kTestResponse); |
+ } |
+ |
+ // Test that client and server are not connected after handshake failure. |
+ void TestDisconnectAfterFailedHandshake() { |
+ EXPECT_TRUE(!client_peer_->session_delegate()->connected()); |
+ EXPECT_TRUE(!server_peer_->session_delegate()->connected()); |
+ |
+ EXPECT_FALSE(client_peer_->IsEncryptionEstablished()); |
+ EXPECT_FALSE(client_peer_->IsCryptoHandshakeConfirmed()); |
+ |
+ EXPECT_FALSE(server_peer_->IsEncryptionEstablished()); |
+ EXPECT_FALSE(server_peer_->IsCryptoHandshakeConfirmed()); |
+ } |
+ |
+ protected: |
+ std::unique_ptr<QuicConfig> config_; |
+ std::unique_ptr<QuartcConnectionHelper> helper_; |
+ std::unique_ptr<QuicClock> clock_; |
+ std::unique_ptr<QuicAlarmFactory> alarm_factory_; |
+ |
+ std::unique_ptr<FakeTransportChannel> client_channel_; |
+ std::unique_ptr<FakeTransportChannel> server_channel_; |
+ std::unique_ptr<FakeTransport> client_transport_; |
+ std::unique_ptr<FakeTransport> server_transport_; |
+ std::unique_ptr<QuartcPacketWriter> client_writer_; |
+ std::unique_ptr<QuartcPacketWriter> server_writer_; |
+ std::unique_ptr<QuartcSessionForTest> client_peer_; |
+ std::unique_ptr<QuartcSessionForTest> server_peer_; |
+}; |
+ |
+TEST_F(QuartcSessionTest, StreamConnection) { |
+ CreateClientAndServerSessions(); |
+ StartHandshake(); |
+ TestStreamConnection(); |
+} |
+ |
+TEST_F(QuartcSessionTest, ClientRejection) { |
+ CreateClientAndServerSessions(false /*client_handshake_success*/, |
+ true /*server_handshake_success*/); |
+ StartHandshake(); |
+ TestDisconnectAfterFailedHandshake(); |
+} |
+ |
+TEST_F(QuartcSessionTest, ServerRejection) { |
+ CreateClientAndServerSessions(true /*client_handshake_success*/, |
+ false /*server_handshake_success*/); |
+ StartHandshake(); |
+ TestDisconnectAfterFailedHandshake(); |
+} |
+ |
+// Test that data streams are not created before handshake. |
+TEST_F(QuartcSessionTest, CannotCreateDataStreamBeforeHandshake) { |
+ CreateClientAndServerSessions(); |
+ EXPECT_EQ(nullptr, server_peer_->CreateOutgoingStream(kDefaultStreamParam)); |
+ EXPECT_EQ(nullptr, client_peer_->CreateOutgoingStream(kDefaultStreamParam)); |
+} |
+ |
+TEST_F(QuartcSessionTest, CloseQuartcStream) { |
+ CreateClientAndServerSessions(); |
+ StartHandshake(); |
+ ASSERT_TRUE(client_peer_->IsCryptoHandshakeConfirmed() && |
+ server_peer_->IsCryptoHandshakeConfirmed()); |
+ QuartcStreamInterface* stream = |
+ client_peer_->CreateOutgoingStream(kDefaultStreamParam); |
+ ASSERT_NE(nullptr, stream); |
+ |
+ uint32_t id = stream->stream_id(); |
+ EXPECT_FALSE(client_peer_->IsClosedStream(id)); |
+ stream->SetDelegate(client_peer_->stream_delegate()); |
+ stream->Close(); |
+ EXPECT_TRUE(client_peer_->IsClosedStream(id)); |
+} |
+ |
+} // namespace |
+} // namespace test |
+} // namespace net |