// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/quic/crypto/quic_crypto_server_config.h" #include #include "base/stl_util.h" #include "net/quic/crypto/aes_128_gcm_12_encrypter.h" #include "net/quic/crypto/crypto_handshake_message.h" #include "net/quic/crypto/crypto_secret_boxer.h" #include "net/quic/crypto/crypto_server_config_protobuf.h" #include "net/quic/crypto/quic_random.h" #include "net/quic/crypto/strike_register_client.h" #include "net/quic/quic_flags.h" #include "net/quic/quic_time.h" #include "net/quic/test_tools/mock_clock.h" #include "net/quic/test_tools/quic_test_utils.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" using base::StringPiece; using std::map; using std::pair; using std::string; using std::vector; namespace net { namespace test { class QuicCryptoServerConfigPeer { public: explicit QuicCryptoServerConfigPeer(QuicCryptoServerConfig* server_config) : server_config_(server_config) {} scoped_refptr GetConfig(string config_id) { base::AutoLock locked(server_config_->configs_lock_); if (config_id == "") { return scoped_refptr( server_config_->primary_config_); } else { return server_config_->GetConfigWithScid(config_id); } } bool ConfigHasDefaultSourceAddressTokenBoxer(string config_id) { scoped_refptr config = GetConfig(config_id); return config->source_address_token_boxer == &(server_config_->default_source_address_token_boxer_); } string NewSourceAddressToken(string config_id, SourceAddressTokens previous_tokens, const IPEndPoint& ip, QuicRandom* rand, QuicWallTime now, CachedNetworkParameters* cached_network_params) { return server_config_->NewSourceAddressToken(*GetConfig(config_id), previous_tokens, ip, rand, now, cached_network_params); } HandshakeFailureReason ValidateSourceAddressToken(string config_id, StringPiece srct, const IPEndPoint& ip, QuicWallTime now) { return ValidateSourceAddressToken(config_id, srct, ip, now, NULL); } HandshakeFailureReason ValidateSourceAddressToken( string config_id, StringPiece srct, const IPEndPoint& ip, QuicWallTime now, CachedNetworkParameters* cached_network_params) { return server_config_->ValidateSourceAddressToken( *GetConfig(config_id), srct, ip, now, cached_network_params); } HandshakeFailureReason ValidateSourceAddressTokens(string config_id, StringPiece srct, const IPEndPoint& ip, QuicWallTime now) { return ValidateSourceAddressTokens(config_id, srct, ip, now, NULL); } HandshakeFailureReason ValidateSourceAddressTokens( string config_id, StringPiece srct, const IPEndPoint& ip, QuicWallTime now, CachedNetworkParameters* cached_network_params) { SourceAddressTokens tokens; HandshakeFailureReason reason = server_config_->ParseSourceAddressToken( *GetConfig(config_id), srct, &tokens); if (reason != HANDSHAKE_OK) { return reason; } return server_config_->ValidateSourceAddressTokens(tokens, ip, now, cached_network_params); } string NewServerNonce(QuicRandom* rand, QuicWallTime now) const { return server_config_->NewServerNonce(rand, now); } HandshakeFailureReason ValidateServerNonce(StringPiece token, QuicWallTime now) { return server_config_->ValidateServerNonce(token, now); } base::Lock* GetStrikeRegisterClientLock() { return &server_config_->strike_register_client_lock_; } // CheckConfigs compares the state of the Configs in |server_config_| to the // description given as arguments. The arguments are given as // nullptr-terminated pairs. The first of each pair is the server config ID of // a Config. The second is a boolean describing whether the config is the // primary. For example: // CheckConfigs(nullptr); // checks that no Configs are loaded. // // // Checks that exactly three Configs are loaded with the given IDs and // // status. // CheckConfigs( // "id1", false, // "id2", true, // "id3", false, // nullptr); void CheckConfigs(const char* server_config_id1, ...) { va_list ap; va_start(ap, server_config_id1); vector > expected; bool first = true; for (;;) { const char* server_config_id; if (first) { server_config_id = server_config_id1; first = false; } else { server_config_id = va_arg(ap, const char*); } if (!server_config_id) { break; } // varargs will promote the value to an int so we have to read that from // the stack and cast down. const bool is_primary = static_cast(va_arg(ap, int)); expected.push_back(std::make_pair(server_config_id, is_primary)); } va_end(ap); base::AutoLock locked(server_config_->configs_lock_); ASSERT_EQ(expected.size(), server_config_->configs_.size()) << ConfigsDebug(); for (QuicCryptoServerConfig::ConfigMap::const_iterator i = server_config_->configs_.begin(); i != server_config_->configs_.end(); ++i) { bool found = false; for (vector >::iterator j = expected.begin(); j != expected.end(); ++j) { if (i->first == j->first && i->second->is_primary == j->second) { found = true; j->first.clear(); break; } } ASSERT_TRUE(found) << "Failed to find match for " << i->first << " in configs:\n" << ConfigsDebug(); } } // ConfigsDebug returns a string that contains debugging information about // the set of Configs loaded in |server_config_| and their status. // ConfigsDebug() should be called after acquiring // server_config_->configs_lock_. string ConfigsDebug() { if (server_config_->configs_.empty()) { return "No Configs in QuicCryptoServerConfig"; } string s; for (QuicCryptoServerConfig::ConfigMap::const_iterator i = server_config_->configs_.begin(); i != server_config_->configs_.end(); ++i) { const scoped_refptr config = i->second; if (config->is_primary) { s += "(primary) "; } else { s += " "; } s += config->id; s += "\n"; } return s; } void SelectNewPrimaryConfig(int seconds) { base::AutoLock locked(server_config_->configs_lock_); server_config_->SelectNewPrimaryConfig( QuicWallTime::FromUNIXSeconds(seconds)); } private: const QuicCryptoServerConfig* server_config_; }; class TestStrikeRegisterClient : public StrikeRegisterClient { public: explicit TestStrikeRegisterClient(QuicCryptoServerConfig* config) : config_(config), is_known_orbit_called_(false) { } bool IsKnownOrbit(StringPiece orbit) const override { // Ensure that the strike register client lock is not held. QuicCryptoServerConfigPeer peer(config_); base::Lock* m = peer.GetStrikeRegisterClientLock(); // In Chromium, we will dead lock if the lock is held by the current thread. // Chromium doesn't have AssertNotHeld API call. // m->AssertNotHeld(); base::AutoLock lock(*m); is_known_orbit_called_ = true; return true; } void VerifyNonceIsValidAndUnique(StringPiece nonce, QuicWallTime now, ResultCallback* cb) override { LOG(FATAL) << "Not implemented"; } bool is_known_orbit_called() { return is_known_orbit_called_; } private: QuicCryptoServerConfig* config_; mutable bool is_known_orbit_called_; }; TEST(QuicCryptoServerConfigTest, ServerConfig) { QuicRandom* rand = QuicRandom::GetInstance(); QuicCryptoServerConfig server(QuicCryptoServerConfig::TESTING, rand); MockClock clock; scoped_ptr( server.AddDefaultConfig(rand, &clock, QuicCryptoServerConfig::ConfigOptions())); } TEST(QuicCryptoServerConfigTest, GetOrbitIsCalledWithoutTheStrikeRegisterLock) { QuicRandom* rand = QuicRandom::GetInstance(); QuicCryptoServerConfig server(QuicCryptoServerConfig::TESTING, rand); MockClock clock; TestStrikeRegisterClient* strike_register = new TestStrikeRegisterClient(&server); server.SetStrikeRegisterClient(strike_register); QuicCryptoServerConfig::ConfigOptions options; scoped_ptr( server.AddDefaultConfig(rand, &clock, options)); EXPECT_TRUE(strike_register->is_known_orbit_called()); } class SourceAddressTokenTest : public ::testing::Test { public: SourceAddressTokenTest() : ip4_(IPEndPoint(Loopback4(), 1)), ip4_dual_(ConvertIPv4NumberToIPv6Number(ip4_.address()), 1), ip6_(IPEndPoint(Loopback6(), 2)), original_time_(QuicWallTime::Zero()), rand_(QuicRandom::GetInstance()), server_(QuicCryptoServerConfig::TESTING, rand_), peer_(&server_) { // Advance the clock to some non-zero time. clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1000000)); original_time_ = clock_.WallNow(); primary_config_.reset(server_.AddDefaultConfig( rand_, &clock_, QuicCryptoServerConfig::ConfigOptions())); // Add a config that overrides the default boxer. QuicCryptoServerConfig::ConfigOptions options; options.id = kOverride; override_config_protobuf_.reset( QuicCryptoServerConfig::GenerateConfig(rand_, &clock_, options)); override_config_protobuf_->set_source_address_token_secret_override( "a secret key"); // Lower priority than the default config. override_config_protobuf_->set_priority(1); override_config_.reset( server_.AddConfig(override_config_protobuf_.get(), original_time_)); } string NewSourceAddressToken(string config_id, const IPEndPoint& ip) { return NewSourceAddressToken(config_id, ip, NULL); } string NewSourceAddressToken(string config_id, const IPEndPoint& ip, const SourceAddressTokens& previous_tokens) { return peer_.NewSourceAddressToken(config_id, previous_tokens, ip, rand_, clock_.WallNow(), NULL); } string NewSourceAddressToken(string config_id, const IPEndPoint& ip, CachedNetworkParameters* cached_network_params) { SourceAddressTokens previous_tokens; return peer_.NewSourceAddressToken(config_id, previous_tokens, ip, rand_, clock_.WallNow(), cached_network_params); } HandshakeFailureReason ValidateSourceAddressToken(string config_id, StringPiece srct, const IPEndPoint& ip) { return ValidateSourceAddressToken(config_id, srct, ip, NULL); } HandshakeFailureReason ValidateSourceAddressToken( string config_id, StringPiece srct, const IPEndPoint& ip, CachedNetworkParameters* cached_network_params) { return peer_.ValidateSourceAddressToken( config_id, srct, ip, clock_.WallNow(), cached_network_params); } HandshakeFailureReason ValidateSourceAddressTokens(string config_id, StringPiece srct, const IPEndPoint& ip) { return ValidateSourceAddressTokens(config_id, srct, ip, NULL); } HandshakeFailureReason ValidateSourceAddressTokens( string config_id, StringPiece srct, const IPEndPoint& ip, CachedNetworkParameters* cached_network_params) { return peer_.ValidateSourceAddressTokens( config_id, srct, ip, clock_.WallNow(), cached_network_params); } const string kPrimary = ""; const string kOverride = "Config with custom source address token key"; IPEndPoint ip4_; IPEndPoint ip4_dual_; IPEndPoint ip6_; MockClock clock_; QuicWallTime original_time_; QuicRandom* rand_ = QuicRandom::GetInstance(); QuicCryptoServerConfig server_; QuicCryptoServerConfigPeer peer_; // Stores the primary config. scoped_ptr primary_config_; scoped_ptr override_config_protobuf_; scoped_ptr override_config_; }; TEST_F(SourceAddressTokenTest, SourceAddressToken) { ValueRestore old_flag(&FLAGS_quic_use_multiple_address_in_source_tokens, false); EXPECT_TRUE(peer_.ConfigHasDefaultSourceAddressTokenBoxer(kPrimary)); EXPECT_FALSE(peer_.ConfigHasDefaultSourceAddressTokenBoxer(kOverride)); // Primary config generates configs that validate successfully. const string token4 = NewSourceAddressToken(kPrimary, ip4_); const string token4d = NewSourceAddressToken(kPrimary, ip4_dual_); const string token6 = NewSourceAddressToken(kPrimary, ip6_); EXPECT_EQ(HANDSHAKE_OK, ValidateSourceAddressToken(kPrimary, token4, ip4_)); ASSERT_EQ(HANDSHAKE_OK, ValidateSourceAddressToken(kPrimary, token4, ip4_dual_)); ASSERT_EQ(SOURCE_ADDRESS_TOKEN_DIFFERENT_IP_ADDRESS_FAILURE, ValidateSourceAddressToken(kPrimary, token4, ip6_)); ASSERT_EQ(HANDSHAKE_OK, ValidateSourceAddressToken(kPrimary, token4d, ip4_)); ASSERT_EQ(HANDSHAKE_OK, ValidateSourceAddressToken(kPrimary, token4d, ip4_dual_)); ASSERT_EQ(SOURCE_ADDRESS_TOKEN_DIFFERENT_IP_ADDRESS_FAILURE, ValidateSourceAddressToken(kPrimary, token4d, ip6_)); ASSERT_EQ(HANDSHAKE_OK, ValidateSourceAddressToken(kPrimary, token6, ip6_)); // Override config generates configs that validate successfully. const string override_token4 = NewSourceAddressToken(kOverride, ip4_); const string override_token6 = NewSourceAddressToken(kOverride, ip6_); ASSERT_EQ(HANDSHAKE_OK, ValidateSourceAddressToken(kOverride, override_token4, ip4_)); ASSERT_EQ(SOURCE_ADDRESS_TOKEN_DIFFERENT_IP_ADDRESS_FAILURE, ValidateSourceAddressToken(kOverride, override_token4, ip6_)); ASSERT_EQ(HANDSHAKE_OK, ValidateSourceAddressToken(kOverride, override_token6, ip6_)); // Tokens generated by the primary config do not validate // successfully against the override config, and vice versa. ASSERT_EQ(SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE, ValidateSourceAddressToken(kOverride, token4, ip4_)); ASSERT_EQ(SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE, ValidateSourceAddressToken(kOverride, token6, ip6_)); ASSERT_EQ(SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE, ValidateSourceAddressToken(kPrimary, override_token4, ip4_)); ASSERT_EQ(SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE, ValidateSourceAddressToken(kPrimary, override_token6, ip6_)); } TEST_F(SourceAddressTokenTest, SourceAddressTokenExpiration) { ValueRestore old_flag(&FLAGS_quic_use_multiple_address_in_source_tokens, false); const string token = NewSourceAddressToken(kPrimary, ip4_); // Validation fails if the token is from the future. clock_.AdvanceTime(QuicTime::Delta::FromSeconds(-3600 * 2)); ASSERT_EQ(SOURCE_ADDRESS_TOKEN_CLOCK_SKEW_FAILURE, ValidateSourceAddressToken(kPrimary, token, ip4_)); // Validation fails after tokens expire. clock_.AdvanceTime(QuicTime::Delta::FromSeconds(86400 * 7)); ASSERT_EQ(SOURCE_ADDRESS_TOKEN_EXPIRED_FAILURE, ValidateSourceAddressToken(kPrimary, token, ip4_)); } TEST_F(SourceAddressTokenTest, SourceAddressTokenWithNetworkParams) { ValueRestore old_flag(&FLAGS_quic_use_multiple_address_in_source_tokens, false); // Make sure that if the source address token contains CachedNetworkParameters // that this gets written to ValidateSourceAddressToken output argument. CachedNetworkParameters cached_network_params_input; cached_network_params_input.set_bandwidth_estimate_bytes_per_second(1234); const string token4_with_cached_network_params = NewSourceAddressToken(kPrimary, ip4_, &cached_network_params_input); CachedNetworkParameters cached_network_params_output; #if 0 // TODO(rtenneti): For server, enable the following check after serialization // of optional CachedNetworkParameters is implemented. EXPECT_NE(cached_network_params_output.DebugString(), cached_network_params_input.DebugString()); #endif ValidateSourceAddressToken(kPrimary, token4_with_cached_network_params, ip4_, &cached_network_params_output); #if 0 // TODO(rtenneti): For server, enable the following check after serialization // of optional CachedNetworkParameters is implemented. EXPECT_EQ(cached_network_params_output.DebugString(), cached_network_params_input.DebugString()); #endif } // Test basic behavior of source address tokens including being specific // to a single IP address and server config. // // TODO(rtenneti): For server, enable the following test after serialization of // SourceAddressTokens is implemented. TEST_F(SourceAddressTokenTest, DISABLED_NewSourceAddressToken) { ValueRestore old_flag(&FLAGS_quic_use_multiple_address_in_source_tokens, true); // Primary config generates configs that validate successfully. const string token4 = NewSourceAddressToken(kPrimary, ip4_); const string token4d = NewSourceAddressToken(kPrimary, ip4_dual_); const string token6 = NewSourceAddressToken(kPrimary, ip6_); EXPECT_EQ(HANDSHAKE_OK, ValidateSourceAddressTokens(kPrimary, token4, ip4_)); ASSERT_EQ(HANDSHAKE_OK, ValidateSourceAddressTokens(kPrimary, token4, ip4_dual_)); ASSERT_EQ(SOURCE_ADDRESS_TOKEN_DIFFERENT_IP_ADDRESS_FAILURE, ValidateSourceAddressTokens(kPrimary, token4, ip6_)); ASSERT_EQ(HANDSHAKE_OK, ValidateSourceAddressTokens(kPrimary, token4d, ip4_)); ASSERT_EQ(HANDSHAKE_OK, ValidateSourceAddressTokens(kPrimary, token4d, ip4_dual_)); ASSERT_EQ(SOURCE_ADDRESS_TOKEN_DIFFERENT_IP_ADDRESS_FAILURE, ValidateSourceAddressTokens(kPrimary, token4d, ip6_)); ASSERT_EQ(HANDSHAKE_OK, ValidateSourceAddressTokens(kPrimary, token6, ip6_)); // Override config generates configs that validate successfully. const string override_token4 = NewSourceAddressToken(kOverride, ip4_); const string override_token6 = NewSourceAddressToken(kOverride, ip6_); ASSERT_EQ(HANDSHAKE_OK, ValidateSourceAddressTokens(kOverride, override_token4, ip4_)); ASSERT_EQ(SOURCE_ADDRESS_TOKEN_DIFFERENT_IP_ADDRESS_FAILURE, ValidateSourceAddressTokens(kOverride, override_token4, ip6_)); ASSERT_EQ(HANDSHAKE_OK, ValidateSourceAddressTokens(kOverride, override_token6, ip6_)); // Tokens generated by the primary config do not validate // successfully against the override config, and vice versa. ASSERT_EQ(SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE, ValidateSourceAddressTokens(kOverride, token4, ip4_)); ASSERT_EQ(SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE, ValidateSourceAddressTokens(kOverride, token6, ip6_)); ASSERT_EQ(SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE, ValidateSourceAddressTokens(kPrimary, override_token4, ip4_)); ASSERT_EQ(SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE, ValidateSourceAddressTokens(kPrimary, override_token6, ip6_)); } // TODO(rtenneti): For server, enable the following test after serialization of // SourceAddressTokens is implemented. TEST_F(SourceAddressTokenTest, DISABLED_NewSourceAddressTokenExpiration) { ValueRestore old_flag(&FLAGS_quic_use_multiple_address_in_source_tokens, true); const string token = NewSourceAddressToken(kPrimary, ip4_); // Validation fails if the token is from the future. clock_.AdvanceTime(QuicTime::Delta::FromSeconds(-3600 * 2)); ASSERT_EQ(SOURCE_ADDRESS_TOKEN_CLOCK_SKEW_FAILURE, ValidateSourceAddressTokens(kPrimary, token, ip4_)); // Validation fails after tokens expire. clock_.AdvanceTime(QuicTime::Delta::FromSeconds(86400 * 7)); ASSERT_EQ(SOURCE_ADDRESS_TOKEN_EXPIRED_FAILURE, ValidateSourceAddressTokens(kPrimary, token, ip4_)); } TEST_F(SourceAddressTokenTest, NewSourceAddressTokenWithNetworkParams) { ValueRestore old_flag(&FLAGS_quic_use_multiple_address_in_source_tokens, true); // Make sure that if the source address token contains CachedNetworkParameters // that this gets written to ValidateSourceAddressToken output argument. CachedNetworkParameters cached_network_params_input; cached_network_params_input.set_bandwidth_estimate_bytes_per_second(1234); const string token4_with_cached_network_params = NewSourceAddressToken(kPrimary, ip4_, &cached_network_params_input); CachedNetworkParameters cached_network_params_output; #if 0 // TODO(rtenneti): For server, enable the following check after serialization // of optional CachedNetworkParameters is implemented. EXPECT_NE(cached_network_params_output.DebugString(), cached_network_params_input.DebugString()); #endif ValidateSourceAddressTokens(kPrimary, token4_with_cached_network_params, ip4_, &cached_network_params_output); #if 0 // TODO(rtenneti): For server, enable the following check after serialization // of optional CachedNetworkParameters is implemented. EXPECT_EQ(cached_network_params_output.DebugString(), cached_network_params_input.DebugString()); #endif } // Test the ability for a source address token to be valid for multiple // addresses. // // TODO(rtenneti): For server, enable the following test after serialization of // SourceAddressTokens is implemented. TEST_F(SourceAddressTokenTest, DISABLED_SourceAddressTokenMultipleAddresses) { ValueRestore old_flag(&FLAGS_quic_use_multiple_address_in_source_tokens, true); QuicWallTime now = clock_.WallNow(); // Now create a token which is usable for both addresses. SourceAddressToken previous_token; IPAddressNumber ip_address = ip6_.address(); if (ip6_.GetSockAddrFamily() == AF_INET) { ip_address = ConvertIPv4NumberToIPv6Number(ip_address); } previous_token.set_ip(IPAddressToPackedString(ip_address)); previous_token.set_timestamp(now.ToUNIXSeconds()); SourceAddressTokens previous_tokens; (*previous_tokens.add_tokens()) = previous_token; const string token4or6 = NewSourceAddressToken(kPrimary, ip4_, previous_tokens); EXPECT_EQ(HANDSHAKE_OK, ValidateSourceAddressTokens(kPrimary, token4or6, ip4_)); ASSERT_EQ(HANDSHAKE_OK, ValidateSourceAddressTokens(kPrimary, token4or6, ip6_)); } TEST(QuicCryptoServerConfigTest, ValidateServerNonce) { QuicRandom* rand = QuicRandom::GetInstance(); QuicCryptoServerConfig server(QuicCryptoServerConfig::TESTING, rand); QuicCryptoServerConfigPeer peer(&server); StringPiece message("hello world"); const size_t key_size = CryptoSecretBoxer::GetKeySize(); scoped_ptr key(new uint8[key_size]); memset(key.get(), 0x11, key_size); CryptoSecretBoxer boxer; boxer.SetKey(StringPiece(reinterpret_cast(key.get()), key_size)); const string box = boxer.Box(rand, message); MockClock clock; QuicWallTime now = clock.WallNow(); const QuicWallTime original_time = now; EXPECT_EQ(SERVER_NONCE_DECRYPTION_FAILURE, peer.ValidateServerNonce(box, now)); string server_nonce = peer.NewServerNonce(rand, now); EXPECT_EQ(HANDSHAKE_OK, peer.ValidateServerNonce(server_nonce, now)); EXPECT_EQ(SERVER_NONCE_NOT_UNIQUE_FAILURE, peer.ValidateServerNonce(server_nonce, now)); now = original_time.Add(QuicTime::Delta::FromSeconds(1000 * 7)); server_nonce = peer.NewServerNonce(rand, now); EXPECT_EQ(HANDSHAKE_OK, peer.ValidateServerNonce(server_nonce, now)); } class CryptoServerConfigsTest : public ::testing::Test { public: CryptoServerConfigsTest() : rand_(QuicRandom::GetInstance()), config_(QuicCryptoServerConfig::TESTING, rand_), test_peer_(&config_) {} void SetUp() override { clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1000)); } // SetConfigs constructs suitable config protobufs and calls SetConfigs on // |config_|. The arguments are given as nullptr-terminated pairs. The first // of each pair is the server config ID of a Config. The second is the // |primary_time| of that Config, given in epoch seconds. (Although note that, // in these tests, time is set to 1000 seconds since the epoch.) For example: // SetConfigs(nullptr); // calls |config_.SetConfigs| with no protobufs. // // // Calls |config_.SetConfigs| with two protobufs: one for a Config with // // a |primary_time| of 900 and priority 1, and another with // // a |primary_time| of 1000 and priority 2. // CheckConfigs( // "id1", 900, 1, // "id2", 1000, 2, // nullptr); // // If the server config id starts with "INVALID" then the generated protobuf // will be invalid. void SetConfigs(const char* server_config_id1, ...) { const char kOrbit[] = "12345678"; va_list ap; va_start(ap, server_config_id1); bool has_invalid = false; bool is_empty = true; vector protobufs; bool first = true; for (;;) { const char* server_config_id; if (first) { server_config_id = server_config_id1; first = false; } else { server_config_id = va_arg(ap, const char*); } if (!server_config_id) { break; } is_empty = false; int primary_time = va_arg(ap, int); int priority = va_arg(ap, int); QuicCryptoServerConfig::ConfigOptions options; options.id = server_config_id; options.orbit = kOrbit; QuicServerConfigProtobuf* protobuf( QuicCryptoServerConfig::GenerateConfig(rand_, &clock_, options)); protobuf->set_primary_time(primary_time); protobuf->set_priority(priority); if (string(server_config_id).find("INVALID") == 0) { protobuf->clear_key(); has_invalid = true; } protobufs.push_back(protobuf); } ASSERT_EQ(!has_invalid && !is_empty, config_.SetConfigs(protobufs, clock_.WallNow())); STLDeleteElements(&protobufs); } protected: QuicRandom* const rand_; MockClock clock_; QuicCryptoServerConfig config_; QuicCryptoServerConfigPeer test_peer_; }; TEST_F(CryptoServerConfigsTest, NoConfigs) { test_peer_.CheckConfigs(nullptr); } TEST_F(CryptoServerConfigsTest, MakePrimaryFirst) { // Make sure that "b" is primary even though "a" comes first. SetConfigs("a", 1100, 1, "b", 900, 1, nullptr); test_peer_.CheckConfigs( "a", false, "b", true, nullptr); } TEST_F(CryptoServerConfigsTest, MakePrimarySecond) { // Make sure that a remains primary after b is added. SetConfigs("a", 900, 1, "b", 1100, 1, nullptr); test_peer_.CheckConfigs( "a", true, "b", false, nullptr); } TEST_F(CryptoServerConfigsTest, Delete) { // Ensure that configs get deleted when removed. SetConfigs("a", 800, 1, "b", 900, 1, "c", 1100, 1, nullptr); test_peer_.CheckConfigs( "a", false, "b", true, "c", false, nullptr); SetConfigs("b", 900, 1, "c", 1100, 1, nullptr); test_peer_.CheckConfigs( "b", true, "c", false, nullptr); } TEST_F(CryptoServerConfigsTest, DeletePrimary) { // Ensure that deleting the primary config works. SetConfigs("a", 800, 1, "b", 900, 1, "c", 1100, 1, nullptr); test_peer_.CheckConfigs( "a", false, "b", true, "c", false, nullptr); SetConfigs("a", 800, 1, "c", 1100, 1, nullptr); test_peer_.CheckConfigs( "a", true, "c", false, nullptr); } TEST_F(CryptoServerConfigsTest, FailIfDeletingAllConfigs) { // Ensure that configs get deleted when removed. SetConfigs("a", 800, 1, "b", 900, 1, nullptr); test_peer_.CheckConfigs( "a", false, "b", true, nullptr); SetConfigs(nullptr); // Config change is rejected, still using old configs. test_peer_.CheckConfigs( "a", false, "b", true, nullptr); } TEST_F(CryptoServerConfigsTest, ChangePrimaryTime) { // Check that updates to primary time get picked up. SetConfigs("a", 400, 1, "b", 800, 1, "c", 1200, 1, nullptr); test_peer_.SelectNewPrimaryConfig(500); test_peer_.CheckConfigs( "a", true, "b", false, "c", false, nullptr); SetConfigs("a", 1200, 1, "b", 800, 1, "c", 400, 1, nullptr); test_peer_.SelectNewPrimaryConfig(500); test_peer_.CheckConfigs( "a", false, "b", false, "c", true, nullptr); } TEST_F(CryptoServerConfigsTest, AllConfigsInThePast) { // Check that the most recent config is selected. SetConfigs("a", 400, 1, "b", 800, 1, "c", 1200, 1, nullptr); test_peer_.SelectNewPrimaryConfig(1500); test_peer_.CheckConfigs( "a", false, "b", false, "c", true, nullptr); } TEST_F(CryptoServerConfigsTest, AllConfigsInTheFuture) { // Check that the first config is selected. SetConfigs("a", 400, 1, "b", 800, 1, "c", 1200, 1, nullptr); test_peer_.SelectNewPrimaryConfig(100); test_peer_.CheckConfigs( "a", true, "b", false, "c", false, nullptr); } TEST_F(CryptoServerConfigsTest, SortByPriority) { // Check that priority is used to decide on a primary config when // configs have the same primary time. SetConfigs("a", 900, 1, "b", 900, 2, "c", 900, 3, nullptr); test_peer_.CheckConfigs( "a", true, "b", false, "c", false, nullptr); test_peer_.SelectNewPrimaryConfig(800); test_peer_.CheckConfigs( "a", true, "b", false, "c", false, nullptr); test_peer_.SelectNewPrimaryConfig(1000); test_peer_.CheckConfigs( "a", true, "b", false, "c", false, nullptr); // Change priorities and expect sort order to change. SetConfigs("a", 900, 2, "b", 900, 1, "c", 900, 0, nullptr); test_peer_.CheckConfigs( "a", false, "b", false, "c", true, nullptr); test_peer_.SelectNewPrimaryConfig(800); test_peer_.CheckConfigs( "a", false, "b", false, "c", true, nullptr); test_peer_.SelectNewPrimaryConfig(1000); test_peer_.CheckConfigs( "a", false, "b", false, "c", true, nullptr); } TEST_F(CryptoServerConfigsTest, AdvancePrimary) { // Check that a new primary config is enabled at the right time. SetConfigs("a", 900, 1, "b", 1100, 1, nullptr); test_peer_.SelectNewPrimaryConfig(1000); test_peer_.CheckConfigs( "a", true, "b", false, nullptr); test_peer_.SelectNewPrimaryConfig(1101); test_peer_.CheckConfigs( "a", false, "b", true, nullptr); } TEST_F(CryptoServerConfigsTest, InvalidConfigs) { // Ensure that invalid configs don't change anything. SetConfigs("a", 800, 1, "b", 900, 1, "c", 1100, 1, nullptr); test_peer_.CheckConfigs( "a", false, "b", true, "c", false, nullptr); SetConfigs("a", 800, 1, "c", 1100, 1, "INVALID1", 1000, 1, nullptr); test_peer_.CheckConfigs( "a", false, "b", true, "c", false, nullptr); } } // namespace test } // namespace net