blob: 0f92a48945c95e631f18c54078c86c178bc4cab1 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/variations/variations_seed_store.h"
#include <memory>
#include <utility>
#include "base/base64.h"
#include "base/build_time.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/callback_helpers.h"
#include "base/run_loop.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_entropy_provider.h"
#include "base/test/protobuf_matchers.h"
#include "base/test/scoped_command_line.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/threading/thread.h"
#include "base/time/time.h"
#include "base/timer/mock_timer.h"
#include "base/version.h"
#include "build/build_config.h"
#include "components/prefs/testing_pref_service.h"
#include "components/variations/client_filterable_state.h"
#include "components/variations/entropy_provider.h"
#include "components/variations/pref_names.h"
#include "components/variations/proto/study.pb.h"
#include "components/variations/proto/variations_seed.pb.h"
#include "components/variations/variations_safe_seed_store_local_state.h"
#include "components/variations/variations_switches.h"
#include "components/variations/variations_test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/zlib/google/compression_utils.h"
#if BUILDFLAG(IS_ANDROID)
#include "components/variations/android/variations_seed_bridge.h"
#endif // BUILDFLAG(IS_ANDROID)
#if BUILDFLAG(IS_CHROMEOS)
#include "chromeos/ash/components/dbus/featured/fake_featured_client.h"
#include "chromeos/ash/components/dbus/featured/featured.pb.h"
#endif // BUILDFLAG(IS_CHROMEOS)
namespace variations {
namespace {
using ::base::test::EqualsProto;
using ::testing::IsEmpty;
using ::testing::Not;
// The sentinel value that may be stored as the latest variations seed value in
// prefs to indicate that the latest seed is identical to the safe seed.
// Note: This constant is intentionally duplicated in the test because it is
// persisted to disk. In order to maintain backward-compatibility, it's
// important that code continue to correctly handle this specific constant, even
// if the constant used internally in the implementation changes.
constexpr char kIdenticalToSafeSeedSentinel[] = "safe_seed_content";
// File used by SeedReaderWriter to store a latest seed.
const base::FilePath::CharType kSeedFilename[] = FILE_PATH_LITERAL("TestSeed");
// Used for clients that do not participate in SeedFiles experiment.
constexpr char kNoGroup[] = "";
class TestVariationsSeedStore : public VariationsSeedStore {
public:
explicit TestVariationsSeedStore(
PrefService* local_state,
base::FilePath seed_file_dir = base::FilePath(),
bool signature_verification_needed = false,
std::unique_ptr<SeedResponse> initial_seed = nullptr,
bool use_first_run_prefs = true,
version_info::Channel channel = version_info::Channel::UNKNOWN,
std::unique_ptr<const EntropyProviders> entropy_providers =
std::make_unique<const MockEntropyProviders>(
MockEntropyProviders::Results{
.low_entropy = kAlwaysUseLastGroup}))
: VariationsSeedStore(local_state,
std::move(initial_seed),
signature_verification_needed,
std::make_unique<VariationsSafeSeedStoreLocalState>(
local_state,
seed_file_dir,
channel,
entropy_providers.get()),
channel,
seed_file_dir,
entropy_providers.get(),
use_first_run_prefs) {}
~TestVariationsSeedStore() override = default;
};
// Creates a base::Time object from the corresponding raw value. The specific
// implementation is not important; it's only important that distinct inputs map
// to distinct outputs.
base::Time WrapTime(int64_t time) {
return base::Time::FromDeltaSinceWindowsEpoch(base::Microseconds(time));
}
// Populates |seed| with simple test data. The resulting seed will contain one
// study called "test", which contains one experiment called "abc" with
// probability weight 100. |seed|'s study field will be cleared before adding
// the new study.
VariationsSeed CreateTestSeed() {
VariationsSeed seed;
Study* study = seed.add_study();
study->set_name("test");
study->set_default_experiment_name("abc");
Study_Experiment* experiment = study->add_experiment();
experiment->set_name("abc");
experiment->set_probability_weight(100);
seed.set_serial_number("123");
return seed;
}
// Returns a ClientFilterableState with all fields set to "interesting" values
// for testing.
std::unique_ptr<ClientFilterableState> CreateTestClientFilterableState() {
std::unique_ptr<ClientFilterableState> client_state =
std::make_unique<ClientFilterableState>(
base::BindOnce([] { return false; }),
base::BindOnce([] { return base::flat_set<uint64_t>(); }));
client_state->locale = "es-MX";
client_state->reference_date = WrapTime(1234554321);
client_state->version = base::Version("1.2.3.4");
client_state->channel = Study::CANARY;
client_state->form_factor = Study::PHONE;
client_state->platform = Study::PLATFORM_MAC;
client_state->hardware_class = "mario";
client_state->is_low_end_device = true;
client_state->session_consistency_country = "mx";
client_state->permanent_consistency_country = "br";
return client_state;
}
// Serializes |seed| to protobuf binary format.
std::string SerializeSeed(const VariationsSeed& seed) {
std::string serialized_seed;
seed.SerializeToString(&serialized_seed);
return serialized_seed;
}
// Compresses |data| using Gzip compression and returns the result.
std::string Gzip(const std::string& data) {
std::string compressed;
EXPECT_TRUE(compression::GzipCompress(data, &compressed));
return compressed;
}
// Gzips |data| and then base64-encodes it.
std::string GzipAndBase64Encode(const std::string& data) {
return base::Base64Encode(Gzip(data));
}
// Serializes |seed| to gzipped base64-encoded protobuf binary format.
std::string SerializeSeedBase64(const VariationsSeed& seed) {
return GzipAndBase64Encode(SerializeSeed(seed));
}
// Wrapper over base::Base64Decode() that returns the result.
std::string Base64DecodeData(const std::string& data) {
std::string decoded;
EXPECT_TRUE(base::Base64Decode(data, &decoded));
return decoded;
}
// Returns true if a local state seed should be used.
bool ShouldUseLocalStateSeed() {
return base::FieldTrialList::FindFullName(kSeedFileTrial) != kSeedFilesGroup;
}
// Loads the seed from the seed store and returns true if successful.
bool MakeSeedStoreLoadStoredSeed(TestVariationsSeedStore& seed_store) {
VariationsSeed seed;
std::string seed_data;
std::string seed_signature;
return seed_store.LoadSeed(&seed, &seed_data, &seed_signature);
}
// Sample seeds and the server produced delta between them to verify that the
// client code is able to decode the deltas produced by the server.
struct {
const std::string base64_initial_seed_data =
"CigxN2E4ZGJiOTI4ODI0ZGU3ZDU2MGUyODRlODY1ZDllYzg2NzU1MTE0ElgKDFVNQVN0YWJp"
"bGl0eRjEyomgBTgBQgtTZXBhcmF0ZUxvZ0oLCgdEZWZhdWx0EABKDwoLU2VwYXJhdGVMb2cQ"
"ZFIVEgszNC4wLjE4MDEuMCAAIAEgAiADEkQKIFVNQS1Vbmlmb3JtaXR5LVRyaWFsLTEwMC1Q"
"ZXJjZW50GIDjhcAFOAFCCGdyb3VwXzAxSgwKCGdyb3VwXzAxEAFgARJPCh9VTUEtVW5pZm9y"
"bWl0eS1UcmlhbC01MC1QZXJjZW50GIDjhcAFOAFCB2RlZmF1bHRKDAoIZ3JvdXBfMDEQAUoL"
"CgdkZWZhdWx0EAFgAQ==";
const std::string base64_new_seed_data =
"CigyNGQzYTM3ZTAxYmViOWYwNWYzMjM4YjUzNWY3MDg1ZmZlZWI4NzQwElgKDFVNQVN0YWJp"
"bGl0eRjEyomgBTgBQgtTZXBhcmF0ZUxvZ0oLCgdEZWZhdWx0EABKDwoLU2VwYXJhdGVMb2cQ"
"ZFIVEgszNC4wLjE4MDEuMCAAIAEgAiADEpIBCh9VTUEtVW5pZm9ybWl0eS1UcmlhbC0yMC1Q"
"ZXJjZW50GIDjhcAFOAFCB2RlZmF1bHRKEQoIZ3JvdXBfMDEQARijtskBShEKCGdyb3VwXzAy"
"EAEYpLbJAUoRCghncm91cF8wMxABGKW2yQFKEQoIZ3JvdXBfMDQQARimtskBShAKB2RlZmF1"
"bHQQARiitskBYAESWAofVU1BLVVuaWZvcm1pdHktVHJpYWwtNTAtUGVyY2VudBiA44XABTgB"
"QgdkZWZhdWx0Sg8KC25vbl9kZWZhdWx0EAFKCwoHZGVmYXVsdBABUgQoACgBYAE=";
const std::string base64_delta_data =
"KgooMjRkM2EzN2UwMWJlYjlmMDVmMzIzOGI1MzVmNzA4NWZmZWViODc0MAAqW+4BkgEKH1VN"
"QS1Vbmlmb3JtaXR5LVRyaWFsLTIwLVBlcmNlbnQYgOOFwAU4AUIHZGVmYXVsdEoRCghncm91"
"cF8wMRABGKO2yQFKEQoIZ3JvdXBfMDIQARiktskBShEKCGdyb3VwXzAzEAEYpbbJAUoRCghn"
"cm91cF8wNBABGKa2yQFKEAoHZGVmYXVsdBABGKK2yQFgARJYCh9VTUEtVW5pZm9ybWl0eS1U"
"cmlhbC01MC1QZXJjZW50GIDjhcAFOAFCB2RlZmF1bHRKDwoLbm9uX2RlZmF1bHQQAUoLCgdk"
"ZWZhdWx0EAFSBCgAKAFgAQ==";
std::string GetInitialSeedData() {
return Base64DecodeData(base64_initial_seed_data);
}
std::string GetInitialSeedDataAsPrefValue() {
return GzipAndBase64Encode(GetInitialSeedData());
}
std::string GetNewSeedData() {
return Base64DecodeData(base64_new_seed_data);
}
std::string GetDeltaData() { return Base64DecodeData(base64_delta_data); }
} kSeedDeltaTestData;
// Sets all seed-related prefs to non-default values. Also, sets seed-file-based
// seeds to non-default values using |seed_store| for the seed file experiments
// treatment-group clients. Used to verify whether pref values were cleared.
void SetAllSeedsAndSeedPrefsToNonDefaultValues(
PrefService* prefs,
TestVariationsSeedStore& seed_store) {
const base::Time now = base::Time::Now();
const base::TimeDelta delta = base::Days(1);
// Update the latest seed in memory. This is done for the Local-State-based
// seed OR the seed-file-based seed depending on the seed file trial group to
// which the client belongs.
seed_store.GetSeedReaderWriterForTesting()->StoreValidatedSeed("coffee",
"coffee");
prefs->SetTime(prefs::kVariationsLastFetchTime, now);
prefs->SetTime(prefs::kVariationsSeedDate, now - delta * 1);
prefs->SetString(prefs::kVariationsSeedSignature, "tea");
// Update the safe seed in memory. This is done for the Local-State-based
// seed OR the seed-file-based seed depending on the seed file trial group to
// which the client belongs.
seed_store.GetSafeSeedReaderWriterForTesting()->StoreValidatedSeed("ketchup",
"ketchup");
prefs->SetTime(prefs::kVariationsSafeSeedDate, now - delta * 2);
prefs->SetTime(prefs::kVariationsSafeSeedFetchTime, now - delta * 3);
prefs->SetString(prefs::kVariationsSafeSeedLocale, "en-MX");
prefs->SetInteger(prefs::kVariationsSafeSeedMilestone, 90);
prefs->SetString(prefs::kVariationsSafeSeedPermanentConsistencyCountry, "mx");
prefs->SetString(prefs::kVariationsSafeSeedSessionConsistencyCountry, "gt");
prefs->SetString(prefs::kVariationsSafeSeedSignature, "mustard");
}
// Checks whether the given pref has its default value in |prefs|.
bool PrefHasDefaultValue(const TestingPrefServiceSimple& prefs,
const char* pref_name) {
return prefs.FindPreference(pref_name)->IsDefaultValue();
}
void CheckRegularSeedAndSeedPrefsAreSet(const TestingPrefServiceSimple& prefs,
TestVariationsSeedStore& seed_store) {
EXPECT_THAT(seed_store.GetSeedReaderWriterForTesting()->GetSeedData().data,
Not(IsEmpty()));
if (ShouldUseLocalStateSeed()) {
EXPECT_FALSE(PrefHasDefaultValue(prefs, prefs::kVariationsCompressedSeed));
}
EXPECT_FALSE(PrefHasDefaultValue(prefs, prefs::kVariationsLastFetchTime));
EXPECT_FALSE(PrefHasDefaultValue(prefs, prefs::kVariationsSeedDate));
EXPECT_FALSE(PrefHasDefaultValue(prefs, prefs::kVariationsSeedSignature));
}
void CheckRegularSeedAndSeedPrefsAreCleared(
const TestingPrefServiceSimple& prefs,
TestVariationsSeedStore& seed_store) {
EXPECT_THAT(seed_store.GetSeedReaderWriterForTesting()->GetSeedData().data,
IsEmpty());
if (ShouldUseLocalStateSeed()) {
EXPECT_TRUE(PrefHasDefaultValue(prefs, prefs::kVariationsCompressedSeed));
}
EXPECT_TRUE(PrefHasDefaultValue(prefs, prefs::kVariationsLastFetchTime));
EXPECT_TRUE(PrefHasDefaultValue(prefs, prefs::kVariationsSeedDate));
EXPECT_TRUE(PrefHasDefaultValue(prefs, prefs::kVariationsSeedSignature));
}
void CheckSafeSeedAndSeedPrefsAreSet(const TestingPrefServiceSimple& prefs,
TestVariationsSeedStore& seed_store) {
EXPECT_THAT(
seed_store.GetSafeSeedReaderWriterForTesting()->GetSeedData().data,
Not(IsEmpty()));
if (ShouldUseLocalStateSeed()) {
EXPECT_FALSE(
PrefHasDefaultValue(prefs, prefs::kVariationsSafeCompressedSeed));
}
EXPECT_FALSE(PrefHasDefaultValue(prefs, prefs::kVariationsSafeSeedDate));
EXPECT_FALSE(PrefHasDefaultValue(prefs, prefs::kVariationsSafeSeedFetchTime));
EXPECT_FALSE(PrefHasDefaultValue(prefs, prefs::kVariationsSafeSeedLocale));
EXPECT_FALSE(PrefHasDefaultValue(prefs, prefs::kVariationsSafeSeedMilestone));
EXPECT_FALSE(PrefHasDefaultValue(
prefs, prefs::kVariationsSafeSeedPermanentConsistencyCountry));
EXPECT_FALSE(PrefHasDefaultValue(
prefs, prefs::kVariationsSafeSeedSessionConsistencyCountry));
EXPECT_FALSE(PrefHasDefaultValue(prefs, prefs::kVariationsSafeSeedSignature));
}
void CheckSafeSeedAndSeedPrefsAreCleared(const TestingPrefServiceSimple& prefs,
TestVariationsSeedStore& seed_store) {
EXPECT_THAT(
seed_store.GetSafeSeedReaderWriterForTesting()->GetSeedData().data,
IsEmpty());
if (ShouldUseLocalStateSeed()) {
EXPECT_TRUE(
PrefHasDefaultValue(prefs, prefs::kVariationsSafeCompressedSeed));
}
EXPECT_TRUE(PrefHasDefaultValue(prefs, prefs::kVariationsSafeSeedDate));
EXPECT_TRUE(PrefHasDefaultValue(prefs, prefs::kVariationsSafeSeedFetchTime));
EXPECT_TRUE(PrefHasDefaultValue(prefs, prefs::kVariationsSafeSeedLocale));
EXPECT_TRUE(PrefHasDefaultValue(prefs, prefs::kVariationsSafeSeedMilestone));
EXPECT_TRUE(PrefHasDefaultValue(
prefs, prefs::kVariationsSafeSeedPermanentConsistencyCountry));
EXPECT_TRUE(PrefHasDefaultValue(
prefs, prefs::kVariationsSafeSeedSessionConsistencyCountry));
EXPECT_TRUE(PrefHasDefaultValue(prefs, prefs::kVariationsSafeSeedSignature));
}
} // namespace
class VariationsSeedStoreTest : public ::testing::Test {
private:
base::test::TaskEnvironment task_environment_;
};
class SeedStoreGroupTestBase : public ::testing::Test {
public:
explicit SeedStoreGroupTestBase(std::string_view seed_pref,
std::string_view field_trial_group)
: file_writer_thread_("SeedReaderWriter Test thread") {
scoped_feature_list_.InitWithEmptyFeatureAndFieldTrialLists();
file_writer_thread_.Start();
CHECK(temp_dir_.CreateUniqueTempDir());
temp_seed_file_path_ = temp_dir_.GetPath().Append(kSeedFilename);
VariationsSeedStore::RegisterPrefs(prefs_.registry());
SetUpSeedFileTrial(std::string(field_trial_group));
// Initialize |seed_reader_writer_|.
seed_reader_writer_ = std::make_unique<SeedReaderWriter>(
&prefs_, temp_dir_.GetPath(), kSeedFilename, seed_pref,
version_info::Channel::UNKNOWN,
std::make_unique<const MockEntropyProviders>(
MockEntropyProviders::Results{.low_entropy = kAlwaysUseLastGroup})
.get(),
file_writer_thread_.task_runner());
seed_reader_writer_->SetTimerForTesting(&timer_);
}
~SeedStoreGroupTestBase() override = default;
protected:
base::test::ScopedFeatureList scoped_feature_list_;
base::test::TaskEnvironment task_environment_;
base::Thread file_writer_thread_;
base::ScopedTempDir temp_dir_;
base::MockOneShotTimer timer_;
base::FilePath temp_seed_file_path_;
TestingPrefServiceSimple prefs_;
std::unique_ptr<SeedReaderWriter> seed_reader_writer_;
};
class LoadSeedDataGroupTest
: public SeedStoreGroupTestBase,
public ::testing::WithParamInterface<std::string_view> {
public:
explicit LoadSeedDataGroupTest()
: SeedStoreGroupTestBase(prefs::kVariationsCompressedSeed, GetParam()) {}
~LoadSeedDataGroupTest() override = default;
};
class LoadSeedDataAllGroupsTest : public LoadSeedDataGroupTest {
protected:
void SetUp() override {
ASSERT_TRUE(base::Base64Decode(kTestSeedData.base64_uncompressed_data,
&seed_data_));
}
// Stores the seed data to the given seed store.
// If |test_signature| is empty, the default test signature is used.
// If |seed_data| is nullptr, the test's default seed data is used.
void StoreValidatedSeed(
TestVariationsSeedStore& seed_store,
std::string_view test_signature = kTestSeedData.base64_signature,
const std::string* seed_data = nullptr) {
if (seed_data == nullptr) {
seed_data = &seed_data_;
}
ASSERT_TRUE(seed_data != nullptr);
VariationsSeed seed;
ASSERT_TRUE(seed.ParseFromString(*seed_data));
std::string compressed_seed_data = Gzip(SerializeSeed(seed));
std::string base64_seed_data = SerializeSeedBase64(seed);
seed_store.GetSeedReaderWriterForTesting()->StoreValidatedSeed(
compressed_seed_data, base64_seed_data);
prefs_.SetString(prefs::kVariationsSeedSignature, test_signature);
}
std::string seed_data_;
};
class LoadSeedDataControlAndDefaultGroupsTest : public LoadSeedDataGroupTest {};
INSTANTIATE_TEST_SUITE_P(
All,
LoadSeedDataAllGroupsTest,
::testing::Values(kSeedFilesGroup, kControlGroup, kDefaultGroup, kNoGroup));
TEST_P(LoadSeedDataAllGroupsTest, LoadSeed_ValidSeed) {
// Store good seed data to test if loading from prefs works.
const std::string seed_data = SerializeSeed(CreateTestSeed());
const std::string base64_seed = GzipAndBase64Encode(seed_data);
const std::string compressed_seed = Gzip(seed_data);
const std::string base64_seed_signature = "a test signature, ignored.";
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath());
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial), GetParam());
seed_store.GetSeedReaderWriterForTesting()->StoreValidatedSeed(
compressed_seed, base64_seed);
prefs_.SetString(prefs::kVariationsSeedSignature, base64_seed_signature);
const std::string expected_seed =
GetParam() == kSeedFilesGroup ? compressed_seed : base64_seed;
base::HistogramTester histogram_tester;
VariationsSeed loaded_seed;
std::string loaded_seed_data;
std::string loaded_base64_seed_signature;
// Check that loading a seed works correctly.
ASSERT_TRUE(seed_store.LoadSeed(&loaded_seed, &loaded_seed_data,
&loaded_base64_seed_signature));
// Verify metrics.
histogram_tester.ExpectUniqueSample("Variations.SeedLoadResult",
LoadSeedResult::kSuccess, 1);
// Check that the loaded data is the same as the original.
EXPECT_EQ(seed_data, SerializeSeed(loaded_seed));
EXPECT_EQ(seed_data, loaded_seed_data);
EXPECT_EQ(base64_seed_signature, loaded_base64_seed_signature);
// Make sure the seed data hasn't been changed.
if (ShouldUseLocalStateSeed()) {
EXPECT_EQ(base64_seed, prefs_.GetString(prefs::kVariationsCompressedSeed));
}
EXPECT_EQ(expected_seed,
seed_store.GetSeedReaderWriterForTesting()->GetSeedData().data);
}
TEST_P(LoadSeedDataAllGroupsTest, LoadSeed_InvalidSignature) {
const std::string seed_data = SerializeSeed(CreateTestSeed());
// Loading a valid seed with an invalid signature should return false and
// clear seeds and associated prefs when signature verification is enabled.
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath(),
/*signature_verification_needed=*/true);
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial), GetParam());
SetAllSeedsAndSeedPrefsToNonDefaultValues(&prefs_, seed_store);
seed_store.GetSeedReaderWriterForTesting()->StoreValidatedSeed(
Gzip(seed_data), GzipAndBase64Encode(seed_data));
prefs_.SetString(prefs::kVariationsSeedSignature,
"a deeply compromised signature.");
base::HistogramTester histogram_tester;
VariationsSeed loaded_seed;
std::string loaded_seed_data;
std::string loaded_base64_seed_signature;
ASSERT_FALSE(seed_store.LoadSeed(&loaded_seed, &loaded_seed_data,
&loaded_base64_seed_signature));
// Verify metrics and prefs.
histogram_tester.ExpectUniqueSample("Variations.SeedLoadResult",
LoadSeedResult::kInvalidSignature, 1);
CheckRegularSeedAndSeedPrefsAreCleared(prefs_, seed_store);
CheckSafeSeedAndSeedPrefsAreSet(prefs_, seed_store);
}
TEST_P(LoadSeedDataAllGroupsTest, LoadSeed_InvalidProto) {
// Loading a valid seed with an invalid signature should return false and
// clear seeds and associated prefs when signature verification is enabled.
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath());
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial), GetParam());
SetAllSeedsAndSeedPrefsToNonDefaultValues(&prefs_, seed_store);
seed_store.GetSeedReaderWriterForTesting()->StoreValidatedSeed(
Gzip("Not a proto"), GzipAndBase64Encode("Not a proto"));
base::HistogramTester histogram_tester;
VariationsSeed loaded_seed;
std::string loaded_seed_data;
std::string loaded_base64_seed_signature;
ASSERT_FALSE(seed_store.LoadSeed(&loaded_seed, &loaded_seed_data,
&loaded_base64_seed_signature));
// Verify metrics and prefs.
histogram_tester.ExpectUniqueSample("Variations.SeedLoadResult",
LoadSeedResult::kCorruptProtobuf, 1);
CheckRegularSeedAndSeedPrefsAreCleared(prefs_, seed_store);
CheckSafeSeedAndSeedPrefsAreSet(prefs_, seed_store);
}
TEST_P(LoadSeedDataAllGroupsTest, LoadSeed_RejectEmptySignature) {
const std::string seed_data = SerializeSeed(CreateTestSeed());
// Loading a valid seed with an empty signature should fail and clear seeds
// and associated prefs when signature verification is enabled.
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath(),
/*signature_verification_needed=*/true);
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial), GetParam());
SetAllSeedsAndSeedPrefsToNonDefaultValues(&prefs_, seed_store);
seed_store.GetSeedReaderWriterForTesting()->StoreValidatedSeed(
Gzip(seed_data), GzipAndBase64Encode(seed_data));
prefs_.SetString(prefs::kVariationsSeedSignature, "");
base::HistogramTester histogram_tester;
VariationsSeed loaded_seed;
std::string loaded_seed_data;
std::string loaded_base64_seed_signature;
ASSERT_FALSE(seed_store.LoadSeed(&loaded_seed, &loaded_seed_data,
&loaded_base64_seed_signature));
// Verify metrics and prefs.
histogram_tester.ExpectUniqueSample("Variations.SeedLoadResult",
LoadSeedResult::kInvalidSignature, 1);
CheckRegularSeedAndSeedPrefsAreCleared(prefs_, seed_store);
CheckSafeSeedAndSeedPrefsAreSet(prefs_, seed_store);
}
TEST_P(LoadSeedDataAllGroupsTest, LoadSeed_AcceptEmptySignature) {
const std::string seed_data = SerializeSeed(CreateTestSeed());
// Loading a valid seed with an empty signature should succeed iff
// switches::kAcceptEmptySeedSignatureForTesting is on the command line.
base::test::ScopedCommandLine scoped_command_line;
scoped_command_line.GetProcessCommandLine()->AppendSwitch(
switches::kAcceptEmptySeedSignatureForTesting);
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath(),
/*signature_verification_needed=*/true);
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial), GetParam());
SetAllSeedsAndSeedPrefsToNonDefaultValues(&prefs_, seed_store);
seed_store.GetSeedReaderWriterForTesting()->StoreValidatedSeed(
Gzip(seed_data), GzipAndBase64Encode(seed_data));
prefs_.SetString(prefs::kVariationsSeedSignature, "");
base::HistogramTester histogram_tester;
VariationsSeed loaded_seed;
std::string loaded_seed_data;
std::string loaded_base64_seed_signature;
ASSERT_TRUE(seed_store.LoadSeed(&loaded_seed, &loaded_seed_data,
&loaded_base64_seed_signature));
// Verify metrics and prefs.
histogram_tester.ExpectUniqueSample("Variations.SeedLoadResult",
LoadSeedResult::kSuccess, 1);
CheckRegularSeedAndSeedPrefsAreSet(prefs_, seed_store);
CheckSafeSeedAndSeedPrefsAreSet(prefs_, seed_store);
}
TEST_P(LoadSeedDataAllGroupsTest, LoadSeed_EmptySeed) {
// Loading an empty seed should return false.
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath());
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial), GetParam());
ASSERT_THAT(seed_store.GetSeedReaderWriterForTesting()->GetSeedData().data,
IsEmpty());
base::HistogramTester histogram_tester;
VariationsSeed loaded_seed;
std::string loaded_seed_data;
std::string loaded_base64_seed_signature;
ASSERT_FALSE(seed_store.LoadSeed(&loaded_seed, &loaded_seed_data,
&loaded_base64_seed_signature));
// Verify metrics.
histogram_tester.ExpectUniqueSample("Variations.SeedLoadResult",
LoadSeedResult::kEmpty, 1);
}
TEST_P(LoadSeedDataAllGroupsTest, LoadSeed_IdenticalToSafeSeed) {
// Store good seed data for safe seed, and store a sentinel value for the
// latest seed, to verify that loading via the alias works.
const std::string seed_data = SerializeSeed(CreateTestSeed());
const std::string base64_seed_signature = "a test signature, ignored.";
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath());
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial), GetParam());
seed_store.GetSeedReaderWriterForTesting()->StoreValidatedSeed(
kIdenticalToSafeSeedSentinel, kIdenticalToSafeSeedSentinel);
seed_store.GetSafeSeedReaderWriterForTesting()->StoreValidatedSeed(
Gzip(seed_data), GzipAndBase64Encode(seed_data));
prefs_.SetString(prefs::kVariationsSeedSignature, base64_seed_signature);
base::HistogramTester histogram_tester;
VariationsSeed loaded_seed;
std::string loaded_seed_data;
std::string loaded_base64_seed_signature;
// Check that loading the seed works correctly.
ASSERT_TRUE(seed_store.LoadSeed(&loaded_seed, &loaded_seed_data,
&loaded_base64_seed_signature));
// Verify metrics.
histogram_tester.ExpectUniqueSample("Variations.SeedLoadResult",
LoadSeedResult::kSuccess, 1);
// Check that the loaded data is the same as the original.
EXPECT_EQ(seed_data, SerializeSeed(loaded_seed));
EXPECT_EQ(seed_data, loaded_seed_data);
EXPECT_EQ(base64_seed_signature, loaded_base64_seed_signature);
}
TEST_P(LoadSeedDataAllGroupsTest, LoadSeed_CorruptGzip) {
// Loading a corrupted compressed seed should return false.
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath());
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial), GetParam());
SetAllSeedsAndSeedPrefsToNonDefaultValues(&prefs_, seed_store);
std::string compressed_seed = Gzip("seed data");
// Flip some bits to corrupt the data
compressed_seed[5] ^= 0xFF;
compressed_seed[10] ^= 0xFF;
seed_store.GetSeedReaderWriterForTesting()->StoreValidatedSeed(
compressed_seed, base::Base64Encode(compressed_seed));
base::HistogramTester histogram_tester;
VariationsSeed loaded_seed;
std::string loaded_seed_data;
std::string loaded_base64_seed_signature;
ASSERT_FALSE(seed_store.LoadSeed(&loaded_seed, &loaded_seed_data,
&loaded_base64_seed_signature));
// Verify metrics and prefs.
histogram_tester.ExpectUniqueSample("Variations.SeedLoadResult",
LoadSeedResult::kCorruptGzip, 1);
CheckRegularSeedAndSeedPrefsAreCleared(prefs_, seed_store);
CheckSafeSeedAndSeedPrefsAreSet(prefs_, seed_store);
}
TEST_P(LoadSeedDataAllGroupsTest, LoadSeed_ExceedsUncompressedSizeLimit) {
// Loading a seed that exceeds the uncompressed size should return false.
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath());
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial), GetParam());
SetAllSeedsAndSeedPrefsToNonDefaultValues(&prefs_, seed_store);
// 51MiB of uncompressed data to exceed 50MiB limit.
const std::string compressed_seed = Gzip(std::string(51 * 1024 * 1024, 'A'));
seed_store.GetSeedReaderWriterForTesting()->StoreValidatedSeed(
compressed_seed, base::Base64Encode(compressed_seed));
base::HistogramTester histogram_tester;
VariationsSeed loaded_seed;
std::string loaded_seed_data;
std::string loaded_base64_seed_signature;
ASSERT_FALSE(seed_store.LoadSeed(&loaded_seed, &loaded_seed_data,
&loaded_base64_seed_signature));
// Verify metrics and prefs.
histogram_tester.ExpectUniqueSample(
"Variations.SeedLoadResult",
LoadSeedResult::kExceedsUncompressedSizeLimit, 1);
CheckRegularSeedAndSeedPrefsAreCleared(prefs_, seed_store);
CheckSafeSeedAndSeedPrefsAreSet(prefs_, seed_store);
}
INSTANTIATE_TEST_SUITE_P(All,
LoadSeedDataControlAndDefaultGroupsTest,
::testing::Values(kControlGroup,
kDefaultGroup,
kNoGroup));
// Coverage for base64 decoding issues is N/A to treatment-group clients because
// they don't use base64 encoding.
TEST_P(LoadSeedDataControlAndDefaultGroupsTest,
LoadSeed_Base64DecodingFailure) {
// Loading a non-base64-encoded seed should return false.
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath());
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial), GetParam());
SetAllSeedsAndSeedPrefsToNonDefaultValues(&prefs_, seed_store);
seed_store.GetSeedReaderWriterForTesting()->StoreValidatedSeed(
"invalid seed data", "invalid seed data");
base::HistogramTester histogram_tester;
VariationsSeed loaded_seed;
std::string loaded_seed_data;
std::string loaded_base64_seed_signature;
ASSERT_FALSE(seed_store.LoadSeed(&loaded_seed, &loaded_seed_data,
&loaded_base64_seed_signature));
// Verify metrics and prefs.
histogram_tester.ExpectUniqueSample("Variations.SeedLoadResult",
LoadSeedResult::kCorruptBase64, 1);
CheckRegularSeedAndSeedPrefsAreCleared(prefs_, seed_store);
CheckSafeSeedAndSeedPrefsAreSet(prefs_, seed_store);
}
TEST_F(VariationsSeedStoreTest, ApplyDeltaPatch) {
std::string output;
ASSERT_TRUE(VariationsSeedStore::ApplyDeltaPatch(
kSeedDeltaTestData.GetInitialSeedData(),
kSeedDeltaTestData.GetDeltaData(), &output));
EXPECT_EQ(kSeedDeltaTestData.GetNewSeedData(), output);
}
struct StoreSeedDataTestParams {
using TupleT = std::tuple<bool, std::string_view>;
const bool require_synchronous_stores;
std::string_view field_trial_group;
explicit StoreSeedDataTestParams(const TupleT& t)
: require_synchronous_stores(std::get<0>(t)),
field_trial_group(std::get<1>(t)) {}
};
class StoreSeedDataGroupTest
: public SeedStoreGroupTestBase,
public ::testing::WithParamInterface<StoreSeedDataTestParams> {
public:
explicit StoreSeedDataGroupTest()
: SeedStoreGroupTestBase(prefs::kVariationsCompressedSeed,
GetParam().field_trial_group) {}
~StoreSeedDataGroupTest() override = default;
bool RequireSynchronousStores() const {
return GetParam().require_synchronous_stores;
}
struct Params {
std::string country_code;
bool is_delta_compressed;
bool is_gzip_compressed;
};
// Wrapper for VariationsSeedStore::StoreSeedData() exposing a more convenient
// API. Invokes either the underlying function either in sync or async mode,
// but if async, it blocks on its completion.
bool StoreSeedData(VariationsSeedStore& seed_store,
const std::string& seed_data,
const Params& params = {}) {
base::RunLoop run_loop;
seed_store.StoreSeedData(
seed_data, /*base64_seed_signature=*/std::string(), params.country_code,
base::Time::Now(), params.is_delta_compressed,
params.is_gzip_compressed,
base::BindOnce(&StoreSeedDataGroupTest::OnSeedStoreResult,
base::Unretained(this), run_loop.QuitClosure()),
RequireSynchronousStores());
// If we're testing synchronous stores, we shouldn't issue a Run() call so
// that the test verifies that the operation completed synchronously.
if (!RequireSynchronousStores()) {
run_loop.Run();
}
return store_success_;
}
protected:
bool store_success_ = false;
VariationsSeed stored_seed_;
private:
void OnSeedStoreResult(base::RepeatingClosure quit_closure,
bool store_success,
VariationsSeed seed) {
store_success_ = store_success;
stored_seed_.Swap(&seed);
quit_closure.Run();
}
};
class StoreSeedDataSeedFilesGroupTest : public StoreSeedDataGroupTest {};
class StoreSeedDataControlAndLocalStateOnlyGroupTest
: public StoreSeedDataGroupTest {};
class StoreSeedDataAllGroupsTest : public StoreSeedDataGroupTest {};
INSTANTIATE_TEST_SUITE_P(
All,
StoreSeedDataSeedFilesGroupTest,
::testing::ConvertGenerator<StoreSeedDataTestParams::TupleT>(
::testing::Combine(::testing::Bool(),
::testing::Values(kSeedFilesGroup))));
// Verifies that clients in SeedFiles trial group write latest seeds to a seed
// file.
TEST_P(StoreSeedDataSeedFilesGroupTest, StoreSeedData) {
// Initialize SeedStore with test local state prefs and SeedReaderWriter.
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath());
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial),
GetParam().field_trial_group);
seed_store.SetSeedReaderWriterForTesting(std::move(seed_reader_writer_));
// Store seed and force write for SeedReaderWriter.
const std::string serialized_seed = SerializeSeed(CreateTestSeed());
ASSERT_TRUE(StoreSeedData(seed_store, serialized_seed));
timer_.Fire();
file_writer_thread_.FlushForTesting();
// Make sure seed in seed file matches the one created.
std::string seed_file_data;
ASSERT_TRUE(base::ReadFileToString(temp_seed_file_path_, &seed_file_data));
EXPECT_EQ(seed_file_data, Gzip(serialized_seed));
}
INSTANTIATE_TEST_SUITE_P(
All,
StoreSeedDataControlAndLocalStateOnlyGroupTest,
::testing::ConvertGenerator<StoreSeedDataTestParams::TupleT>(
::testing::Combine(
::testing::Bool(),
::testing::Values(kControlGroup, kDefaultGroup, kNoGroup))));
// Verifies that clients in the control group and those using local state only
// write latest seeds only to local state prefs.
TEST_P(StoreSeedDataControlAndLocalStateOnlyGroupTest, StoreSeedData) {
// Initialize SeedStore with test local state prefs and SeedReaderWriter.
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath());
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial),
GetParam().field_trial_group);
seed_store.SetSeedReaderWriterForTesting(std::move(seed_reader_writer_));
const std::string serialized_seed = SerializeSeed(CreateTestSeed());
ASSERT_TRUE(StoreSeedData(seed_store, serialized_seed));
// Make sure seed in local state prefs matches the one created.
EXPECT_EQ(prefs_.GetString(prefs::kVariationsCompressedSeed),
GzipAndBase64Encode(serialized_seed));
// Check there's no pending write to a seed file and that it was not created.
EXPECT_FALSE(timer_.IsRunning());
EXPECT_FALSE(base::PathExists(temp_seed_file_path_));
}
INSTANTIATE_TEST_SUITE_P(
All,
StoreSeedDataAllGroupsTest,
::testing::ConvertGenerator<StoreSeedDataTestParams::TupleT>(
::testing::Combine(::testing::Bool(),
::testing::Values(kSeedFilesGroup,
kControlGroup,
kDefaultGroup,
kNoGroup))));
// Verifies that invalid latest seeds are not stored.
TEST_P(StoreSeedDataAllGroupsTest, StoreSeedData_InvalidSeed) {
// Initialize SeedStore with test prefs and SeedReaderWriter.
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath());
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial),
GetParam().field_trial_group);
seed_store.SetSeedReaderWriterForTesting(std::move(seed_reader_writer_));
// Check if trying to store a bad seed leaves the local state prefs unchanged.
ASSERT_FALSE(StoreSeedData(seed_store, "should fail"));
EXPECT_TRUE(PrefHasDefaultValue(prefs_, prefs::kVariationsCompressedSeed));
// Check there's no pending write to a seed file and that it was not created.
EXPECT_FALSE(timer_.IsRunning());
EXPECT_FALSE(base::PathExists(temp_seed_file_path_));
}
TEST_P(StoreSeedDataAllGroupsTest, ParsedSeed) {
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath());
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial),
GetParam().field_trial_group);
const std::string serialized_seed = SerializeSeed(CreateTestSeed());
ASSERT_TRUE(StoreSeedData(seed_store, serialized_seed));
EXPECT_EQ(serialized_seed, SerializeSeed(stored_seed_));
}
TEST_P(StoreSeedDataAllGroupsTest, CountryCode) {
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath());
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial),
GetParam().field_trial_group);
// Test with a valid header value.
std::string seed = SerializeSeed(CreateTestSeed());
ASSERT_TRUE(
StoreSeedData(seed_store, seed, {.country_code = "test_country"}));
EXPECT_EQ("test_country", prefs_.GetString(prefs::kVariationsCountry));
// Test with no country code specified - which should preserve the old value.
ASSERT_TRUE(StoreSeedData(seed_store, seed));
EXPECT_EQ("test_country", prefs_.GetString(prefs::kVariationsCountry));
}
TEST_P(StoreSeedDataAllGroupsTest, GzippedSeed) {
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath());
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial),
GetParam().field_trial_group);
const std::string serialized_seed = SerializeSeed(CreateTestSeed());
ASSERT_TRUE(StoreSeedData(seed_store, Gzip(serialized_seed),
{.is_gzip_compressed = true}));
EXPECT_EQ(serialized_seed, SerializeSeed(stored_seed_));
}
TEST_P(StoreSeedDataAllGroupsTest, GzippedEmptySeed) {
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath());
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial),
GetParam().field_trial_group);
store_success_ = true;
EXPECT_FALSE(StoreSeedData(seed_store, Gzip(/*data=*/std::string()),
{.is_gzip_compressed = true}));
}
TEST_P(StoreSeedDataAllGroupsTest, DeltaCompressed) {
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath());
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial),
GetParam().field_trial_group);
seed_store.GetSeedReaderWriterForTesting()->StoreValidatedSeed(
Gzip(kSeedDeltaTestData.GetInitialSeedData()),
kSeedDeltaTestData.GetInitialSeedDataAsPrefValue());
prefs_.SetString(prefs::kVariationsSeedSignature, "ignored signature");
ASSERT_TRUE(StoreSeedData(seed_store, kSeedDeltaTestData.GetDeltaData(),
{.is_delta_compressed = true}));
EXPECT_EQ(kSeedDeltaTestData.GetNewSeedData(), SerializeSeed(stored_seed_));
}
TEST_P(StoreSeedDataAllGroupsTest, DeltaCompressedGzipped) {
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath());
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial),
GetParam().field_trial_group);
seed_store.GetSeedReaderWriterForTesting()->StoreValidatedSeed(
Gzip(kSeedDeltaTestData.GetInitialSeedData()),
kSeedDeltaTestData.GetInitialSeedDataAsPrefValue());
prefs_.SetString(prefs::kVariationsSeedSignature, "ignored signature");
ASSERT_TRUE(StoreSeedData(seed_store, Gzip(kSeedDeltaTestData.GetDeltaData()),
{
.is_delta_compressed = true,
.is_gzip_compressed = true,
}));
EXPECT_EQ(kSeedDeltaTestData.GetNewSeedData(), SerializeSeed(stored_seed_));
}
TEST_P(StoreSeedDataAllGroupsTest, DeltaButNoInitialSeed) {
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath());
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial),
GetParam().field_trial_group);
store_success_ = true;
EXPECT_FALSE(StoreSeedData(seed_store,
Gzip(kSeedDeltaTestData.GetDeltaData()),
{
.is_delta_compressed = true,
.is_gzip_compressed = true,
}));
}
TEST_P(StoreSeedDataAllGroupsTest, BadDelta) {
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath());
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial),
GetParam().field_trial_group);
seed_store.GetSeedReaderWriterForTesting()->StoreValidatedSeed(
Gzip(kSeedDeltaTestData.GetInitialSeedData()),
kSeedDeltaTestData.GetInitialSeedDataAsPrefValue());
prefs_.SetString(prefs::kVariationsSeedSignature, "ignored signature");
store_success_ = true;
// Provide a gzipped delta, when gzip is not expected.
EXPECT_FALSE(StoreSeedData(seed_store,
Gzip(kSeedDeltaTestData.GetDeltaData()),
{.is_delta_compressed = true}));
}
TEST_P(StoreSeedDataAllGroupsTest, IdenticalToSafeSeed) {
const std::string serialized_seed = SerializeSeed(CreateTestSeed());
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath());
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial),
GetParam().field_trial_group);
seed_store.GetSafeSeedReaderWriterForTesting()->StoreValidatedSeed(
Gzip(serialized_seed), GzipAndBase64Encode(serialized_seed));
ASSERT_TRUE(StoreSeedData(seed_store, serialized_seed));
// Verify that the pref has a sentinel value, rather than the full string.
if (ShouldUseLocalStateSeed()) {
EXPECT_EQ(kIdenticalToSafeSeedSentinel,
prefs_.GetString(prefs::kVariationsCompressedSeed));
}
EXPECT_EQ(kIdenticalToSafeSeedSentinel,
seed_store.GetSeedReaderWriterForTesting()->GetSeedData().data);
// Verify that loading the stored seed returns the original seed value.
VariationsSeed loaded_seed;
std::string loaded_seed_data;
std::string unused_loaded_base64_seed_signature;
ASSERT_TRUE(seed_store.LoadSeed(&loaded_seed, &loaded_seed_data,
&unused_loaded_base64_seed_signature));
EXPECT_EQ(serialized_seed, SerializeSeed(loaded_seed));
EXPECT_EQ(serialized_seed, loaded_seed_data);
}
// Verifies that the cached serial number is correctly updated when a new seed
// is saved.
TEST_P(StoreSeedDataAllGroupsTest,
GetLatestSerialNumber_UpdatedWithNewStoredSeed) {
// Store good seed data initially.
const std::string seed_data = SerializeSeed(CreateTestSeed());
// Call GetLatestSerialNumber() once to prime the cached value.
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath());
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial),
GetParam().field_trial_group);
seed_store.GetSeedReaderWriterForTesting()->StoreValidatedSeed(
Gzip(seed_data), GzipAndBase64Encode(seed_data));
prefs_.SetString(prefs::kVariationsSeedSignature,
"a completely ignored signature");
EXPECT_EQ("123", seed_store.GetLatestSerialNumber());
VariationsSeed new_seed = CreateTestSeed();
new_seed.set_serial_number("456");
ASSERT_TRUE(StoreSeedData(seed_store, SerializeSeed(new_seed)));
EXPECT_EQ("456", seed_store.GetLatestSerialNumber());
}
class LoadSafeSeedDataGroupTest
: public SeedStoreGroupTestBase,
public ::testing::WithParamInterface<std::string_view> {
public:
explicit LoadSafeSeedDataGroupTest()
: SeedStoreGroupTestBase(prefs::kVariationsSafeCompressedSeed,
GetParam()) {}
~LoadSafeSeedDataGroupTest() override = default;
};
class LoadSafeSeedDataAllGroupsTest : public LoadSafeSeedDataGroupTest {};
class LoadSafeSeedDataControlAndDefaultGroupsTest
: public LoadSafeSeedDataGroupTest {};
INSTANTIATE_TEST_SUITE_P(
All,
LoadSafeSeedDataAllGroupsTest,
::testing::Values(kSeedFilesGroup, kControlGroup, kDefaultGroup, kNoGroup));
TEST_P(LoadSafeSeedDataAllGroupsTest, LoadSafeSeed_ValidSeed) {
// Store good seed data to test if loading from prefs works.
const std::string serialized_seed = SerializeSeed(CreateTestSeed());
const std::string base64_seed = GzipAndBase64Encode(serialized_seed);
const std::string compressed_seed = Gzip(serialized_seed);
const base::Time reference_date = base::Time::Now();
const std::string locale = "en-US";
const std::string permanent_consistency_country = "us";
const std::string session_consistency_country = "ca";
// Attempt to load a valid safe seed.
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath());
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial), GetParam());
seed_store.GetSafeSeedReaderWriterForTesting()->StoreValidatedSeed(
compressed_seed, base64_seed);
prefs_.SetString(prefs::kVariationsSafeSeedSignature,
"a test signature, ignored.");
prefs_.SetTime(prefs::kVariationsSafeSeedDate, reference_date);
prefs_.SetTime(prefs::kVariationsSafeSeedFetchTime,
reference_date - base::Days(3));
prefs_.SetString(prefs::kVariationsSafeSeedLocale, locale);
prefs_.SetString(prefs::kVariationsSafeSeedPermanentConsistencyCountry,
permanent_consistency_country);
prefs_.SetString(prefs::kVariationsSafeSeedSessionConsistencyCountry,
session_consistency_country);
const std::string expected_seed =
GetParam() == kSeedFilesGroup ? compressed_seed : base64_seed;
base::HistogramTester histogram_tester;
VariationsSeed loaded_seed;
std::unique_ptr<ClientFilterableState> client_state =
CreateTestClientFilterableState();
ASSERT_TRUE(seed_store.LoadSafeSeed(&loaded_seed, client_state.get()));
// Verify metrics.
histogram_tester.ExpectUniqueSample("Variations.SafeMode.LoadSafeSeed.Result",
LoadSeedResult::kSuccess, 1);
// Check that the loaded data is the same as the original.
EXPECT_EQ(serialized_seed, SerializeSeed(loaded_seed));
EXPECT_EQ(locale, client_state->locale);
EXPECT_EQ(reference_date, client_state->reference_date);
EXPECT_EQ(permanent_consistency_country,
client_state->permanent_consistency_country);
EXPECT_EQ(session_consistency_country,
client_state->session_consistency_country);
// Make sure that other data in the |client_state| hasn't been changed.
std::unique_ptr<ClientFilterableState> original_state =
CreateTestClientFilterableState();
EXPECT_EQ(original_state->version, client_state->version);
EXPECT_EQ(original_state->channel, client_state->channel);
EXPECT_EQ(original_state->form_factor, client_state->form_factor);
EXPECT_EQ(original_state->platform, client_state->platform);
EXPECT_EQ(original_state->hardware_class, client_state->hardware_class);
EXPECT_EQ(original_state->is_low_end_device, client_state->is_low_end_device);
// Make sure the seed hasn't been changed.
if (ShouldUseLocalStateSeed()) {
EXPECT_EQ(base64_seed,
prefs_.GetString(prefs::kVariationsSafeCompressedSeed));
}
EXPECT_EQ(expected_seed,
seed_store.GetSafeSeedReaderWriterForTesting()->GetSeedData().data);
}
TEST_P(LoadSafeSeedDataAllGroupsTest, LoadSafeSeed_InvalidSignature) {
const std::string seed_data = SerializeSeed(CreateTestSeed());
// Attempt to load a valid safe seed with an invalid signature while signature
// verification is enabled.
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath(),
/*signature_verification_needed=*/true);
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial), GetParam());
SetAllSeedsAndSeedPrefsToNonDefaultValues(&prefs_, seed_store);
seed_store.GetSafeSeedReaderWriterForTesting()->StoreValidatedSeed(
Gzip(seed_data), GzipAndBase64Encode((seed_data)));
prefs_.SetString(prefs::kVariationsSafeSeedSignature,
"a deeply compromised signature.");
base::HistogramTester histogram_tester;
VariationsSeed loaded_seed;
std::unique_ptr<ClientFilterableState> client_state =
CreateTestClientFilterableState();
ASSERT_FALSE(seed_store.LoadSafeSeed(&loaded_seed, client_state.get()));
// Verify metrics and prefs.
histogram_tester.ExpectUniqueSample("Variations.SafeMode.LoadSafeSeed.Result",
LoadSeedResult::kInvalidSignature, 1);
CheckSafeSeedAndSeedPrefsAreCleared(prefs_, seed_store);
CheckRegularSeedAndSeedPrefsAreSet(prefs_, seed_store);
// Moreover, the passed-in |client_state| should remain unmodified.
std::unique_ptr<ClientFilterableState> original_state =
CreateTestClientFilterableState();
EXPECT_EQ(original_state->locale, client_state->locale);
EXPECT_EQ(original_state->reference_date, client_state->reference_date);
EXPECT_EQ(original_state->session_consistency_country,
client_state->session_consistency_country);
EXPECT_EQ(original_state->permanent_consistency_country,
client_state->permanent_consistency_country);
}
TEST_P(LoadSafeSeedDataAllGroupsTest, LoadSafeSeed_EmptySeed) {
// Attempt to load an empty safe seed.
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath());
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial), GetParam());
ASSERT_TRUE(
PrefHasDefaultValue(prefs_, prefs::kVariationsSafeCompressedSeed));
ASSERT_THAT(
seed_store.GetSafeSeedReaderWriterForTesting()->GetSeedData().data,
IsEmpty());
base::HistogramTester histogram_tester;
VariationsSeed loaded_seed;
std::unique_ptr<ClientFilterableState> client_state =
CreateTestClientFilterableState();
ASSERT_FALSE(seed_store.LoadSafeSeed(&loaded_seed, client_state.get()));
// Verify metrics.
histogram_tester.ExpectUniqueSample("Variations.SafeMode.LoadSafeSeed.Result",
LoadSeedResult::kEmpty, 1);
}
TEST_P(LoadSafeSeedDataAllGroupsTest, LoadSafeSeed_CorruptGzip) {
// Loading a corrupted compressed safe seed should return false.
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath());
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial), GetParam());
SetAllSeedsAndSeedPrefsToNonDefaultValues(&prefs_, seed_store);
std::string compressed_seed = Gzip("seed data");
// Flip some bits to corrupt the data
compressed_seed[5] ^= 0xFF;
compressed_seed[10] ^= 0xFF;
seed_store.GetSafeSeedReaderWriterForTesting()->StoreValidatedSeed(
compressed_seed, base::Base64Encode(compressed_seed));
base::HistogramTester histogram_tester;
VariationsSeed loaded_seed;
std::unique_ptr<ClientFilterableState> client_state =
CreateTestClientFilterableState();
ASSERT_FALSE(seed_store.LoadSafeSeed(&loaded_seed, client_state.get()));
// Verify metrics and prefs.
histogram_tester.ExpectUniqueSample("Variations.SafeMode.LoadSafeSeed.Result",
LoadSeedResult::kCorruptGzip, 1);
CheckSafeSeedAndSeedPrefsAreCleared(prefs_, seed_store);
CheckRegularSeedAndSeedPrefsAreSet(prefs_, seed_store);
// Moreover, loading an invalid seed should leave the |client_state|
// unmodified.
std::unique_ptr<ClientFilterableState> original_state =
CreateTestClientFilterableState();
EXPECT_EQ(original_state->locale, client_state->locale);
EXPECT_EQ(original_state->reference_date, client_state->reference_date);
EXPECT_EQ(original_state->session_consistency_country,
client_state->session_consistency_country);
EXPECT_EQ(original_state->permanent_consistency_country,
client_state->permanent_consistency_country);
}
TEST_P(LoadSafeSeedDataAllGroupsTest,
LoadSafeSeed_ExceedsUncompressedSizeLimit) {
// Loading a safe seed that exceeds the uncompressed size should return false.
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath());
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial), GetParam());
SetAllSeedsAndSeedPrefsToNonDefaultValues(&prefs_, seed_store);
// 51MiB of uncompressed data to exceed 50MiB limit.
const std::string compressed_seed = Gzip(std::string(51 * 1024 * 1024, 'A'));
seed_store.GetSafeSeedReaderWriterForTesting()->StoreValidatedSeed(
compressed_seed, base::Base64Encode(compressed_seed));
base::HistogramTester histogram_tester;
VariationsSeed loaded_seed;
std::unique_ptr<ClientFilterableState> client_state =
CreateTestClientFilterableState();
ASSERT_FALSE(seed_store.LoadSafeSeed(&loaded_seed, client_state.get()));
// Verify metrics and prefs.
histogram_tester.ExpectUniqueSample(
"Variations.SafeMode.LoadSafeSeed.Result",
LoadSeedResult::kExceedsUncompressedSizeLimit, 1);
CheckSafeSeedAndSeedPrefsAreCleared(prefs_, seed_store);
CheckRegularSeedAndSeedPrefsAreSet(prefs_, seed_store);
// Moreover, loading an invalid seed should leave the |client_state|
// unmodified.
std::unique_ptr<ClientFilterableState> original_state =
CreateTestClientFilterableState();
EXPECT_EQ(original_state->locale, client_state->locale);
EXPECT_EQ(original_state->reference_date, client_state->reference_date);
EXPECT_EQ(original_state->session_consistency_country,
client_state->session_consistency_country);
EXPECT_EQ(original_state->permanent_consistency_country,
client_state->permanent_consistency_country);
}
INSTANTIATE_TEST_SUITE_P(All,
LoadSafeSeedDataControlAndDefaultGroupsTest,
::testing::Values(kControlGroup,
kDefaultGroup,
kNoGroup));
// Coverage for base64 decoding issues is N/A to treatment-group clients because
// they don't use base64 encoding.
TEST_P(LoadSafeSeedDataControlAndDefaultGroupsTest,
LoadSafeSeed_Base64DecodingFailure) {
// Loading a non-base64-encoded safe seed should return false.
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath());
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial), GetParam());
SetAllSeedsAndSeedPrefsToNonDefaultValues(&prefs_, seed_store);
seed_store.GetSafeSeedReaderWriterForTesting()->StoreValidatedSeed(
"invalid seed data", "invalid seed data");
base::HistogramTester histogram_tester;
VariationsSeed loaded_seed;
std::unique_ptr<ClientFilterableState> client_state =
CreateTestClientFilterableState();
ASSERT_FALSE(seed_store.LoadSafeSeed(&loaded_seed, client_state.get()));
// Verify metrics and prefs.
histogram_tester.ExpectUniqueSample("Variations.SafeMode.LoadSafeSeed.Result",
LoadSeedResult::kCorruptBase64, 1);
CheckSafeSeedAndSeedPrefsAreCleared(prefs_, seed_store);
CheckRegularSeedAndSeedPrefsAreSet(prefs_, seed_store);
// Moreover, loading an invalid seed should leave the |client_state|
// unmodified.
std::unique_ptr<ClientFilterableState> original_state =
CreateTestClientFilterableState();
EXPECT_EQ(original_state->locale, client_state->locale);
EXPECT_EQ(original_state->reference_date, client_state->reference_date);
EXPECT_EQ(original_state->session_consistency_country,
client_state->session_consistency_country);
EXPECT_EQ(original_state->permanent_consistency_country,
client_state->permanent_consistency_country);
}
struct InvalidSafeSeedTestParams {
const std::string test_name;
const std::string seed;
const std::string signature;
StoreSeedResult store_seed_result;
std::optional<VerifySignatureResult> verify_signature_result = std::nullopt;
};
struct StoreInvalidSafeSeedTestParams {
using TupleT = std::tuple<InvalidSafeSeedTestParams, std::string_view>;
InvalidSafeSeedTestParams invalid_params;
std::string_view field_trial_group;
explicit StoreInvalidSafeSeedTestParams(const TupleT& t)
: invalid_params(std::get<0>(t)), field_trial_group(std::get<1>(t)) {}
};
class StoreInvalidSafeSeedTest
: public SeedStoreGroupTestBase,
public ::testing::WithParamInterface<StoreInvalidSafeSeedTestParams> {
public:
StoreInvalidSafeSeedTest()
: SeedStoreGroupTestBase(prefs::kVariationsSafeCompressedSeed,
GetParam().field_trial_group) {}
~StoreInvalidSafeSeedTest() override = default;
};
INSTANTIATE_TEST_SUITE_P(
All,
StoreInvalidSafeSeedTest,
::testing::ConvertGenerator<
StoreInvalidSafeSeedTestParams::TupleT>(::testing::Combine(
::testing::Values(
InvalidSafeSeedTestParams{
.test_name = "EmptySeed",
.seed = "",
.signature = "unused signature",
.store_seed_result = StoreSeedResult::kFailedEmptyGzipContents},
InvalidSafeSeedTestParams{
.test_name = "InvalidSeed",
.seed = "invalid seed",
.signature = "unused signature",
.store_seed_result = StoreSeedResult::kFailedParse},
InvalidSafeSeedTestParams{
.test_name = "InvalidSignature",
.seed = SerializeSeed(CreateTestSeed()),
// A well-formed signature that does not correspond to the seed.
.signature = kTestSeedData.base64_signature,
.store_seed_result = StoreSeedResult::kFailedSignature,
.verify_signature_result =
VerifySignatureResult::INVALID_SEED}),
::testing::Values(kSeedFilesGroup,
kControlGroup,
kDefaultGroup,
kNoGroup))),
[](const ::testing::TestParamInfo<StoreInvalidSafeSeedTestParams>& params) {
if (params.param.field_trial_group.empty()) {
return params.param.invalid_params.test_name + "_LocalStateOnly";
}
return params.param.invalid_params.test_name + "_" +
std::string(params.param.field_trial_group);
});
// Verify that attempting to store an invalid safe seed fails and does not
// modify Local State's safe-seed-related prefs or a seed file.
TEST_P(StoreInvalidSafeSeedTest, StoreSafeSeed) {
// Set a safe seed in the seed file and local state prefs.
const std::string expected_seed = "a seed";
InvalidSafeSeedTestParams params = GetParam().invalid_params;
const std::string seed_to_store = params.seed;
prefs_.SetString(prefs::kVariationsSafeCompressedSeed, expected_seed);
ASSERT_TRUE(base::WriteFile(temp_seed_file_path_, expected_seed));
// Set associated safe seed local state prefs to their expected values.
const std::string expected_signature = "a signature";
prefs_.SetString(prefs::kVariationsSafeSeedSignature, expected_signature);
const int expected_milestone = 90;
prefs_.SetInteger(prefs::kVariationsSafeSeedMilestone, expected_milestone);
const base::Time now = base::Time::Now();
const base::Time expected_fetch_time = now - base::Hours(3);
prefs_.SetTime(prefs::kVariationsSafeSeedFetchTime, expected_fetch_time);
std::unique_ptr<ClientFilterableState> client_state =
CreateTestClientFilterableState();
const std::string expected_locale = "en-US";
client_state->locale = "pt-PT";
prefs_.SetString(prefs::kVariationsSafeSeedLocale, expected_locale);
const std::string expected_permanent_consistency_country = "US";
client_state->permanent_consistency_country = "CA";
prefs_.SetString(prefs::kVariationsSafeSeedPermanentConsistencyCountry,
expected_permanent_consistency_country);
const std::string expected_session_consistency_country = "BR";
client_state->session_consistency_country = "PT";
prefs_.SetString(prefs::kVariationsSafeSeedSessionConsistencyCountry,
expected_session_consistency_country);
const base::Time expected_date = now - base::Days(2);
client_state->reference_date = now - base::Days(1);
prefs_.SetTime(prefs::kVariationsSafeSeedDate, expected_date);
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath(),
/*signature_verification_needed=*/true);
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial),
GetParam().field_trial_group);
seed_store.SetSafeSeedReaderWriterForTesting(std::move(seed_reader_writer_));
base::HistogramTester histogram_tester;
// Verify that attempting to store an invalid seed fails.
ASSERT_FALSE(
seed_store.StoreSafeSeed(params.seed, params.signature,
/*seed_milestone=*/91, *client_state,
/*seed_fetch_time=*/now - base::Hours(1)));
// Verify that the seed file has no pending writes and was not overwritten.
ASSERT_FALSE(timer_.IsRunning());
std::string seed_file_data;
ASSERT_TRUE(base::ReadFileToString(temp_seed_file_path_, &seed_file_data));
EXPECT_EQ(seed_file_data, expected_seed);
// Verify that none of the safe seed prefs were overwritten.
if (ShouldUseLocalStateSeed()) {
EXPECT_EQ(prefs_.GetString(prefs::kVariationsSafeCompressedSeed),
expected_seed);
}
EXPECT_EQ(prefs_.GetString(prefs::kVariationsSafeSeedSignature),
expected_signature);
EXPECT_EQ(prefs_.GetString(prefs::kVariationsSafeSeedLocale),
expected_locale);
EXPECT_EQ(prefs_.GetInteger(prefs::kVariationsSafeSeedMilestone),
expected_milestone);
EXPECT_EQ(
prefs_.GetString(prefs::kVariationsSafeSeedPermanentConsistencyCountry),
expected_permanent_consistency_country);
EXPECT_EQ(
prefs_.GetString(prefs::kVariationsSafeSeedSessionConsistencyCountry),
expected_session_consistency_country);
EXPECT_EQ(prefs_.GetTime(prefs::kVariationsSafeSeedDate), expected_date);
EXPECT_EQ(prefs_.GetTime(prefs::kVariationsSafeSeedFetchTime),
expected_fetch_time);
// Verify metrics.
histogram_tester.ExpectUniqueSample(
"Variations.SafeMode.StoreSafeSeed.Result", params.store_seed_result, 1);
if (params.verify_signature_result.has_value()) {
histogram_tester.ExpectUniqueSample(
"Variations.SafeMode.StoreSafeSeed.SignatureValidity",
params.verify_signature_result.value(), 1);
}
}
class StoreSafeSeedDataGroupTest
: public SeedStoreGroupTestBase,
public ::testing::WithParamInterface<StoreSeedDataTestParams> {
public:
StoreSafeSeedDataGroupTest()
: SeedStoreGroupTestBase(prefs::kVariationsSafeCompressedSeed,
GetParam().field_trial_group) {}
~StoreSafeSeedDataGroupTest() override = default;
};
class StoreSafeSeedDataSeedFilesGroupTest : public StoreSafeSeedDataGroupTest {
};
class StoreSafeSeedDataControlAndLocalStateOnlyGroupTest
: public StoreSafeSeedDataGroupTest {};
class StoreSafeSeedDataAllGroupsTest : public StoreSafeSeedDataGroupTest {};
INSTANTIATE_TEST_SUITE_P(
All,
StoreSafeSeedDataSeedFilesGroupTest,
::testing::ConvertGenerator<StoreSeedDataTestParams::TupleT>(
::testing::Combine(::testing::Bool(),
::testing::Values(kSeedFilesGroup))));
TEST_P(StoreSafeSeedDataSeedFilesGroupTest, StoreSafeSeed_ValidSignature) {
auto client_state = CreateDummyClientFilterableState();
const std::string expected_locale = "en-US";
client_state->locale = expected_locale;
const base::Time now = base::Time::Now();
const base::Time expected_date = now - base::Days(1);
client_state->reference_date = expected_date;
const std::string expected_permanent_consistency_country = "US";
client_state->permanent_consistency_country =
expected_permanent_consistency_country;
const std::string expected_session_consistency_country = "CA";
client_state->session_consistency_country =
expected_session_consistency_country;
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath(),
/*signature_verification_needed=*/true);
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial),
GetParam().field_trial_group);
base::HistogramTester histogram_tester;
seed_store.SetSafeSeedReaderWriterForTesting(std::move(seed_reader_writer_));
std::string expected_seed;
ASSERT_TRUE(base::Base64Decode(kTestSeedData.base64_uncompressed_data,
&expected_seed));
const std::string expected_signature = kTestSeedData.base64_signature;
const int expected_seed_milestone = 92;
const base::Time expected_fetch_time = now - base::Hours(6);
// Verify that storing the safe seed succeeded.
ASSERT_TRUE(seed_store.StoreSafeSeed(expected_seed, expected_signature,
expected_seed_milestone, *client_state,
expected_fetch_time));
// Force write for SeedReaderWriter.
timer_.Fire();
file_writer_thread_.FlushForTesting();
// Make sure the seed was successfully stored in the seed file.
std::string seed_file_data;
EXPECT_TRUE(base::ReadFileToString(temp_seed_file_path_, &seed_file_data));
EXPECT_EQ(seed_file_data, Gzip(expected_seed));
// Verify that safe-seed-related prefs were successfully stored.
EXPECT_EQ(prefs_.GetString(prefs::kVariationsSafeSeedSignature),
expected_signature);
EXPECT_EQ(prefs_.GetString(prefs::kVariationsSafeSeedLocale),
expected_locale);
EXPECT_EQ(prefs_.GetInteger(prefs::kVariationsSafeSeedMilestone),
expected_seed_milestone);
EXPECT_EQ(
prefs_.GetString(prefs::kVariationsSafeSeedPermanentConsistencyCountry),
expected_permanent_consistency_country);
EXPECT_EQ(
prefs_.GetString(prefs::kVariationsSafeSeedSessionConsistencyCountry),
expected_session_consistency_country);
EXPECT_EQ(prefs_.GetTime(prefs::kVariationsSafeSeedDate), expected_date);
EXPECT_EQ(prefs_.GetTime(prefs::kVariationsSafeSeedFetchTime),
expected_fetch_time);
// Verify metrics.
histogram_tester.ExpectUniqueSample(
"Variations.SafeMode.StoreSafeSeed.Result", StoreSeedResult::kSuccess, 1);
histogram_tester.ExpectUniqueSample(
"Variations.SafeMode.StoreSafeSeed.SignatureValidity",
VerifySignatureResult::VALID_SIGNATURE, 1);
}
TEST_P(StoreSafeSeedDataSeedFilesGroupTest,
StoreSafeSeed_PreviouslyIdenticalToLatestSeed) {
// Create two distinct seeds: an old one saved as both the safe and the latest
// seed value, and a new one that should overwrite only the stored safe seed
// value.
const std::string old_seed_data = SerializeSeed(CreateTestSeed());
VariationsSeed new_seed = CreateTestSeed();
new_seed.set_serial_number("12345678");
const std::string new_seed_data = SerializeSeed(new_seed);
ASSERT_NE(old_seed_data, new_seed_data);
const std::string base64_old_seed = GzipAndBase64Encode(old_seed_data);
const std::string compressed_old_seed = Gzip(old_seed_data);
const base::Time fetch_time = WrapTime(12345);
auto unused_client_state = CreateDummyClientFilterableState();
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath());
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial),
GetParam().field_trial_group);
seed_store.GetSafeSeedReaderWriterForTesting()->StoreValidatedSeed(
compressed_old_seed, base64_old_seed);
seed_store.GetSeedReaderWriterForTesting()->StoreValidatedSeed(
kIdenticalToSafeSeedSentinel, kIdenticalToSafeSeedSentinel);
base::HistogramTester histogram_tester;
ASSERT_TRUE(seed_store.StoreSafeSeed(
new_seed_data, "a completely ignored signature",
/*seed_milestone=*/92, *unused_client_state, fetch_time));
// Verify the latest seed value was copied before the safe seed was
// overwritten.
EXPECT_EQ(compressed_old_seed,
seed_store.GetSeedReaderWriterForTesting()->GetSeedData().data);
// Verify that loading the stored seed returns the old seed value.
VariationsSeed loaded_seed;
std::string loaded_seed_data;
std::string unused_loaded_base64_seed_signature;
ASSERT_TRUE(seed_store.LoadSeed(&loaded_seed, &loaded_seed_data,
&unused_loaded_base64_seed_signature));
EXPECT_EQ(old_seed_data, SerializeSeed(loaded_seed));
EXPECT_EQ(old_seed_data, loaded_seed_data);
// Verify that the seed file indeed contains the new seed's serialized value.
EXPECT_EQ(Gzip(new_seed_data),
seed_store.GetSafeSeedReaderWriterForTesting()->GetSeedData().data);
VariationsSeed loaded_safe_seed;
ASSERT_TRUE(
seed_store.LoadSafeSeed(&loaded_safe_seed, unused_client_state.get()));
EXPECT_EQ(SerializeSeed(new_seed), SerializeSeed(loaded_safe_seed));
EXPECT_EQ(fetch_time, seed_store.GetSafeSeedFetchTime());
// Verify metrics.
histogram_tester.ExpectUniqueSample(
"Variations.SafeMode.StoreSafeSeed.Result", StoreSeedResult::kSuccess, 1);
}
INSTANTIATE_TEST_SUITE_P(
All,
StoreSafeSeedDataControlAndLocalStateOnlyGroupTest,
::testing::ConvertGenerator<StoreSeedDataTestParams::TupleT>(
::testing::Combine(
::testing::Bool(),
::testing::Values(kControlGroup, kDefaultGroup, kNoGroup))));
TEST_P(StoreSafeSeedDataControlAndLocalStateOnlyGroupTest,
StoreSafeSeed_ValidSignature) {
std::string expected_seed;
ASSERT_TRUE(base::Base64Decode(kTestSeedData.base64_uncompressed_data,
&expected_seed));
const std::string expected_signature = kTestSeedData.base64_signature;
const int expected_seed_milestone = 92;
auto client_state = CreateDummyClientFilterableState();
const std::string expected_locale = "en-US";
client_state->locale = expected_locale;
const base::Time now = base::Time::Now();
const base::Time expected_date = now - base::Days(1);
client_state->reference_date = expected_date;
const std::string expected_permanent_consistency_country = "US";
client_state->permanent_consistency_country =
expected_permanent_consistency_country;
const std::string expected_session_consistency_country = "CA";
client_state->session_consistency_country =
expected_session_consistency_country;
const base::Time expected_fetch_time = now - base::Hours(6);
// Initialize SeedStore with test prefs and SeedReaderWriter.
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath(),
/*signature_verification_needed=*/true);
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial),
GetParam().field_trial_group);
base::HistogramTester histogram_tester;
seed_store.SetSafeSeedReaderWriterForTesting(std::move(seed_reader_writer_));
// Verify that storing the safe seed succeeded.
ASSERT_TRUE(seed_store.StoreSafeSeed(expected_seed, expected_signature,
expected_seed_milestone, *client_state,
expected_fetch_time));
// Verify that the seed file has no pending or executed writes
ASSERT_FALSE(timer_.IsRunning());
EXPECT_FALSE(base::PathExists(temp_seed_file_path_));
// Verify that safe-seed-related prefs were successfully stored.
const std::string safe_seed =
prefs_.GetString(prefs::kVariationsSafeCompressedSeed);
std::string decoded_compressed_seed;
ASSERT_TRUE(
base::Base64Decode(prefs_.GetString(prefs::kVariationsSafeCompressedSeed),
&decoded_compressed_seed));
EXPECT_EQ(Gzip(expected_seed), decoded_compressed_seed);
EXPECT_EQ(prefs_.GetString(prefs::kVariationsSafeSeedSignature),
expected_signature);
EXPECT_EQ(prefs_.GetString(prefs::kVariationsSafeSeedLocale),
expected_locale);
EXPECT_EQ(prefs_.GetInteger(prefs::kVariationsSafeSeedMilestone),
expected_seed_milestone);
EXPECT_EQ(
prefs_.GetString(prefs::kVariationsSafeSeedPermanentConsistencyCountry),
expected_permanent_consistency_country);
EXPECT_EQ(
prefs_.GetString(prefs::kVariationsSafeSeedSessionConsistencyCountry),
expected_session_consistency_country);
EXPECT_EQ(prefs_.GetTime(prefs::kVariationsSafeSeedDate), expected_date);
EXPECT_EQ(prefs_.GetTime(prefs::kVariationsSafeSeedFetchTime),
expected_fetch_time);
// Verify metrics.
histogram_tester.ExpectUniqueSample(
"Variations.SafeMode.StoreSafeSeed.Result", StoreSeedResult::kSuccess, 1);
histogram_tester.ExpectUniqueSample(
"Variations.SafeMode.StoreSafeSeed.SignatureValidity",
VerifySignatureResult::VALID_SIGNATURE, 1);
}
TEST_P(StoreSafeSeedDataControlAndLocalStateOnlyGroupTest,
StoreSafeSeed_PreviouslyIdenticalToLatestSeed) {
// Create two distinct seeds: an old one saved as both the safe and the latest
// seed value, and a new one that should overwrite only the stored safe seed
// value.
const std::string old_seed_data = SerializeSeed(CreateTestSeed());
VariationsSeed new_seed = CreateTestSeed();
new_seed.set_serial_number("12345678");
const std::string new_seed_data = SerializeSeed(new_seed);
ASSERT_NE(old_seed_data, new_seed_data);
const std::string base64_old_seed = GzipAndBase64Encode(old_seed_data);
const std::string compressed_old_seed = Gzip(old_seed_data);
const base::Time fetch_time = WrapTime(12345);
auto unused_client_state = CreateDummyClientFilterableState();
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath());
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial),
GetParam().field_trial_group);
seed_store.GetSafeSeedReaderWriterForTesting()->StoreValidatedSeed(
compressed_old_seed, base64_old_seed);
seed_store.GetSeedReaderWriterForTesting()->StoreValidatedSeed(
kIdenticalToSafeSeedSentinel, kIdenticalToSafeSeedSentinel);
base::HistogramTester histogram_tester;
ASSERT_TRUE(seed_store.StoreSafeSeed(
new_seed_data, "a completely ignored signature",
/*seed_milestone=*/92, *unused_client_state, fetch_time));
// Verify the latest seed value was copied before the safe seed was
// overwritten.
EXPECT_EQ(base64_old_seed,
prefs_.GetString(prefs::kVariationsCompressedSeed));
EXPECT_EQ(base64_old_seed,
seed_store.GetSeedReaderWriterForTesting()->GetSeedData().data);
// Verify that loading the stored seed returns the old seed value.
VariationsSeed loaded_seed;
std::string loaded_seed_data;
std::string unused_loaded_base64_seed_signature;
ASSERT_TRUE(seed_store.LoadSeed(&loaded_seed, &loaded_seed_data,
&unused_loaded_base64_seed_signature));
EXPECT_EQ(old_seed_data, SerializeSeed(loaded_seed));
EXPECT_EQ(old_seed_data, loaded_seed_data);
// Verify that the safe seed prefs indeed contain the new seed's serialized
// value.
EXPECT_EQ(GzipAndBase64Encode(new_seed_data),
prefs_.GetString(prefs::kVariationsSafeCompressedSeed));
EXPECT_EQ(GzipAndBase64Encode(new_seed_data),
seed_store.GetSafeSeedReaderWriterForTesting()->GetSeedData().data);
VariationsSeed loaded_safe_seed;
ASSERT_TRUE(
seed_store.LoadSafeSeed(&loaded_safe_seed, unused_client_state.get()));
EXPECT_EQ(SerializeSeed(new_seed), SerializeSeed(loaded_safe_seed));
EXPECT_EQ(fetch_time, seed_store.GetSafeSeedFetchTime());
// Verify metrics.
histogram_tester.ExpectUniqueSample(
"Variations.SafeMode.StoreSafeSeed.Result", StoreSeedResult::kSuccess, 1);
}
INSTANTIATE_TEST_SUITE_P(
All,
StoreSafeSeedDataAllGroupsTest,
::testing::ConvertGenerator<StoreSeedDataTestParams::TupleT>(
::testing::Combine(::testing::Bool(),
::testing::Values(kSeedFilesGroup,
kControlGroup,
kDefaultGroup,
kNoGroup))));
TEST_P(StoreSafeSeedDataAllGroupsTest, StoreSafeSeed_IdenticalToLatestSeed) {
const VariationsSeed seed = CreateTestSeed();
const std::string serialized_seed = SerializeSeed(seed);
const std::string compressed_seed = Gzip(serialized_seed);
const std::string base64_seed = SerializeSeedBase64(seed);
auto unused_client_state = CreateDummyClientFilterableState();
const base::Time last_fetch_time = WrapTime(99999);
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath());
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial),
GetParam().field_trial_group);
seed_store.GetSeedReaderWriterForTesting()->StoreValidatedSeed(
compressed_seed, base64_seed);
prefs_.SetTime(prefs::kVariationsLastFetchTime, last_fetch_time);
const std::string expected_seed =
GetParam().field_trial_group == kSeedFilesGroup ? compressed_seed
: base64_seed;
base::HistogramTester histogram_tester;
ASSERT_TRUE(seed_store.StoreSafeSeed(
serialized_seed, "a completely ignored signature", /*seed_milestone=*/92,
*unused_client_state, /*seed_fetch_time=*/WrapTime(12345)));
// Verify the latest seed value was migrated to a sentinel value, rather than
// the full string.
if (ShouldUseLocalStateSeed()) {
EXPECT_EQ(kIdenticalToSafeSeedSentinel,
prefs_.GetString(prefs::kVariationsCompressedSeed));
}
EXPECT_EQ(kIdenticalToSafeSeedSentinel,
seed_store.GetSeedReaderWriterForTesting()->GetSeedData().data);
// Verify that loading the stored seed returns the original seed value.
VariationsSeed loaded_seed;
std::string loaded_seed_data;
std::string unused_loaded_base64_seed_signature;
ASSERT_TRUE(seed_store.LoadSeed(&loaded_seed, &loaded_seed_data,
&unused_loaded_base64_seed_signature));
EXPECT_EQ(serialized_seed, SerializeSeed(loaded_seed));
EXPECT_EQ(serialized_seed, loaded_seed_data);
// Verify that the safe seed from prefs and SeedReaderWriter is unchanged
// and that the last fetch time was copied from the latest seed.
if (ShouldUseLocalStateSeed()) {
EXPECT_EQ(base64_seed,
prefs_.GetString(prefs::kVariationsSafeCompressedSeed));
}
EXPECT_EQ(expected_seed,
seed_store.GetSafeSeedReaderWriterForTesting()->GetSeedData().data);
VariationsSeed loaded_safe_seed;
EXPECT_TRUE(
seed_store.LoadSafeSeed(&loaded_safe_seed, unused_client_state.get()));
EXPECT_EQ(serialized_seed, SerializeSeed(loaded_safe_seed));
EXPECT_EQ(last_fetch_time, seed_store.GetSafeSeedFetchTime());
// Verify metrics.
histogram_tester.ExpectUniqueSample(
"Variations.SafeMode.StoreSafeSeed.Result", StoreSeedResult::kSuccess, 1);
}
TEST_P(LoadSeedDataAllGroupsTest, VerifySeedSignatureSignatureIsValid) {
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath(),
/*signature_verification_needed=*/true);
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial), GetParam());
StoreValidatedSeed(seed_store);
base::HistogramTester histogram_tester;
ASSERT_TRUE(MakeSeedStoreLoadStoredSeed(seed_store));
histogram_tester.ExpectUniqueSample("Variations.LoadSeedSignature",
VerifySignatureResult::VALID_SIGNATURE,
1);
}
TEST_P(LoadSeedDataAllGroupsTest, VerifySeedSignatureSignatureIsMissing) {
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath(),
/*signature_verification_needed=*/true);
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial), GetParam());
StoreValidatedSeed(seed_store, /*test_signature=*/std::string());
base::HistogramTester histogram_tester;
ASSERT_FALSE(MakeSeedStoreLoadStoredSeed(seed_store));
histogram_tester.ExpectUniqueSample("Variations.LoadSeedSignature",
VerifySignatureResult::MISSING_SIGNATURE,
1);
}
TEST_P(LoadSeedDataAllGroupsTest,
VerifySeedSignatureSignatureNotBase64Encoded) {
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath(),
/*signature_verification_needed=*/true);
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial), GetParam());
StoreValidatedSeed(seed_store,
/*test_signature=*/"not a base64-encoded string");
base::HistogramTester histogram_tester;
ASSERT_FALSE(MakeSeedStoreLoadStoredSeed(seed_store));
histogram_tester.ExpectUniqueSample("Variations.LoadSeedSignature",
VerifySignatureResult::DECODE_FAILED, 1);
}
TEST_P(LoadSeedDataAllGroupsTest, VerifySeedSignatureSignatureDoesNotMatch) {
// Using a different signature (e.g. the base64 seed data) should fail.
// OpenSSL doesn't distinguish signature decode failure from the
// signature not matching.
VariationsSeed seed;
ASSERT_TRUE(seed.ParseFromString(seed_data_));
std::string base64_seed_data = SerializeSeedBase64(seed);
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath(),
/*signature_verification_needed=*/true);
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial), GetParam());
StoreValidatedSeed(seed_store, /*test_signature=*/base64_seed_data);
base::HistogramTester histogram_tester;
ASSERT_FALSE(MakeSeedStoreLoadStoredSeed(seed_store));
histogram_tester.ExpectUniqueSample("Variations.LoadSeedSignature",
VerifySignatureResult::INVALID_SEED, 1);
}
TEST_P(LoadSeedDataAllGroupsTest, VerifySeedSignatureSeedDoesNotMatch) {
const std::string base64_seed_signature = kTestSeedData.base64_signature;
VariationsSeed wrong_seed;
ASSERT_TRUE(wrong_seed.ParseFromString(seed_data_));
(*wrong_seed.mutable_study(0)->mutable_name())[0] = 'x';
const std::string wrong_seed_data = SerializeSeed(wrong_seed);
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath(),
/*signature_verification_needed=*/true);
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial), GetParam());
StoreValidatedSeed(seed_store, /*test_signature=*/base64_seed_signature,
/*seed_data=*/&wrong_seed_data);
base::HistogramTester histogram_tester;
ASSERT_FALSE(MakeSeedStoreLoadStoredSeed(seed_store));
histogram_tester.ExpectUniqueSample("Variations.LoadSeedSignature",
VerifySignatureResult::INVALID_SEED, 1);
}
class VariationsSeedStoreTestAllGroups
: public SeedStoreGroupTestBase,
public ::testing::WithParamInterface<std::string_view> {
public:
explicit VariationsSeedStoreTestAllGroups()
: SeedStoreGroupTestBase(prefs::kVariationsCompressedSeed, GetParam()) {}
~VariationsSeedStoreTestAllGroups() override = default;
};
INSTANTIATE_TEST_SUITE_P(
All,
VariationsSeedStoreTestAllGroups,
::testing::Values(kSeedFilesGroup, kControlGroup, kDefaultGroup, kNoGroup));
TEST_P(VariationsSeedStoreTestAllGroups, LastFetchTime_DistinctSeeds) {
base::Time start_time = WrapTime(10);
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath());
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial), GetParam());
seed_store.GetSafeSeedReaderWriterForTesting()->StoreValidatedSeed("one",
"one");
seed_store.GetSafeSeedReaderWriterForTesting()->StoreValidatedSeed("not one",
"not one");
prefs_.SetTime(prefs::kVariationsLastFetchTime, WrapTime(1));
prefs_.SetTime(prefs::kVariationsSafeSeedFetchTime, WrapTime(0));
seed_store.RecordLastFetchTime(WrapTime(11));
// Verify that the last fetch time was updated.
const base::Time last_fetch_time =
prefs_.GetTime(prefs::kVariationsLastFetchTime);
EXPECT_EQ(WrapTime(11), last_fetch_time);
EXPECT_GE(last_fetch_time, start_time);
// Verify that the safe seed's fetch time was *not* updated.
EXPECT_EQ(WrapTime(0), prefs_.GetTime(prefs::kVariationsSafeSeedFetchTime));
}
TEST_P(VariationsSeedStoreTestAllGroups, LastFetchTime_IdenticalSeeds) {
base::Time start_time = WrapTime(10);
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath());
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial), GetParam());
seed_store.GetSeedReaderWriterForTesting()->StoreValidatedSeed(
kIdenticalToSafeSeedSentinel, kIdenticalToSafeSeedSentinel);
seed_store.GetSafeSeedReaderWriterForTesting()->StoreValidatedSeed(
"some seed", "some seed");
prefs_.SetTime(prefs::kVariationsLastFetchTime, WrapTime(1));
prefs_.SetTime(prefs::kVariationsSafeSeedFetchTime, WrapTime(0));
seed_store.RecordLastFetchTime(WrapTime(11));
// Verify that the last fetch time was updated.
const base::Time last_fetch_time =
prefs_.GetTime(prefs::kVariationsLastFetchTime);
EXPECT_EQ(WrapTime(11), last_fetch_time);
EXPECT_GE(last_fetch_time, start_time);
// Verify that the safe seed's fetch time *was* also updated.
EXPECT_EQ(last_fetch_time,
prefs_.GetTime(prefs::kVariationsSafeSeedFetchTime));
}
TEST_P(VariationsSeedStoreTestAllGroups,
GetLatestSerialNumber_LoadsInitialValue) {
// Store good seed data to test if loading works.
const std::string seed_data = SerializeSeed(CreateTestSeed());
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath());
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial), GetParam());
seed_store.GetSeedReaderWriterForTesting()->StoreValidatedSeed(
Gzip(seed_data), GzipAndBase64Encode(seed_data));
prefs_.SetString(prefs::kVariationsSeedSignature,
"a completely ignored signature");
EXPECT_EQ("123", seed_store.GetLatestSerialNumber());
}
TEST_P(VariationsSeedStoreTestAllGroups,
GetLatestSerialNumber_ClearsPrefsOnFailure) {
// Store corrupted seed data to test that prefs are cleared when loading
// fails.
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath());
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial), GetParam());
seed_store.GetSeedReaderWriterForTesting()->StoreValidatedSeed(
"invalid seed data", "invalid seed data");
prefs_.SetString(prefs::kVariationsSeedSignature, "an unused signature");
EXPECT_EQ(std::string(), seed_store.GetLatestSerialNumber());
EXPECT_TRUE(PrefHasDefaultValue(prefs_, prefs::kVariationsCompressedSeed));
EXPECT_THAT(seed_store.GetSeedReaderWriterForTesting()->GetSeedData().data,
IsEmpty());
}
TEST_F(VariationsSeedStoreTest, GetLatestSerialNumber_EmptyWhenNoSeedIsSaved) {
// Start with empty prefs.
TestingPrefServiceSimple prefs;
VariationsSeedStore::RegisterPrefs(prefs.registry());
TestVariationsSeedStore seed_store(&prefs);
EXPECT_EQ(std::string(), seed_store.GetLatestSerialNumber());
}
// Verifies that GetTimeForStudyDateChecks() returns the server timestamp for
// when the regular seed was fetched,|kVariationsSeedDate|, when the time is
// more recent than the build time.
TEST_F(VariationsSeedStoreTest, RegularSeedTimeReturned) {
TestingPrefServiceSimple prefs;
VariationsSeedStore::RegisterPrefs(prefs.registry());
const base::Time seed_fetch_time = base::GetBuildTime() + base::Days(4);
prefs.SetTime(prefs::kVariationsSeedDate, seed_fetch_time);
TestVariationsSeedStore seed_store(&prefs);
EXPECT_EQ(seed_store.GetTimeForStudyDateChecks(/*is_safe_seed=*/false),
seed_fetch_time);
}
// Verifies that GetTimeForStudyDateChecks() returns the server timestamp for
// when the safe seed was fetched, |kVariationsSafeSeedDate|, when the time is
// more recent than the build time.
TEST_F(VariationsSeedStoreTest, SafeSeedTimeReturned) {
TestingPrefServiceSimple prefs;
VariationsSeedStore::RegisterPrefs(prefs.registry());
const base::Time safe_seed_fetch_time = base::GetBuildTime() + base::Days(7);
prefs.SetTime(prefs::kVariationsSafeSeedDate, safe_seed_fetch_time);
TestVariationsSeedStore seed_store(&prefs);
EXPECT_EQ(seed_store.GetTimeForStudyDateChecks(/*is_safe_seed=*/true),
safe_seed_fetch_time);
}
// Verifies that GetTimeForStudyDateChecks() returns the build time when it is
// more recent than |kVariationsSeedDate|.
TEST_F(VariationsSeedStoreTest, BuildTimeReturnedForRegularSeed) {
TestingPrefServiceSimple prefs;
VariationsSeedStore::RegisterPrefs(prefs.registry());
prefs.SetTime(prefs::kVariationsSeedDate,
base::GetBuildTime() - base::Days(2));
TestVariationsSeedStore seed_store(&prefs);
EXPECT_EQ(seed_store.GetTimeForStudyDateChecks(/*is_safe_seed=*/false),
base::GetBuildTime());
}
// Verifies that GetTimeForStudyDateChecks() returns the build time when it is
// more recent than |kVariationsSafeSeedDate|.
TEST_F(VariationsSeedStoreTest, BuildTimeReturnedForSafeSeed) {
TestingPrefServiceSimple prefs;
VariationsSeedStore::RegisterPrefs(prefs.registry());
prefs.SetTime(prefs::kVariationsSeedDate,
base::GetBuildTime() - base::Days(3));
TestVariationsSeedStore seed_store(&prefs);
EXPECT_EQ(seed_store.GetTimeForStudyDateChecks(/*is_safe_seed=*/true),
base::GetBuildTime());
}
// Verifies that GetTimeForStudyDateChecks() returns the build time when the
// seed time is null.
TEST_F(VariationsSeedStoreTest, BuildTimeReturnedForNullSeedTimes) {
TestingPrefServiceSimple prefs;
VariationsSeedStore::RegisterPrefs(prefs.registry());
ASSERT_TRUE(prefs.GetTime(prefs::kVariationsSeedDate).is_null());
TestVariationsSeedStore seed_store(&prefs);
EXPECT_EQ(seed_store.GetTimeForStudyDateChecks(/*is_safe_seed=*/false),
base::GetBuildTime());
ASSERT_TRUE(prefs.GetTime(prefs::kVariationsSafeSeedDate).is_null());
EXPECT_EQ(seed_store.GetTimeForStudyDateChecks(/*is_safe_seed=*/true),
base::GetBuildTime());
}
#if BUILDFLAG(IS_ANDROID)
TEST_F(VariationsSeedStoreTest, ImportFirstRunJavaSeed) {
const std::string test_seed_data = "raw_seed_data_test";
const std::string test_seed_signature = "seed_signature_test";
const std::string test_seed_country = "seed_country_code_test";
const int64_t test_response_date = 1234567890;
const bool test_is_gzip_compressed = true;
android::SetJavaFirstRunPrefsForTesting(test_seed_data, test_seed_signature,
test_seed_country, test_response_date,
test_is_gzip_compressed);
auto seed = android::GetVariationsFirstRunSeed();
EXPECT_EQ(test_seed_data, seed->data);
EXPECT_EQ(test_seed_signature, seed->signature);
EXPECT_EQ(test_seed_country, seed->country);
EXPECT_EQ(test_response_date, seed->date.InMillisecondsSinceUnixEpoch());
EXPECT_EQ(test_is_gzip_compressed, seed->is_gzip_compressed);
android::ClearJavaFirstRunPrefs();
seed = android::GetVariationsFirstRunSeed();
EXPECT_EQ("", seed->data);
EXPECT_EQ("", seed->signature);
EXPECT_EQ("", seed->country);
EXPECT_EQ(0, seed->date.InMillisecondsSinceUnixEpoch());
EXPECT_FALSE(seed->is_gzip_compressed);
}
class VariationsSeedStoreFirstRunPrefsTest
: public ::testing::TestWithParam<bool> {
private:
base::test::TaskEnvironment task_environment_;
};
INSTANTIATE_TEST_SUITE_P(VariationsSeedStoreTest,
VariationsSeedStoreFirstRunPrefsTest,
::testing::Bool());
TEST_P(VariationsSeedStoreFirstRunPrefsTest, FirstRunPrefsAllowed) {
bool use_first_run_prefs = GetParam();
const std::string test_seed_data = "raw_seed_data_test";
const std::string test_seed_signature = "seed_signature_test";
const std::string test_seed_country = "seed_country_code_test";
const int64_t test_response_date = 1234567890;
const bool test_is_gzip_compressed = true;
android::SetJavaFirstRunPrefsForTesting(test_seed_data, test_seed_signature,
test_seed_country, test_response_date,
test_is_gzip_compressed);
const VariationsSeed test_seed = CreateTestSeed();
const std::string seed_data = SerializeSeed(test_seed);
auto seed = std::make_unique<SeedResponse>();
seed->data = seed_data;
seed->signature = "java_seed_signature";
seed->country = "java_seed_country";
seed->date = base::Time::FromMillisecondsSinceUnixEpoch(test_response_date) +
base::Days(1);
seed->is_gzip_compressed = false;
TestingPrefServiceSimple prefs;
VariationsSeedStore::RegisterPrefs(prefs.registry());
TestVariationsSeedStore seed_store(&prefs,
/*seed_file_dir=*/base::FilePath(),
/*signature_verification_needed=*/false,
/*initial_seed=*/std::move(seed),
use_first_run_prefs);
seed = android::GetVariationsFirstRunSeed();
// VariationsSeedStore must not modify Java prefs at all.
EXPECT_EQ(test_seed_data, seed->data);
EXPECT_EQ(test_seed_signature, seed->signature);
EXPECT_EQ(test_seed_country, seed->country);
EXPECT_EQ(test_response_date, seed->date.InMillisecondsSinceUnixEpoch());
EXPECT_EQ(test_is_gzip_compressed, seed->is_gzip_compressed);
if (use_first_run_prefs) {
EXPECT_TRUE(android::HasMarkedPrefsForTesting());
} else {
EXPECT_FALSE(android::HasMarkedPrefsForTesting());
}
// Seed should be stored in prefs.
EXPECT_FALSE(PrefHasDefaultValue(prefs, prefs::kVariationsCompressedSeed));
EXPECT_EQ(SerializeSeedBase64(test_seed),
prefs.GetString(prefs::kVariationsCompressedSeed));
}
#endif // BUILDFLAG(IS_ANDROID)
#if BUILDFLAG(IS_CHROMEOS)
const featured::SeedDetails CreateDummySafeSeed(
ClientFilterableState* client_state,
base::Time fetch_time_to_store) {
featured::SeedDetails expected_seed;
expected_seed.set_b64_compressed_data(kTestSeedData.base64_compressed_data);
expected_seed.set_signature(kTestSeedData.base64_signature);
expected_seed.set_milestone(92);
expected_seed.set_locale(client_state->locale);
expected_seed.set_date(
client_state->reference_date.ToDeltaSinceWindowsEpoch().InMilliseconds());
expected_seed.set_permanent_consistency_country(
client_state->permanent_consistency_country);
expected_seed.set_session_consistency_country(
client_state->session_consistency_country);
expected_seed.set_fetch_time(
fetch_time_to_store.ToDeltaSinceWindowsEpoch().InMilliseconds());
return expected_seed;
}
// Checks that |platform_data| and |expected_data| deserialize to the same
// VariationsSeed proto.
// |platform_data| and |expected_data| are base64_compressed forms of seed data.
void ExpectSeedData(const std::string& platform_data,
const std::string& expected_data) {
std::string decoded_platform_data;
EXPECT_TRUE(base::Base64Decode(platform_data, &decoded_platform_data));
std::string uncompressed_decoded_platform_data;
EXPECT_TRUE(compression::GzipUncompress(decoded_platform_data,
&uncompressed_decoded_platform_data));
VariationsSeed platform_seed;
EXPECT_TRUE(
platform_seed.ParseFromString(uncompressed_decoded_platform_data));
std::string decoded_expected_data;
EXPECT_TRUE(base::Base64Decode(expected_data, &decoded_expected_data));
std::string uncompressed_decoded_expected_data;
EXPECT_TRUE(compression::GzipUncompress(decoded_expected_data,
&uncompressed_decoded_expected_data));
VariationsSeed expected_seed;
EXPECT_TRUE(
expected_seed.ParseFromString(uncompressed_decoded_expected_data));
EXPECT_THAT(platform_seed, EqualsProto(expected_seed));
}
// Manually verifying each field in featured::SeedDetails rather than using
// EqualsProto is necessary because the
// featured::SeedDetails::b64_compressed_data field may be different between
// |platform| and |expected| even if the data unserializes to the same
// VariationsSeed. This could be caused by implementation differences between
// different versions of compression::GzipCompress.
//
// To accurately compare two featured::SeedDetails protos, the
// `b64_compressed_data` should be deserialized into a VariationsSeed proto and
// the two VariationsSeed protos should be compared.
void ExpectSafeSeed(const featured::SeedDetails& platform,
const featured::SeedDetails expected) {
ExpectSeedData(platform.b64_compressed_data(),
expected.b64_compressed_data());
EXPECT_EQ(platform.locale(), expected.locale());
EXPECT_EQ(platform.milestone(), expected.milestone());
EXPECT_EQ(platform.permanent_consistency_country(),
expected.permanent_consistency_country());
EXPECT_EQ(platform.session_consistency_country(),
expected.session_consistency_country());
EXPECT_EQ(platform.signature(), expected.signature());
EXPECT_EQ(platform.date(), expected.date());
EXPECT_EQ(platform.fetch_time(), expected.fetch_time());
}
TEST_P(StoreSafeSeedDataAllGroupsTest,
SendSafeSeedToPlatform_SucceedFirstAttempt) {
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath(),
/*signature_verification_needed=*/true);
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial),
GetParam().field_trial_group);
ash::featured::FeaturedClient::InitializeFake();
ash::featured::FakeFeaturedClient* client =
ash::featured::FakeFeaturedClient::Get();
client->AddResponse(true);
std::unique_ptr<ClientFilterableState> client_state =
CreateDummyClientFilterableState();
const base::Time fetch_time_to_store = base::Time::Now() - base::Hours(1);
featured::SeedDetails expected_platform_seed =
CreateDummySafeSeed(client_state.get(), fetch_time_to_store);
std::string expected_seed_data;
ASSERT_TRUE(base::Base64Decode(kTestSeedData.base64_uncompressed_data,
&expected_seed_data));
// Verify that storing the safe seed succeeded.
ASSERT_TRUE(seed_store.StoreSafeSeed(
expected_seed_data, expected_platform_seed.signature(),
expected_platform_seed.milestone(), *client_state, fetch_time_to_store));
// Verify that the validated safe seed was received on Platform.
ExpectSafeSeed(client->latest_safe_seed(), expected_platform_seed);
EXPECT_EQ(client->handle_seed_fetched_attempts(), 1);
ash::featured::FeaturedClient::Shutdown();
}
TEST_P(StoreSafeSeedDataAllGroupsTest,
SendSafeSeedToPlatform_FailFirstAttempt) {
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath(),
/*signature_verification_needed=*/true);
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial),
GetParam().field_trial_group);
ash::featured::FeaturedClient::InitializeFake();
ash::featured::FakeFeaturedClient* client =
ash::featured::FakeFeaturedClient::Get();
client->AddResponse(false);
client->AddResponse(true);
std::unique_ptr<ClientFilterableState> client_state =
CreateDummyClientFilterableState();
const base::Time fetch_time_to_store = base::Time::Now() - base::Hours(1);
featured::SeedDetails expected_platform_seed =
CreateDummySafeSeed(client_state.get(), fetch_time_to_store);
std::string expected_seed_data;
ASSERT_TRUE(base::Base64Decode(kTestSeedData.base64_uncompressed_data,
&expected_seed_data));
// Verify that storing the safe seed succeeded.
ASSERT_TRUE(seed_store.StoreSafeSeed(
expected_seed_data, expected_platform_seed.signature(),
expected_platform_seed.milestone(), *client_state, fetch_time_to_store));
// Verify that the validated safe seed was received on Platform.
ExpectSafeSeed(client->latest_safe_seed(), expected_platform_seed);
EXPECT_EQ(client->handle_seed_fetched_attempts(), 2);
ash::featured::FeaturedClient::Shutdown();
}
TEST_P(StoreSafeSeedDataAllGroupsTest, SendSafeSeedToPlatform_FailTwoAttempts) {
TestVariationsSeedStore seed_store(&prefs_, temp_dir_.GetPath(),
/*signature_verification_needed=*/true);
ASSERT_EQ(base::FieldTrialList::FindFullName(kSeedFileTrial),
GetParam().field_trial_group);
ash::featured::FeaturedClient::InitializeFake();
ash::featured::FakeFeaturedClient* client =
ash::featured::FakeFeaturedClient::Get();
client->AddResponse(false);
client->AddResponse(false);
std::unique_ptr<ClientFilterableState> client_state =
CreateDummyClientFilterableState();
const base::Time fetch_time_to_store = base::Time::Now() - base::Hours(1);
featured::SeedDetails seed =
CreateDummySafeSeed(client_state.get(), fetch_time_to_store);
std::string seed_data;
ASSERT_TRUE(
base::Base64Decode(kTestSeedData.base64_uncompressed_data, &seed_data));
// Verify that storing the safe seed succeeded.
ASSERT_TRUE(seed_store.StoreSafeSeed(seed_data, seed.signature(),
seed.milestone(), *client_state,
fetch_time_to_store));
// Verify that the validated safe seed was not received on Platform.
featured::SeedDetails empty_seed;
EXPECT_THAT(client->latest_safe_seed(), EqualsProto(empty_seed));
EXPECT_EQ(client->handle_seed_fetched_attempts(), 2);
ash::featured::FeaturedClient::Shutdown();
}
#endif // BUILDFLAG(IS_CHROMEOS)
} // namespace variations