// 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 "chrome/browser/prefs/pref_hash_store_impl.h" #include #include "base/macros.h" #include "base/values.h" #include "chrome/browser/prefs/pref_hash_store_impl.h" #include "chrome/browser/prefs/pref_hash_store_transaction.h" #include "chrome/browser/prefs/tracked/hash_store_contents.h" #include "testing/gtest/include/gtest/gtest.h" class MockHashStoreContents : public HashStoreContents { public: // Keep the data separate from the API implementation so that it can be owned // by the test and reused. The API implementation is owned by the // PrefHashStoreImpl. struct Data { Data() : commit_performed(false) {} // Returns the current value of |commit_performed| and resets it to false // immediately after. bool GetCommitPerformedAndReset() { bool current_commit_performed = commit_performed; commit_performed = false; return current_commit_performed; } scoped_ptr contents; std::string super_mac; scoped_ptr version; bool commit_performed; }; explicit MockHashStoreContents(Data* data) : data_(data) {} // HashStoreContents implementation virtual std::string hash_store_id() const OVERRIDE { return "store_id"; } virtual void Reset() OVERRIDE { data_->contents.reset(); data_->super_mac = ""; data_->version.reset(); } virtual bool IsInitialized() const OVERRIDE { return data_->contents; } virtual bool GetVersion(int* version) const OVERRIDE { if (data_->version) *version = *data_->version; return data_->version; } virtual void SetVersion(int version) OVERRIDE { data_->version.reset(new int(version)); } virtual const base::DictionaryValue* GetContents() const OVERRIDE { return data_->contents.get(); } virtual scoped_ptr GetMutableContents() OVERRIDE { return scoped_ptr(new MockMutableDictionary(data_)); } virtual std::string GetSuperMac() const OVERRIDE { return data_->super_mac; } virtual void SetSuperMac(const std::string& super_mac) OVERRIDE { data_->super_mac = super_mac; } virtual void CommitPendingWrite() OVERRIDE { EXPECT_FALSE(data_->commit_performed); data_->commit_performed = true; } private: class MockMutableDictionary : public MutableDictionary { public: explicit MockMutableDictionary(Data* data) : data_(data) {} // MutableDictionary implementation virtual base::DictionaryValue* operator->() OVERRIDE { if (!data_->contents) data_->contents.reset(new base::DictionaryValue); return data_->contents.get(); } private: Data* data_; DISALLOW_COPY_AND_ASSIGN(MockMutableDictionary); }; Data* data_; DISALLOW_COPY_AND_ASSIGN(MockHashStoreContents); }; class PrefHashStoreImplTest : public testing::Test { protected: scoped_ptr CreateHashStoreContents() { return scoped_ptr( new MockHashStoreContents(&hash_store_data_)); } MockHashStoreContents::Data hash_store_data_; }; TEST_F(PrefHashStoreImplTest, AtomicHashStoreAndCheck) { base::StringValue string_1("string1"); base::StringValue string_2("string2"); { // 32 NULL bytes is the seed that was used to generate the legacy hash. PrefHashStoreImpl pref_hash_store( std::string(32, 0), "device_id", CreateHashStoreContents()); scoped_ptr transaction( pref_hash_store.BeginTransaction()); // Only NULL should be trusted in the absence of a hash. EXPECT_EQ(PrefHashStoreTransaction::UNTRUSTED_UNKNOWN_VALUE, transaction->CheckValue("path1", &string_1)); EXPECT_EQ(PrefHashStoreTransaction::TRUSTED_UNKNOWN_VALUE, transaction->CheckValue("path1", NULL)); transaction->StoreHash("path1", &string_1); EXPECT_EQ(PrefHashStoreTransaction::UNCHANGED, transaction->CheckValue("path1", &string_1)); EXPECT_EQ(PrefHashStoreTransaction::CLEARED, transaction->CheckValue("path1", NULL)); transaction->StoreHash("path1", NULL); EXPECT_EQ(PrefHashStoreTransaction::UNCHANGED, transaction->CheckValue("path1", NULL)); EXPECT_EQ(PrefHashStoreTransaction::CHANGED, transaction->CheckValue("path1", &string_2)); base::DictionaryValue dict; dict.Set("a", new base::StringValue("foo")); dict.Set("d", new base::StringValue("bad")); dict.Set("b", new base::StringValue("bar")); dict.Set("c", new base::StringValue("baz")); // Manually shove in a legacy hash. (*CreateHashStoreContents()->GetMutableContents())->SetString( "path1", "C503FB7C65EEFD5C07185F616A0AA67923C069909933F362022B1F187E73E9A2"); EXPECT_EQ(PrefHashStoreTransaction::WEAK_LEGACY, transaction->CheckValue("path1", &dict)); transaction->StoreHash("path1", &dict); EXPECT_EQ(PrefHashStoreTransaction::UNCHANGED, transaction->CheckValue("path1", &dict)); // Test that the |pref_hash_store| flushes its changes on request post // transaction. transaction.reset(); pref_hash_store.CommitPendingWrite(); EXPECT_TRUE(hash_store_data_.GetCommitPerformedAndReset()); } { // |pref_hash_store2| should trust its initial hashes dictionary and thus // trust new unknown values. PrefHashStoreImpl pref_hash_store2( std::string(32, 0), "device_id", CreateHashStoreContents()); scoped_ptr transaction( pref_hash_store2.BeginTransaction()); EXPECT_EQ(PrefHashStoreTransaction::TRUSTED_UNKNOWN_VALUE, transaction->CheckValue("new_path", &string_1)); EXPECT_EQ(PrefHashStoreTransaction::TRUSTED_UNKNOWN_VALUE, transaction->CheckValue("new_path", &string_2)); EXPECT_EQ(PrefHashStoreTransaction::TRUSTED_UNKNOWN_VALUE, transaction->CheckValue("new_path", NULL)); // Test that |pref_hash_store2| doesn't flush its contents to disk when it // didn't change. transaction.reset(); pref_hash_store2.CommitPendingWrite(); EXPECT_FALSE(hash_store_data_.GetCommitPerformedAndReset()); } // Manually corrupt the super MAC. hash_store_data_.super_mac = std::string(64, 'A'); { // |pref_hash_store3| should no longer trust its initial hashes dictionary // and thus shouldn't trust non-NULL unknown values. PrefHashStoreImpl pref_hash_store3( std::string(32, 0), "device_id", CreateHashStoreContents()); scoped_ptr transaction( pref_hash_store3.BeginTransaction()); EXPECT_EQ(PrefHashStoreTransaction::UNTRUSTED_UNKNOWN_VALUE, transaction->CheckValue("new_path", &string_1)); EXPECT_EQ(PrefHashStoreTransaction::UNTRUSTED_UNKNOWN_VALUE, transaction->CheckValue("new_path", &string_2)); EXPECT_EQ(PrefHashStoreTransaction::TRUSTED_UNKNOWN_VALUE, transaction->CheckValue("new_path", NULL)); // Test that |pref_hash_store3| doesn't flush its contents to disk when it // didn't change. transaction.reset(); pref_hash_store3.CommitPendingWrite(); EXPECT_FALSE(hash_store_data_.GetCommitPerformedAndReset()); } } TEST_F(PrefHashStoreImplTest, SplitHashStoreAndCheck) { base::DictionaryValue dict; dict.Set("a", new base::StringValue("to be replaced")); dict.Set("b", new base::StringValue("same")); dict.Set("o", new base::StringValue("old")); base::DictionaryValue modified_dict; modified_dict.Set("a", new base::StringValue("replaced")); modified_dict.Set("b", new base::StringValue("same")); modified_dict.Set("c", new base::StringValue("new")); base::DictionaryValue empty_dict; std::vector invalid_keys; { PrefHashStoreImpl pref_hash_store( std::string(32, 0), "device_id", CreateHashStoreContents()); scoped_ptr transaction( pref_hash_store.BeginTransaction()); // No hashes stored yet and hashes dictionary is empty (and thus not // trusted). EXPECT_EQ(PrefHashStoreTransaction::UNTRUSTED_UNKNOWN_VALUE, transaction->CheckSplitValue("path1", &dict, &invalid_keys)); EXPECT_TRUE(invalid_keys.empty()); transaction->StoreSplitHash("path1", &dict); // Verify match post storage. EXPECT_EQ(PrefHashStoreTransaction::UNCHANGED, transaction->CheckSplitValue("path1", &dict, &invalid_keys)); EXPECT_TRUE(invalid_keys.empty()); // Verify new path is still unknown. EXPECT_EQ(PrefHashStoreTransaction::UNTRUSTED_UNKNOWN_VALUE, transaction->CheckSplitValue("path2", &dict, &invalid_keys)); EXPECT_TRUE(invalid_keys.empty()); // Verify NULL or empty dicts are declared as having been cleared. EXPECT_EQ(PrefHashStoreTransaction::CLEARED, transaction->CheckSplitValue("path1", NULL, &invalid_keys)); EXPECT_TRUE(invalid_keys.empty()); EXPECT_EQ( PrefHashStoreTransaction::CLEARED, transaction->CheckSplitValue("path1", &empty_dict, &invalid_keys)); EXPECT_TRUE(invalid_keys.empty()); // Verify changes are properly detected. EXPECT_EQ( PrefHashStoreTransaction::CHANGED, transaction->CheckSplitValue("path1", &modified_dict, &invalid_keys)); std::vector expected_invalid_keys1; expected_invalid_keys1.push_back("a"); expected_invalid_keys1.push_back("c"); expected_invalid_keys1.push_back("o"); EXPECT_EQ(expected_invalid_keys1, invalid_keys); invalid_keys.clear(); // Verify |dict| still matches post check. EXPECT_EQ(PrefHashStoreTransaction::UNCHANGED, transaction->CheckSplitValue("path1", &dict, &invalid_keys)); EXPECT_TRUE(invalid_keys.empty()); // Store hash for |modified_dict|. transaction->StoreSplitHash("path1", &modified_dict); // Verify |modified_dict| is now the one that verifies correctly. EXPECT_EQ( PrefHashStoreTransaction::UNCHANGED, transaction->CheckSplitValue("path1", &modified_dict, &invalid_keys)); EXPECT_TRUE(invalid_keys.empty()); // Verify old dict no longer matches. EXPECT_EQ(PrefHashStoreTransaction::CHANGED, transaction->CheckSplitValue("path1", &dict, &invalid_keys)); std::vector expected_invalid_keys2; expected_invalid_keys2.push_back("a"); expected_invalid_keys2.push_back("o"); expected_invalid_keys2.push_back("c"); EXPECT_EQ(expected_invalid_keys2, invalid_keys); invalid_keys.clear(); // Test that the |pref_hash_store| flushes its changes on request. transaction.reset(); pref_hash_store.CommitPendingWrite(); EXPECT_TRUE(hash_store_data_.GetCommitPerformedAndReset()); } { // |pref_hash_store2| should trust its initial hashes dictionary and thus // trust new unknown values. PrefHashStoreImpl pref_hash_store2( std::string(32, 0), "device_id", CreateHashStoreContents()); scoped_ptr transaction( pref_hash_store2.BeginTransaction()); EXPECT_EQ(PrefHashStoreTransaction::TRUSTED_UNKNOWN_VALUE, transaction->CheckSplitValue("new_path", &dict, &invalid_keys)); EXPECT_TRUE(invalid_keys.empty()); // Test that |pref_hash_store2| doesn't flush its contents to disk when it // didn't change. transaction.reset(); pref_hash_store2.CommitPendingWrite(); EXPECT_FALSE(hash_store_data_.GetCommitPerformedAndReset()); } // Manually corrupt the super MAC. hash_store_data_.super_mac = std::string(64, 'A'); { // |pref_hash_store3| should no longer trust its initial hashes dictionary // and thus shouldn't trust unknown values. PrefHashStoreImpl pref_hash_store3( std::string(32, 0), "device_id", CreateHashStoreContents()); scoped_ptr transaction( pref_hash_store3.BeginTransaction()); EXPECT_EQ(PrefHashStoreTransaction::UNTRUSTED_UNKNOWN_VALUE, transaction->CheckSplitValue("new_path", &dict, &invalid_keys)); EXPECT_TRUE(invalid_keys.empty()); // Test that |pref_hash_store3| doesn't flush its contents to disk when it // didn't change. transaction.reset(); pref_hash_store3.CommitPendingWrite(); EXPECT_FALSE(hash_store_data_.GetCommitPerformedAndReset()); } } TEST_F(PrefHashStoreImplTest, EmptyAndNULLSplitDict) { base::DictionaryValue empty_dict; std::vector invalid_keys; { PrefHashStoreImpl pref_hash_store( std::string(32, 0), "device_id", CreateHashStoreContents()); scoped_ptr transaction( pref_hash_store.BeginTransaction()); // Store hashes for a random dict to be overwritten below. base::DictionaryValue initial_dict; initial_dict.Set("a", new base::StringValue("foo")); transaction->StoreSplitHash("path1", &initial_dict); // Verify stored empty dictionary matches NULL and empty dictionary back. transaction->StoreSplitHash("path1", &empty_dict); EXPECT_EQ(PrefHashStoreTransaction::UNCHANGED, transaction->CheckSplitValue("path1", NULL, &invalid_keys)); EXPECT_TRUE(invalid_keys.empty()); EXPECT_EQ( PrefHashStoreTransaction::UNCHANGED, transaction->CheckSplitValue("path1", &empty_dict, &invalid_keys)); EXPECT_TRUE(invalid_keys.empty()); // Same when storing NULL directly. transaction->StoreSplitHash("path1", NULL); EXPECT_EQ(PrefHashStoreTransaction::UNCHANGED, transaction->CheckSplitValue("path1", NULL, &invalid_keys)); EXPECT_TRUE(invalid_keys.empty()); EXPECT_EQ( PrefHashStoreTransaction::UNCHANGED, transaction->CheckSplitValue("path1", &empty_dict, &invalid_keys)); EXPECT_TRUE(invalid_keys.empty()); } { // |pref_hash_store2| should trust its initial hashes dictionary (and thus // trust new unknown values) even though the last action done was to clear // the hashes for path1 by setting its value to NULL (this is a regression // test ensuring that the internal action of clearing some hashes does // update the stored hash of hashes). PrefHashStoreImpl pref_hash_store2( std::string(32, 0), "device_id", CreateHashStoreContents()); scoped_ptr transaction( pref_hash_store2.BeginTransaction()); base::DictionaryValue tested_dict; tested_dict.Set("a", new base::StringValue("foo")); tested_dict.Set("b", new base::StringValue("bar")); EXPECT_EQ( PrefHashStoreTransaction::TRUSTED_UNKNOWN_VALUE, transaction->CheckSplitValue("new_path", &tested_dict, &invalid_keys)); EXPECT_TRUE(invalid_keys.empty()); } } // Test that the PrefHashStore returns TRUSTED_UNKNOWN_VALUE when checking for // a split preference even if there is an existing atomic preference's hash // stored. There is no point providing a migration path for preferences // switching strategies after their initial release as split preferences are // turned into split preferences specifically because the atomic hash isn't // considered useful. TEST_F(PrefHashStoreImplTest, TrustedUnknownSplitValueFromExistingAtomic) { base::StringValue string("string1"); base::DictionaryValue dict; dict.Set("a", new base::StringValue("foo")); dict.Set("d", new base::StringValue("bad")); dict.Set("b", new base::StringValue("bar")); dict.Set("c", new base::StringValue("baz")); { PrefHashStoreImpl pref_hash_store( std::string(32, 0), "device_id", CreateHashStoreContents()); scoped_ptr transaction( pref_hash_store.BeginTransaction()); transaction->StoreHash("path1", &string); EXPECT_EQ(PrefHashStoreTransaction::UNCHANGED, transaction->CheckValue("path1", &string)); } { // Load a new |pref_hash_store2| in which the hashes dictionary is trusted. PrefHashStoreImpl pref_hash_store2( std::string(32, 0), "device_id", CreateHashStoreContents()); scoped_ptr transaction( pref_hash_store2.BeginTransaction()); std::vector invalid_keys; EXPECT_EQ(PrefHashStoreTransaction::TRUSTED_UNKNOWN_VALUE, transaction->CheckSplitValue("path1", &dict, &invalid_keys)); EXPECT_TRUE(invalid_keys.empty()); } } TEST_F(PrefHashStoreImplTest, GetCurrentVersion) { COMPILE_ASSERT(PrefHashStoreImpl::VERSION_LATEST == 2, new_versions_should_be_tested_here); { PrefHashStoreImpl pref_hash_store( std::string(32, 0), "device_id", CreateHashStoreContents()); // VERSION_UNINITIALIZED when no hashes are stored. EXPECT_EQ(PrefHashStoreImpl::VERSION_UNINITIALIZED, pref_hash_store.GetCurrentVersion()); scoped_ptr transaction( pref_hash_store.BeginTransaction()); base::StringValue string_value("foo"); transaction->StoreHash("path1", &string_value); // Test that |pref_hash_store| flushes its content to disk when it // initializes its version. transaction.reset(); pref_hash_store.CommitPendingWrite(); EXPECT_TRUE(hash_store_data_.GetCommitPerformedAndReset()); } { PrefHashStoreImpl pref_hash_store( std::string(32, 0), "device_id", CreateHashStoreContents()); // VERSION_LATEST after storing a hash. EXPECT_EQ(PrefHashStoreImpl::VERSION_LATEST, pref_hash_store.GetCurrentVersion()); // Test that |pref_hash_store| doesn't flush its contents to disk when it // didn't change. pref_hash_store.CommitPendingWrite(); EXPECT_FALSE(hash_store_data_.GetCommitPerformedAndReset()); } // Manually clear the version number. hash_store_data_.version.reset(); { PrefHashStoreImpl pref_hash_store( std::string(32, 0), "device_id", CreateHashStoreContents()); // VERSION_PRE_MIGRATION when no version is stored. EXPECT_EQ(PrefHashStoreImpl::VERSION_PRE_MIGRATION, pref_hash_store.GetCurrentVersion()); scoped_ptr transaction( pref_hash_store.BeginTransaction()); // Test that |pref_hash_store| flushes its content to disk when it // re-initializes its version. transaction.reset(); pref_hash_store.CommitPendingWrite(); EXPECT_TRUE(hash_store_data_.GetCommitPerformedAndReset()); } { PrefHashStoreImpl pref_hash_store( std::string(32, 0), "device_id", CreateHashStoreContents()); // Back to VERSION_LATEST after performing a transaction from // VERSION_PRE_MIGRATION (the presence of an existing hash should be // sufficient, no need for the transaction itself to perform any work). EXPECT_EQ(PrefHashStoreImpl::VERSION_LATEST, pref_hash_store.GetCurrentVersion()); // Test that |pref_hash_store| doesn't flush its contents to disk when it // didn't change (i.e., its version was already up-to-date and the only // transaction performed was empty). scoped_ptr transaction( pref_hash_store.BeginTransaction()); transaction.reset(); pref_hash_store.CommitPendingWrite(); EXPECT_FALSE(hash_store_data_.GetCommitPerformedAndReset()); } }