#include "components/variations/variations_seed_store.h"
#include <memory>
#include <utility>
#include "base/base64.h"
#include "base/build_time.h"
#include "base/functional/callback_helpers.h"
#include "base/run_loop.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/protobuf_matchers.h"
#include "base/test/scoped_command_line.h"
#include "base/test/task_environment.h"
#include "base/time/time.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/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
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "chromeos/ash/components/dbus/featured/fake_featured_client.h"
#include "chromeos/ash/components/dbus/featured/featured.pb.h"
#endif
namespace variations {
namespace {
EqualsProto;
constexpr char kIdenticalToSafeSeedSentinel[] = …;
class TestVariationsSeedStore : public VariationsSeedStore { … };
class SignatureVerifyingVariationsSeedStore : public VariationsSeedStore { … };
base::Time WrapTime(int64_t time) { … }
VariationsSeed CreateTestSeed() { … }
std::unique_ptr<ClientFilterableState> CreateTestClientFilterableState() { … }
std::string SerializeSeed(const VariationsSeed& seed) { … }
std::string Gzip(const std::string& data) { … }
std::string GzipAndBase64Encode(const std::string& data) { … }
std::string SerializeSeedBase64(const VariationsSeed& seed) { … }
std::string Base64DecodeData(const std::string& data) { … }
struct { … } kSeedDeltaTestData;
void SetAllSeedPrefsToNonDefaultValues(PrefService* prefs) { … }
bool PrefHasDefaultValue(const TestingPrefServiceSimple& prefs,
const char* pref_name) { … }
void CheckRegularSeedPrefsAreSet(const TestingPrefServiceSimple& prefs) { … }
void CheckRegularSeedPrefsAreCleared(const TestingPrefServiceSimple& prefs) { … }
void CheckSafeSeedPrefsAreSet(const TestingPrefServiceSimple& prefs) { … }
void CheckSafeSeedPrefsAreCleared(const TestingPrefServiceSimple& prefs) { … }
}
TEST(VariationsSeedStoreTest, LoadSeed_ValidSeed) { … }
TEST(VariationsSeedStoreTest, LoadSeed_InvalidSeed) { … }
TEST(VariationsSeedStoreTest, LoadSeed_InvalidSignature) { … }
TEST(VariationsSeedStoreTest, LoadSeed_InvalidProto) { … }
TEST(VariationsSeedStoreTest, LoadSeed_RejectEmptySignature) { … }
TEST(VariationsSeedStoreTest, LoadSeed_AcceptEmptySignature) { … }
TEST(VariationsSeedStoreTest, LoadSeed_EmptySeed) { … }
TEST(VariationsSeedStoreTest, LoadSeed_IdenticalToSafeSeed) { … }
TEST(VariationsSeedStoreTest, ApplyDeltaPatch) { … }
class VariationsStoreSeedDataTest : public ::testing::Test,
public ::testing::WithParamInterface<bool> { … };
INSTANTIATE_TEST_SUITE_P(…);
TEST_P(VariationsStoreSeedDataTest, StoreSeedData) { … }
TEST_P(VariationsStoreSeedDataTest, ParsedSeed) { … }
TEST_P(VariationsStoreSeedDataTest, CountryCode) { … }
TEST_P(VariationsStoreSeedDataTest, GzippedSeed) { … }
TEST_P(VariationsStoreSeedDataTest, GzippedEmptySeed) { … }
TEST_P(VariationsStoreSeedDataTest, DeltaCompressed) { … }
TEST_P(VariationsStoreSeedDataTest, DeltaCompressedGzipped) { … }
TEST_P(VariationsStoreSeedDataTest, DeltaButNoInitialSeed) { … }
TEST_P(VariationsStoreSeedDataTest, BadDelta) { … }
TEST_P(VariationsStoreSeedDataTest, IdenticalToSafeSeed) { … }
TEST_P(VariationsStoreSeedDataTest,
GetLatestSerialNumber_UpdatedWithNewStoredSeed) { … }
TEST(VariationsSeedStoreTest, LoadSafeSeed_ValidSeed) { … }
TEST(VariationsSeedStoreTest, LoadSafeSeed_CorruptSeed) { … }
TEST(VariationsSeedStoreTest, LoadSafeSeed_InvalidSignature) { … }
TEST(VariationsSeedStoreTest, LoadSafeSeed_EmptySeed) { … }
struct InvalidSafeSeedTestParams { … };
StoreInvalidSafeSeedTest;
INSTANTIATE_TEST_SUITE_P(…);
TEST_P(StoreInvalidSafeSeedTest, StoreSafeSeed) { … }
TEST(VariationsSeedStoreTest, StoreSafeSeed_ValidSignature) { … }
TEST(VariationsSeedStoreTest, StoreSafeSeed_IdenticalToLatestSeed) { … }
TEST(VariationsSeedStoreTest, StoreSafeSeed_PreviouslyIdenticalToLatestSeed) { … }
TEST(VariationsSeedStoreTest, VerifySeedSignature) { … }
TEST(VariationsSeedStoreTest, LastFetchTime_DistinctSeeds) { … }
TEST(VariationsSeedStoreTest, LastFetchTime_IdenticalSeeds) { … }
TEST(VariationsSeedStoreTest, GetLatestSerialNumber_LoadsInitialValue) { … }
TEST(VariationsSeedStoreTest, GetLatestSerialNumber_EmptyWhenNoSeedIsSaved) { … }
TEST(VariationsSeedStoreTest, GetLatestSerialNumber_ClearsPrefsOnFailure) { … }
TEST(VariationsSeedStoreTest, RegularSeedTimeReturned) { … }
TEST(VariationsSeedStoreTest, SafeSeedTimeReturned) { … }
TEST(VariationsSeedStoreTest, BuildTimeReturnedForRegularSeed) { … }
TEST(VariationsSeedStoreTest, BuildTimeReturnedForSafeSeed) { … }
TEST(VariationsSeedStoreTest, BuildTimeReturnedForNullSeedTimes) { … }
#if BUILDFLAG(IS_ANDROID)
TEST(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> {};
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);
const std::string base64_seed_data = SerializeSeedBase64(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, std::move(seed),
use_first_run_prefs);
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);
if (use_first_run_prefs) {
EXPECT_TRUE(android::HasMarkedPrefsForTesting());
} else {
EXPECT_FALSE(android::HasMarkedPrefsForTesting());
}
EXPECT_FALSE(PrefHasDefaultValue(prefs, prefs::kVariationsCompressedSeed));
EXPECT_EQ(base64_seed_data,
prefs.GetString(prefs::kVariationsCompressedSeed));
}
#endif
#if BUILDFLAG(IS_CHROMEOS_ASH)
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;
}
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));
}
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(VariationsSeedStoreTest, SendSafeSeedToPlatform_SucceedFirstAttempt) {
TestingPrefServiceSimple prefs;
VariationsSeedStore::RegisterPrefs(prefs.registry());
SignatureVerifyingVariationsSeedStore seed_store(&prefs);
ash::featured::FeaturedClient::InitializeFake();
ash::featured::FakeFeaturedClient* client =
ash::featured::FakeFeaturedClient::Get();
client->AddResponse(true);
std::unique_ptr<ClientFilterableState> client_state =
CreateDummyClientFilterableState();
base::Time now = base::Time::Now();
const base::Time fetch_time_to_store = 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));
EXPECT_TRUE(seed_store.StoreSafeSeed(
expected_seed_data, expected_platform_seed.signature(),
expected_platform_seed.milestone(), *client_state, fetch_time_to_store));
ExpectSafeSeed(client->latest_safe_seed(), expected_platform_seed);
EXPECT_EQ(client->handle_seed_fetched_attempts(), 1);
ash::featured::FeaturedClient::Shutdown();
}
TEST(VariationsSeedStoreTest, SendSafeSeedToPlatform_FailFirstAttempt) {
TestingPrefServiceSimple prefs;
VariationsSeedStore::RegisterPrefs(prefs.registry());
SignatureVerifyingVariationsSeedStore seed_store(&prefs);
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();
base::Time now = base::Time::Now();
const base::Time fetch_time_to_store = 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));
EXPECT_TRUE(seed_store.StoreSafeSeed(
expected_seed_data, expected_platform_seed.signature(),
expected_platform_seed.milestone(), *client_state, fetch_time_to_store));
ExpectSafeSeed(client->latest_safe_seed(), expected_platform_seed);
EXPECT_EQ(client->handle_seed_fetched_attempts(), 2);
ash::featured::FeaturedClient::Shutdown();
}
TEST(VariationsSeedStoreTest, SendSafeSeedToPlatform_FailTwoAttempts) {
TestingPrefServiceSimple prefs;
VariationsSeedStore::RegisterPrefs(prefs.registry());
SignatureVerifyingVariationsSeedStore seed_store(&prefs);
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();
base::Time now = base::Time::Now();
const base::Time fetch_time_to_store = 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));
EXPECT_TRUE(seed_store.StoreSafeSeed(seed_data, seed.signature(),
seed.milestone(), *client_state,
fetch_time_to_store));
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
}