| // 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 <stdint.h> | 
 |  | 
 | #include <utility> | 
 |  | 
 | #include "base/base64.h" | 
 | #include "base/build_time.h" | 
 | #include "base/command_line.h" | 
 | #include "base/functional/callback_helpers.h" | 
 | #include "base/logging.h" | 
 | #include "base/metrics/histogram_macros.h" | 
 | #include "base/numerics/safe_math.h" | 
 | #include "base/strings/string_number_conversions.h" | 
 | #include "base/task/task_runner.h" | 
 | #include "base/task/thread_pool.h" | 
 | #include "base/version_info/channel.h" | 
 | #include "build/build_config.h" | 
 | #include "components/prefs/pref_registry_simple.h" | 
 | #include "components/prefs/pref_service.h" | 
 | #include "components/variations/client_filterable_state.h" | 
 | #include "components/variations/pref_names.h" | 
 | #include "components/variations/proto/variations_seed.pb.h" | 
 | #include "components/variations/seed_reader_writer.h" | 
 | #include "components/variations/variations_safe_seed_store_local_state.h" | 
 | #include "components/variations/variations_switches.h" | 
 | #include "components/version_info/version_info.h" | 
 | #include "crypto/signature_verifier.h" | 
 | #include "third_party/protobuf/src/google/protobuf/io/coded_stream.h" | 
 | #include "third_party/zlib/google/compression_utils.h" | 
 |  | 
 | #if BUILDFLAG(IS_ANDROID) | 
 | #include "components/variations/android/variations_seed_bridge.h" | 
 | #include "components/variations/metrics.h" | 
 | #endif  // BUILDFLAG(IS_ANDROID) | 
 |  | 
 | #if BUILDFLAG(IS_IOS) | 
 | #include "components/variations/metrics.h" | 
 | #endif  // BUILDFLAG(IS_IOS) | 
 |  | 
 | namespace variations { | 
 | namespace { | 
 |  | 
 | // The ECDSA public key of the variations server for verifying variations seed | 
 | // signatures. | 
 | const uint8_t kPublicKey[] = { | 
 |     0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, | 
 |     0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, | 
 |     0x42, 0x00, 0x04, 0x51, 0x7c, 0x31, 0x4b, 0x50, 0x42, 0xdd, 0x59, 0xda, | 
 |     0x0b, 0xfa, 0x43, 0x44, 0x33, 0x7c, 0x5f, 0xa1, 0x0b, 0xd5, 0x82, 0xf6, | 
 |     0xac, 0x04, 0x19, 0x72, 0x6c, 0x40, 0xd4, 0x3e, 0x56, 0xe2, 0xa0, 0x80, | 
 |     0xa0, 0x41, 0xb3, 0x23, 0x7b, 0x71, 0xc9, 0x80, 0x87, 0xde, 0x35, 0x0d, | 
 |     0x25, 0x71, 0x09, 0x7f, 0xb4, 0x15, 0x2b, 0xff, 0x82, 0x4d, 0xd3, 0xfe, | 
 |     0xc5, 0xef, 0x20, 0xc6, 0xa3, 0x10, 0xbf, | 
 | }; | 
 |  | 
 |  | 
 | // LINT.IfChange | 
 | // The name of the seed file that stores the latest seed data. | 
 | const base::FilePath::CharType kSeedFilename[] = | 
 |     FILE_PATH_LITERAL("VariationsSeedV1"); | 
 | // LINT.ThenChange(/testing/scripts/variations_seed_access_helper.py, /components/variations/variations_seed_store.cc, /components/variations/service/variations_field_trial_creator_unittest.cc, /chrome/browser/metrics/variations/variations_safe_mode_end_to_end_browsertest.cc) | 
 |  | 
 | // Returns true if |signature| is empty and if the command-line flag to accept | 
 | // empty seed signature is specified. | 
 | bool AcceptEmptySeedSignatureForTesting(const std::string& signature) { | 
 |   return signature.empty() && | 
 |          base::CommandLine::ForCurrentProcess()->HasSwitch( | 
 |              switches::kAcceptEmptySeedSignatureForTesting); | 
 | } | 
 |  | 
 | // Verifies a variations seed (the serialized proto bytes) with the specified | 
 | // base-64 encoded signature that was received from the server and returns the | 
 | // result. The signature is assumed to be an "ECDSA with SHA-256" signature | 
 | // (see kECDSAWithSHA256AlgorithmID in the .cc file). Returns the result of | 
 | // signature verification. | 
 | VerifySignatureResult VerifySeedSignature( | 
 |     const std::string& seed_bytes, | 
 |     const std::string& base64_seed_signature) { | 
 |   if (base64_seed_signature.empty()) { | 
 |     return VerifySignatureResult::MISSING_SIGNATURE; | 
 |   } | 
 |  | 
 |   std::string signature; | 
 |   if (!base::Base64Decode(base64_seed_signature, &signature)) | 
 |     return VerifySignatureResult::DECODE_FAILED; | 
 |  | 
 |   crypto::SignatureVerifier verifier; | 
 |   if (!verifier.VerifyInit(crypto::SignatureVerifier::ECDSA_SHA256, | 
 |                            base::as_byte_span(signature), kPublicKey)) { | 
 |     return VerifySignatureResult::INVALID_SIGNATURE; | 
 |   } | 
 |  | 
 |   verifier.VerifyUpdate(base::as_byte_span(seed_bytes)); | 
 |   if (!verifier.VerifyFinal()) { | 
 |     return VerifySignatureResult::INVALID_SEED; | 
 |   } | 
 |  | 
 |   return VerifySignatureResult::VALID_SIGNATURE; | 
 | } | 
 |  | 
 | // Truncates a time to the start of the day in UTC. If given a time representing | 
 | // 2014-03-11 10:18:03.1 UTC, it will return a time representing | 
 | // 2014-03-11 00:00:00.0 UTC. | 
 | base::Time TruncateToUTCDay(base::Time time) { | 
 |   base::Time::Exploded exploded; | 
 |   time.UTCExplode(&exploded); | 
 |   exploded.hour = 0; | 
 |   exploded.minute = 0; | 
 |   exploded.second = 0; | 
 |   exploded.millisecond = 0; | 
 |  | 
 |   base::Time out_time; | 
 |   bool conversion_success = base::Time::FromUTCExploded(exploded, &out_time); | 
 |   DCHECK(conversion_success); | 
 |   return out_time; | 
 | } | 
 |  | 
 | UpdateSeedDateResult GetSeedDateChangeState( | 
 |     base::Time server_seed_date, | 
 |     base::Time stored_seed_date) { | 
 |   if (server_seed_date < stored_seed_date) { | 
 |     return UpdateSeedDateResult::NEW_DATE_IS_OLDER; | 
 |   } | 
 |  | 
 |   if (TruncateToUTCDay(server_seed_date) != | 
 |       TruncateToUTCDay(stored_seed_date)) { | 
 |     // The server date is later than the stored date, and they are from | 
 |     // different UTC days, so |server_seed_date| is a valid new day. | 
 |     return UpdateSeedDateResult::NEW_DAY; | 
 |   } | 
 |   return UpdateSeedDateResult::SAME_DAY; | 
 | } | 
 |  | 
 | // Remove gzip compression from |data|. | 
 | // Returns success or error, populating result on success. | 
 | StoreSeedResult Uncompress(const std::string& compressed, std::string* result) { | 
 |   DCHECK(result); | 
 |   if (!compression::GzipUncompress(compressed, result)) { | 
 |     return StoreSeedResult::kFailedUngzip; | 
 |   } | 
 |   if (result->empty()) { | 
 |     return StoreSeedResult::kFailedEmptyGzipContents; | 
 |   } | 
 |   return StoreSeedResult::kSuccess; | 
 | } | 
 |  | 
 | #if BUILDFLAG(IS_ANDROID) | 
 | // Marks seed storing as successful on the Java side to avoid repeated seed | 
 | // fetches. Called only on first run. | 
 | void MarkVariationsSeedAsStoredIfEmptySeed( | 
 |     SeedReaderWriter::ReadSeedDataResult read_result) { | 
 |   if (read_result.result == LoadSeedResult::kEmpty) { | 
 |     android::MarkVariationsSeedAsStored(); | 
 |   } | 
 | } | 
 | #endif  // BUILDFLAG(IS_ANDROID) | 
 |  | 
 | }  // namespace | 
 |  | 
 | class VariationsSeedStore::TwoSeedReader | 
 |     : public base::RefCountedThreadSafe<TwoSeedReader> { | 
 |  public: | 
 |   explicit TwoSeedReader( | 
 |       base::OnceCallback<void(SeedReaderWriter::ReadSeedDataResult, | 
 |                               SeedReaderWriter::ReadSeedDataResult)> | 
 |           done_callback) | 
 |       : done_callback_(std::move(done_callback)) {} | 
 |  | 
 |   // Called when a single seed has been read. If both seeds have been read, | 
 |   // `done_callback_` will be called. | 
 |   void OnSingleSeedRead(SeedType seed_type, | 
 |                         SeedReaderWriter::ReadSeedDataResult read_result) { | 
 |     switch (seed_type) { | 
 |       case SeedType::SAFE: | 
 |         safe_seed_read_result_ = std::move(read_result); | 
 |         break; | 
 |       case SeedType::LATEST: | 
 |         latest_seed_read_result_ = std::move(read_result); | 
 |         break; | 
 |     } | 
 |     if (IsDone()) { | 
 |       std::move(done_callback_) | 
 |           .Run(std::move(safe_seed_read_result_).value(), | 
 |                std::move(latest_seed_read_result_).value()); | 
 |     } | 
 |   } | 
 |  | 
 |  private: | 
 |   friend class base::RefCountedThreadSafe<TwoSeedReader>; | 
 |  | 
 |   ~TwoSeedReader() = default; | 
 |  | 
 |   // Returns true if both seeds have been read. This is used to determine when | 
 |   // to call `done_callback_`. | 
 |   bool IsDone() const { | 
 |     return safe_seed_read_result_.has_value() && | 
 |            latest_seed_read_result_.has_value(); | 
 |   } | 
 |  | 
 |   ReadBothSeedsCallback done_callback_; | 
 |  | 
 |   std::optional<SeedReaderWriter::ReadSeedDataResult> safe_seed_read_result_; | 
 |   std::optional<SeedReaderWriter::ReadSeedDataResult> latest_seed_read_result_; | 
 | }; | 
 |  | 
 | ValidatedSeed::ValidatedSeed() = default; | 
 | ValidatedSeed::~ValidatedSeed() = default; | 
 | ValidatedSeed::ValidatedSeed(ValidatedSeed&& other) = default; | 
 | ValidatedSeed& ValidatedSeed::operator=(ValidatedSeed&& other) = default; | 
 |  | 
 | VariationsSeedStore::VariationsSeedStore( | 
 |     PrefService* local_state, | 
 |     std::unique_ptr<SeedResponse> initial_seed, | 
 |     bool signature_verification_enabled, | 
 |     std::unique_ptr<VariationsSafeSeedStore> safe_seed_store, | 
 |     version_info::Channel channel, | 
 |     const base::FilePath& seed_file_dir, | 
 |     const EntropyProviders* entropy_providers, | 
 |     bool use_first_run_prefs) | 
 |     : local_state_(local_state), | 
 |       safe_seed_store_(std::move(safe_seed_store)), | 
 |       signature_verification_enabled_(signature_verification_enabled), | 
 |       use_first_run_prefs_(use_first_run_prefs), | 
 |       seed_reader_writer_( | 
 |           std::make_unique<SeedReaderWriter>(local_state, | 
 |                                              seed_file_dir, | 
 |                                              kSeedFilename, | 
 |                                              kRegularSeedFieldsPrefs, | 
 |                                              channel, | 
 |                                              entropy_providers)) { | 
 | #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS) | 
 |   if (initial_seed) | 
 |     ImportInitialSeed(std::move(initial_seed)); | 
 | #endif  // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS) | 
 | } | 
 |  | 
 | VariationsSeedStore::~VariationsSeedStore() = default; | 
 |  | 
 | bool VariationsSeedStore::LoadSeed(VariationsSeed* seed, | 
 |                                    std::string* seed_data, | 
 |                                    std::string* base64_seed_signature) { | 
 |   LoadSeedResult result = | 
 |       LoadSeedImpl(SeedType::LATEST, seed, seed_data, base64_seed_signature); | 
 |   RecordLoadSeedResult(result); | 
 |   if (result != LoadSeedResult::kSuccess) | 
 |     return false; | 
 |  | 
 |   // TODO(crbug.com/437811262): Remove after milestone M146. This code is used | 
 |   // to populate the pref with the serial number of the latest seed. This value | 
 |   // is already stored when we fetch a new seed, so we don't need to store it | 
 |   // again here. | 
 |   StoreLatestSerialNumber(seed->serial_number()); | 
 |   return true; | 
 | } | 
 |  | 
 | void VariationsSeedStore::StoreSeedData( | 
 |     base::OnceCallback<void(bool, VariationsSeed)> done_callback, | 
 |     std::string data, | 
 |     std::string base64_seed_signature, | 
 |     std::string country_code, | 
 |     base::Time date_fetched, | 
 |     bool is_delta_compressed, | 
 |     bool is_gzip_compressed, | 
 |     bool require_synchronous) { | 
 |   SCOPED_UMA_HISTOGRAM_TIMER("Variations.StoreSeed.Time"); | 
 |  | 
 |   UMA_HISTOGRAM_COUNTS_1000("Variations.StoreSeed.DataSize", | 
 |                             data.length() / 1024); | 
 |   InstanceManipulations im = { | 
 |       .gzip_compressed = is_gzip_compressed, | 
 |       .delta_compressed = is_delta_compressed, | 
 |   }; | 
 |   RecordSeedInstanceManipulations(im); | 
 |  | 
 |   // Note: SeedData is move-only, so it will be moved into a param below. | 
 |   SeedData seed_data; | 
 |   seed_data.data = std::move(data); | 
 |   seed_data.base64_seed_signature = std::move(base64_seed_signature); | 
 |   seed_data.country_code = std::move(country_code); | 
 |   seed_data.date_fetched = date_fetched; | 
 |   seed_data.is_gzip_compressed = is_gzip_compressed; | 
 |   seed_data.is_delta_compressed = is_delta_compressed; | 
 |  | 
 |   if (is_delta_compressed) { | 
 |     ReadSeedData( | 
 |         /*done_callback=*/base::BindOnce( | 
 |             &VariationsSeedStore::ProcessAndStoreSeedData, | 
 |             weak_ptr_factory_.GetWeakPtr(), std::move(done_callback), | 
 |             std::move(seed_data), require_synchronous), | 
 |         SeedType::LATEST, require_synchronous); | 
 |   } else { | 
 |     ProcessAndStoreSeedData( | 
 |         std::move(done_callback), std::move(seed_data), require_synchronous, | 
 |         SeedReaderWriter::ReadSeedDataResult{LoadSeedResult::kSuccess, "", ""}); | 
 |   } | 
 | } | 
 |  | 
 | bool VariationsSeedStore::LoadSafeSeed(VariationsSeed* seed, | 
 |                                        ClientFilterableState* client_state) { | 
 |   std::string unused_seed_data; | 
 |   std::string unused_base64_seed_signature; | 
 |   LoadSeedResult result = LoadSeedImpl(SeedType::SAFE, seed, &unused_seed_data, | 
 |                                        &unused_base64_seed_signature); | 
 |   RecordLoadSafeSeedResult(result); | 
 |   if (result != LoadSeedResult::kSuccess) | 
 |     return false; | 
 |  | 
 |   // TODO(crbug.com/40202311): While it's not immediately obvious, | 
 |   // |client_state| is not used for successfully loaded safe seeds that are | 
 |   // rejected after additional validation (expiry and future milestone). | 
 |   client_state->reference_date = | 
 |       GetTimeForStudyDateChecks(/*is_safe_seed=*/true); | 
 |   client_state->locale = safe_seed_store_->GetLocale(); | 
 |   client_state->permanent_consistency_country = | 
 |       safe_seed_store_->GetPermanentConsistencyCountry(); | 
 |   client_state->session_consistency_country = | 
 |       safe_seed_store_->GetSessionConsistencyCountry(); | 
 |   return true; | 
 | } | 
 |  | 
 | void VariationsSeedStore::StoreSafeSeed( | 
 |     base::OnceCallback<void(bool)> done_callback, | 
 |     const std::string& seed_data, | 
 |     const std::string& base64_seed_signature, | 
 |     int seed_milestone, | 
 |     const ClientFilterableState& client_state, | 
 |     base::Time seed_fetch_time) { | 
 |   ValidatedSeed seed; | 
 |   // TODO(crbug.com/40839193): See if we can avoid calling this on the UI | 
 |   // thread. | 
 |   StoreSeedResult validation_result = | 
 |       ValidateSeedBytes(seed_data, base64_seed_signature, SeedType::SAFE, | 
 |                         signature_verification_enabled_, &seed); | 
 |   if (validation_result != StoreSeedResult::kSuccess) { | 
 |     RecordStoreSafeSeedResult(validation_result); | 
 |     std::move(done_callback).Run(false); | 
 |     return; | 
 |   } | 
 |  | 
 |   auto on_validated_safe_seed_stored_cb = | 
 |       base::BindOnce(&VariationsSeedStore::OnValidatedSafeSeedStored, | 
 |                      weak_ptr_factory_.GetWeakPtr(), std::move(done_callback)); | 
 |  | 
 |   auto store_validated_safe_seed_cb = base::BindOnce( | 
 |       &VariationsSeedStore::StoreValidatedSafeSeed, | 
 |       weak_ptr_factory_.GetWeakPtr(), | 
 |       /*done_callback=*/std::move(on_validated_safe_seed_stored_cb), | 
 |       std::move(seed), seed_milestone, client_state.reference_date, | 
 |       client_state.session_consistency_country, | 
 |       client_state.permanent_consistency_country, client_state.locale, | 
 |       seed_fetch_time); | 
 |  | 
 |   // Read both seeds and call VariationsSeedStore::StoreValidatedSafeSeed() | 
 |   ReadBothSeedsData(std::move(store_validated_safe_seed_cb)); | 
 | } | 
 |  | 
 | void VariationsSeedStore::ReadBothSeedsData( | 
 |     ReadBothSeedsCallback done_callback) { | 
 |   auto two_seed_reader = | 
 |       base::MakeRefCounted<TwoSeedReader>(std::move(done_callback)); | 
 |   ReadSeedData( | 
 |       /*done_callback=*/base::BindOnce(&TwoSeedReader::OnSingleSeedRead, | 
 |                                        two_seed_reader, SeedType::SAFE), | 
 |       SeedType::SAFE, | 
 |       /*require_synchronous=*/false); | 
 |   ReadSeedData( | 
 |       /*done_callback=*/base::BindOnce(&TwoSeedReader::OnSingleSeedRead, | 
 |                                        two_seed_reader, SeedType::LATEST), | 
 |       SeedType::LATEST, | 
 |       /*require_synchronous=*/false); | 
 | } | 
 |  | 
 | void VariationsSeedStore::OnValidatedSafeSeedStored( | 
 |     base::OnceCallback<void(bool)> done_callback, | 
 |     StoreSeedResult validation_result) { | 
 |   RecordStoreSafeSeedResult(validation_result); | 
 |   std::move(done_callback).Run(validation_result == StoreSeedResult::kSuccess); | 
 | } | 
 |  | 
 | base::Time VariationsSeedStore::GetLatestSeedFetchTime() const { | 
 |   return seed_reader_writer_->GetSeedData().client_fetch_time; | 
 | } | 
 |  | 
 | base::Time VariationsSeedStore::GetSafeSeedFetchTime() const { | 
 |   return safe_seed_store_->GetFetchTime(); | 
 | } | 
 |  | 
 | int VariationsSeedStore::GetLatestMilestone() const { | 
 |   return seed_reader_writer_->GetSeedData().milestone; | 
 | } | 
 |  | 
 | int VariationsSeedStore::GetSafeSeedMilestone() const { | 
 |   return safe_seed_store_->GetMilestone(); | 
 | } | 
 |  | 
 | base::Time VariationsSeedStore::GetLatestTimeForStudyDateChecks() | 
 |     const { | 
 |   return seed_reader_writer_->GetSeedData().seed_date; | 
 | } | 
 |  | 
 | base::Time VariationsSeedStore::GetSafeSeedTimeForStudyDateChecks() const { | 
 |   return safe_seed_store_->GetTimeForStudyDateChecks(); | 
 | } | 
 |  | 
 | base::Time VariationsSeedStore::GetTimeForStudyDateChecks(bool is_safe_seed) { | 
 |   base::Time seed_date = is_safe_seed ? GetSafeSeedTimeForStudyDateChecks() | 
 |                                       : GetLatestTimeForStudyDateChecks(); | 
 |   const base::Time build_time = base::GetBuildTime(); | 
 |  | 
 |   // Use the build time for date checks if either the seed date is unknown or | 
 |   // the build time is newer than the seed date. | 
 |   if (seed_date.is_null() || seed_date < build_time) { | 
 |     return build_time; | 
 |   } | 
 |   return seed_date; | 
 | } | 
 |  | 
 | void VariationsSeedStore::RecordLastFetchTime(base::Time fetch_time) { | 
 |   CHECK(!fetch_time.is_null()) << "Can't record null fetch time."; | 
 |   seed_reader_writer_->SetFetchTime(fetch_time); | 
 |   // If the latest and safe seeds are identical, update the fetch time for the | 
 |   // safe seed as well. | 
 |   if (seed_reader_writer_->IsIdenticalToSafeSeedSentinel()) { | 
 |     safe_seed_store_->SetFetchTime(fetch_time); | 
 |   } | 
 | } | 
 |  | 
 | void VariationsSeedStore::UpdateSeedDateAndLogDayChange( | 
 |     base::Time server_date_fetched) { | 
 |   LogSeedDayChange(server_date_fetched); | 
 |   seed_reader_writer_->SetSeedDate(server_date_fetched); | 
 | } | 
 |  | 
 | void VariationsSeedStore::LogSeedDayChange( | 
 |     base::Time server_date_fetched) { | 
 |   UpdateSeedDateResult result = UpdateSeedDateResult::NO_OLD_DATE; | 
 |   const base::Time stored_date = seed_reader_writer_->GetSeedData().seed_date; | 
 |   if (!stored_date.is_null()) { | 
 |     result = GetSeedDateChangeState(server_date_fetched, stored_date); | 
 |   } | 
 |  | 
 |   UMA_HISTOGRAM_ENUMERATION("Variations.SeedDateChange", result, | 
 |                             UpdateSeedDateResult::ENUM_SIZE); | 
 | } | 
 |  | 
 | const std::string& VariationsSeedStore::GetLatestSerialNumber() { | 
 |   return local_state_->GetString(prefs::kVariationsSeedSerialNumber); | 
 | } | 
 |  | 
 | std::string VariationsSeedStore::GetLatestCountry() { | 
 |   return std::string(seed_reader_writer_->GetSeedData().session_country_code); | 
 | } | 
 |  | 
 | std::string VariationsSeedStore::GetPermanentConsistencyCountry() { | 
 |   return std::string(seed_reader_writer_->GetSeedData().permanent_country_code); | 
 | } | 
 |  | 
 | std::string VariationsSeedStore::GetPermanentConsistencyVersion() { | 
 |   return std::string( | 
 |       seed_reader_writer_->GetSeedData().permanent_country_version); | 
 | } | 
 |  | 
 | void VariationsSeedStore::ClearPermanentConsistencyCountryAndVersion() { | 
 |   seed_reader_writer_->ClearPermanentConsistencyCountryAndVersion(); | 
 | } | 
 |  | 
 | void VariationsSeedStore::SetPermanentConsistencyCountryAndVersion( | 
 |     const std::string_view country, | 
 |     const std::string_view version) { | 
 |   seed_reader_writer_->SetPermanentConsistencyCountryAndVersion(country, | 
 |                                                                 version); | 
 | } | 
 |  | 
 | // static | 
 | void VariationsSeedStore::RegisterPrefs(PrefRegistrySimple* registry) { | 
 |   // Regular seed prefs: | 
 |   registry->RegisterStringPref(prefs::kVariationsCompressedSeed, std::string()); | 
 |   registry->RegisterStringPref(prefs::kVariationsCountry, std::string()); | 
 |   registry->RegisterTimePref(prefs::kVariationsLastFetchTime, base::Time()); | 
 |   registry->RegisterIntegerPref(prefs::kVariationsSeedMilestone, 0); | 
 |   registry->RegisterTimePref(prefs::kVariationsSeedDate, base::Time()); | 
 |   registry->RegisterStringPref(prefs::kVariationsSeedSignature, std::string()); | 
 |   // This preference keeps track of the country code used to filter | 
 |   // permanent-consistency studies. | 
 |   registry->RegisterListPref(prefs::kVariationsPermanentConsistencyCountry); | 
 |   registry->RegisterStringPref(prefs::kVariationsSeedSerialNumber, | 
 |                                std::string()); | 
 |  | 
 |   VariationsSafeSeedStoreLocalState::RegisterPrefs(registry); | 
 | } | 
 |  | 
 | // static | 
 | VerifySignatureResult VariationsSeedStore::VerifySeedSignatureForTesting( | 
 |     const std::string& seed_bytes, | 
 |     const std::string& base64_seed_signature) { | 
 |   return VerifySeedSignature(seed_bytes, base64_seed_signature); | 
 | } | 
 |  | 
 | VariationsSeedStore::SeedData::SeedData() = default; | 
 | VariationsSeedStore::SeedData::~SeedData() = default; | 
 | VariationsSeedStore::SeedData::SeedData(VariationsSeedStore::SeedData&& other) = | 
 |     default; | 
 | VariationsSeedStore::SeedData& VariationsSeedStore::SeedData::operator=( | 
 |     VariationsSeedStore::SeedData&& other) = default; | 
 |  | 
 | VariationsSeedStore::SeedProcessingResult::SeedProcessingResult( | 
 |     SeedData seed_data, | 
 |     StoreSeedResult result) | 
 |     : seed_data(std::move(seed_data)), result(result) {} | 
 | VariationsSeedStore::SeedProcessingResult::~SeedProcessingResult() = default; | 
 | VariationsSeedStore::SeedProcessingResult::SeedProcessingResult( | 
 |     VariationsSeedStore::SeedProcessingResult&& other) = default; | 
 | VariationsSeedStore::SeedProcessingResult& | 
 | VariationsSeedStore::SeedProcessingResult::operator=( | 
 |     VariationsSeedStore::SeedProcessingResult&& other) = default; | 
 |  | 
 | // It is intentional that country-related prefs are retained for regular seeds | 
 | // and cleared for safe seeds. | 
 | // | 
 | // For regular seeds, the prefs are kept for two reasons. First, it's better to | 
 | // have some idea of a country recently associated with the device. Second, some | 
 | // past, country-gated launches started relying on the VariationsService- | 
 | // provided country when they retired server-side configs. | 
 | // | 
 | // The safe seed prefs are needed to correctly apply a safe seed, so if the safe | 
 | // seed is cleared, there's no reason to retain them as they may be incorrect | 
 | // for the next safe seed. | 
 | void VariationsSeedStore::ClearPrefs(SeedType seed_type) { | 
 |   if (seed_type == SeedType::LATEST) { | 
 |     local_state_->ClearPref(prefs::kVariationsSeedSerialNumber); | 
 |     // Seed and other related information is cleared by the SeedReaderWriter. | 
 |     seed_reader_writer_->ClearSeedInfo(); | 
 |     return; | 
 |   } | 
 |  | 
 |   DCHECK_EQ(seed_type, SeedType::SAFE); | 
 |   safe_seed_store_->ClearState(); | 
 | } | 
 |  | 
 | #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS) | 
 | void VariationsSeedStore::ImportInitialSeed( | 
 |     std::unique_ptr<SeedResponse> initial_seed) { | 
 |   if (initial_seed->data.empty()) { | 
 |     // Note: This is an expected case on non-first run starts. | 
 |     RecordFirstRunSeedImportResult( | 
 |         FirstRunSeedImportResult::kFailNoFirstRunSeed); | 
 |     return; | 
 |   } | 
 |  | 
 |   if (initial_seed->date.is_null()) { | 
 |     RecordFirstRunSeedImportResult( | 
 |         FirstRunSeedImportResult::kFailInvalidResponseDate); | 
 |     LOG(WARNING) << "Missing response date"; | 
 |     return; | 
 |   } | 
 |  | 
 |   auto done_callback = | 
 |       base::BindOnce([](bool store_success, VariationsSeed seed) { | 
 |         if (store_success) { | 
 |           RecordFirstRunSeedImportResult(FirstRunSeedImportResult::kSuccess); | 
 |         } else { | 
 |           RecordFirstRunSeedImportResult( | 
 |               FirstRunSeedImportResult::kFailStoreFailed); | 
 |           LOG(WARNING) << "First run variations seed is invalid."; | 
 |         } | 
 |       }); | 
 |   StoreSeedData(std::move(done_callback), std::move(initial_seed->data), | 
 |                 std::move(initial_seed->signature), | 
 |                 std::move(initial_seed->country), initial_seed->date, | 
 |                 /*is_delta_compressed=*/false, initial_seed->is_gzip_compressed, | 
 |                 /*require_synchronous=*/true); | 
 | } | 
 | #endif  // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS) | 
 |  | 
 | // static | 
 | std::optional<std::string> VariationsSeedStore::SeedBytesToCompressedBase64Seed( | 
 |     const std::string& seed_bytes) { | 
 |   if (seed_bytes.empty()) { | 
 |     return std::nullopt; | 
 |   } | 
 |  | 
 |   std::string compressed_seed_data; | 
 |   if (!compression::GzipCompress(seed_bytes, &compressed_seed_data)) { | 
 |     return std::nullopt; | 
 |   } | 
 |  | 
 |   return base::Base64Encode(compressed_seed_data); | 
 | } | 
 |  | 
 | SeedReaderWriter* VariationsSeedStore::GetSeedReaderWriterForTesting() { | 
 |   return seed_reader_writer_.get(); | 
 | } | 
 |  | 
 | void VariationsSeedStore::SetSeedReaderWriterForTesting( | 
 |     std::unique_ptr<SeedReaderWriter> seed_reader_writer) { | 
 |   seed_reader_writer_ = std::move(seed_reader_writer); | 
 | } | 
 |  | 
 | SeedReaderWriter* VariationsSeedStore::GetSafeSeedReaderWriterForTesting() { | 
 |   return safe_seed_store_->GetSeedReaderWriterForTesting();  // IN-TEST | 
 | } | 
 |  | 
 | void VariationsSeedStore::SetSafeSeedReaderWriterForTesting( | 
 |     std::unique_ptr<SeedReaderWriter> seed_reader_writer) { | 
 |   safe_seed_store_->SetSeedReaderWriterForTesting(  // IN-TEST | 
 |       std::move(seed_reader_writer)); | 
 | } | 
 |  | 
 | LoadSeedResult VariationsSeedStore::VerifyAndParseSeed( | 
 |     VariationsSeed* seed, | 
 |     const std::string& seed_data, | 
 |     const std::string& base64_seed_signature, | 
 |     std::optional<VerifySignatureResult>* verify_signature_result) { | 
 |   // TODO(crbug.com/40228403): get rid of |signature_verification_enabled_| and | 
 |   // only support switches::kAcceptEmptySeedSignatureForTesting. | 
 |   if (signature_verification_enabled_ && | 
 |       !AcceptEmptySeedSignatureForTesting(base64_seed_signature)) { | 
 |     *verify_signature_result = | 
 |         VerifySeedSignature(seed_data, base64_seed_signature); | 
 |     if (*verify_signature_result != VerifySignatureResult::VALID_SIGNATURE) { | 
 |       return LoadSeedResult::kInvalidSignature; | 
 |     } | 
 |   } | 
 |  | 
 |   if (!seed->ParseFromString(seed_data)) { | 
 |     return LoadSeedResult::kCorruptProtobuf; | 
 |   } | 
 |  | 
 |   return LoadSeedResult::kSuccess; | 
 | } | 
 |  | 
 | void VariationsSeedStore::StoreLatestSerialNumber( | 
 |     std::string_view serial_number) { | 
 |   local_state_->SetString(prefs::kVariationsSeedSerialNumber, serial_number); | 
 | } | 
 |  | 
 | LoadSeedResult VariationsSeedStore::LoadSeedImpl( | 
 |     SeedType seed_type, | 
 |     VariationsSeed* seed, | 
 |     std::string* seed_data, | 
 |     std::string* base64_seed_signature) { | 
 |   LoadSeedResult read_result = | 
 |       ReadSeedData(seed_type, seed_data, base64_seed_signature); | 
 |   if (read_result != LoadSeedResult::kSuccess) { | 
 |     return read_result; | 
 |   } | 
 |  | 
 |   std::optional<VerifySignatureResult> verify_signature_result; | 
 |   LoadSeedResult result = VerifyAndParseSeed( | 
 |       seed, *seed_data, *base64_seed_signature, &verify_signature_result); | 
 |   if (verify_signature_result.has_value()) { | 
 |     VerifySignatureResult signature_result = verify_signature_result.value(); | 
 |     if (seed_type == SeedType::LATEST) { | 
 |       UMA_HISTOGRAM_ENUMERATION("Variations.LoadSeedSignature", | 
 |                                 signature_result, | 
 |                                 VerifySignatureResult::ENUM_SIZE); | 
 |     } else { | 
 |       UMA_HISTOGRAM_ENUMERATION( | 
 |           "Variations.SafeMode.LoadSafeSeed.SignatureValidity", | 
 |           signature_result, VerifySignatureResult::ENUM_SIZE); | 
 |     } | 
 |     if (signature_result != VerifySignatureResult::VALID_SIGNATURE) { | 
 |       ClearPrefs(seed_type); | 
 |     } | 
 |   } | 
 |  | 
 |   if (result == LoadSeedResult::kCorruptProtobuf) { | 
 |     ClearPrefs(seed_type); | 
 |   } | 
 |  | 
 |   return result; | 
 | } | 
 |  | 
 | LoadSeedResult VariationsSeedStore::ReadSeedData( | 
 |     SeedType seed_type, | 
 |     std::string* seed_data, | 
 |     std::string* base64_seed_signature) { | 
 |   LoadSeedResult load_seed_result = | 
 |       seed_type == SeedType::LATEST | 
 |           ? seed_reader_writer_->ReadSeedData(seed_data, base64_seed_signature) | 
 |           : safe_seed_store_->ReadSeedData(seed_data, base64_seed_signature); | 
 |   if (load_seed_result != LoadSeedResult::kSuccess) { | 
 |     ClearPrefs(seed_type); | 
 |     return load_seed_result; | 
 |   } | 
 |   // As a space optimization, the latest seed might not be stored directly, but | 
 |   // rather aliased to the safe seed. | 
 |   if (seed_type == SeedType::LATEST && | 
 |       seed_reader_writer_->IsIdenticalToSafeSeedSentinel()) { | 
 |     return ReadSeedData(SeedType::SAFE, seed_data, base64_seed_signature); | 
 |   } | 
 |   return LoadSeedResult::kSuccess; | 
 | } | 
 |  | 
 | void VariationsSeedStore::ReadSeedData( | 
 |     SeedReaderWriter::ReadSeedDataCallback done_callback, | 
 |     SeedType seed_type, | 
 |     bool require_synchronous) { | 
 |   auto cb = base::BindOnce( | 
 |       &VariationsSeedStore::CheckReadSeedDataResultAndRunCallback, | 
 |       weak_ptr_factory_.GetWeakPtr(), std::move(done_callback), seed_type, | 
 |       require_synchronous); | 
 |   if (require_synchronous) { | 
 |     std::string seed_data; | 
 |     std::string base64_seed_signature; | 
 |     auto result = ReadSeedData(seed_type, &seed_data, &base64_seed_signature); | 
 |     std::move(cb).Run(SeedReaderWriter::ReadSeedDataResult{ | 
 |         result, std::move(seed_data), std::move(base64_seed_signature)}); | 
 |     return; | 
 |   } | 
 |   if (seed_type == SeedType::LATEST && | 
 |       !seed_reader_writer_->IsIdenticalToSafeSeedSentinel()) { | 
 |     seed_reader_writer_->ReadSeedData(std::move(cb)); | 
 |   } else { | 
 |     safe_seed_store_->ReadSeedData(std::move(cb)); | 
 |   } | 
 | } | 
 |  | 
 | void VariationsSeedStore::CheckReadSeedDataResultAndRunCallback( | 
 |     SeedReaderWriter::ReadSeedDataCallback done_callback, | 
 |     SeedType seed_type, | 
 |     bool require_synchronous, | 
 |     SeedReaderWriter::ReadSeedDataResult load_seed_result) { | 
 |   if (load_seed_result.result != LoadSeedResult::kSuccess) { | 
 |     ClearPrefs(seed_type); | 
 |   } | 
 |   std::move(done_callback).Run(std::move(load_seed_result)); | 
 | } | 
 |  | 
 | void VariationsSeedStore::OnSeedDataProcessed( | 
 |     base::OnceCallback<void(bool, VariationsSeed)> done_callback, | 
 |     bool require_synchronous, | 
 |     SeedProcessingResult result) { | 
 |   if (result.result != StoreSeedResult::kSuccess) { | 
 |     RecordStoreSeedResult(result.result); | 
 |     std::move(done_callback).Run(false, VariationsSeed()); | 
 |     return; | 
 |   } | 
 |  | 
 |   if (result.validate_result != StoreSeedResult::kSuccess) { | 
 |     RecordStoreSeedResult(result.validate_result); | 
 |     if (result.seed_data.is_delta_compressed) | 
 |       RecordStoreSeedResult(StoreSeedResult::kFailedDeltaStore); | 
 |     std::move(done_callback).Run(false, VariationsSeed()); | 
 |     return; | 
 |   } | 
 |  | 
 |   SeedReaderWriter::ReadSeedDataCallback store_validated_seed_cb = | 
 |       base::BindOnce(&VariationsSeedStore::StoreValidatedSeed, | 
 |                      weak_ptr_factory_.GetWeakPtr(), std::move(done_callback), | 
 |                      std::move(result.validated), result.seed_data.country_code, | 
 |                      result.seed_data.date_fetched, require_synchronous); | 
 |   ReadSeedData(/*done_callback=*/std::move(store_validated_seed_cb), | 
 |                SeedType::SAFE, require_synchronous); | 
 | } | 
 |  | 
 | void VariationsSeedStore::ProcessAndStoreSeedData( | 
 |     base::OnceCallback<void(bool, VariationsSeed)> done_callback, | 
 |     SeedData seed_data, | 
 |     bool require_synchronous, | 
 |     SeedReaderWriter::ReadSeedDataResult read_result) { | 
 |   if (read_result.result != LoadSeedResult::kSuccess) { | 
 |     RecordStoreSeedResult(StoreSeedResult::kFailedDeltaReadSeed); | 
 |     std::move(done_callback).Run(false, VariationsSeed()); | 
 |     return; | 
 |   } | 
 |   seed_data.existing_seed_bytes = std::move(read_result.seed_data); | 
 |   if (require_synchronous) { | 
 |     SeedProcessingResult result = | 
 |         ProcessSeedData(signature_verification_enabled_, std::move(seed_data)); | 
 |     OnSeedDataProcessed(std::move(done_callback), require_synchronous, | 
 |                         std::move(result)); | 
 |   } else { | 
 |     base::ThreadPool::PostTaskAndReplyWithResult( | 
 |         FROM_HERE, {base::TaskPriority::BEST_EFFORT}, | 
 |         base::BindOnce(&VariationsSeedStore::ProcessSeedData, | 
 |                        signature_verification_enabled_, std::move(seed_data)), | 
 |         base::BindOnce(&VariationsSeedStore::OnSeedDataProcessed, | 
 |                        weak_ptr_factory_.GetWeakPtr(), std::move(done_callback), | 
 |                        require_synchronous)); | 
 |   } | 
 | } | 
 |  | 
 | void VariationsSeedStore::StoreValidatedSeed( | 
 |     base::OnceCallback<void(bool, VariationsSeed)> done_callback, | 
 |     ValidatedSeed seed, | 
 |     std::string country_code, | 
 |     base::Time date_fetched, | 
 |     bool require_synchronous, | 
 |     SeedReaderWriter::ReadSeedDataResult safe_seed_read_result) { | 
 | #if BUILDFLAG(IS_ANDROID) | 
 |   // If currently we do not have any stored seed, then we mark seed storing as | 
 |   // successful on the Java side to avoid repeated seed fetches. | 
 |   if (use_first_run_prefs_) { | 
 |     ReadSeedData(/*done_callback=*/base::BindOnce( | 
 |                      &MarkVariationsSeedAsStoredIfEmptySeed), | 
 |                  SeedType::LATEST, require_synchronous); | 
 |   } | 
 | #endif  // BUILDFLAG(IS_ANDROID) | 
 |  | 
 |   int milestone = version_info::GetMajorVersionNumberAsInt(); | 
 |  | 
 |   LogSeedDayChange(date_fetched); | 
 |  | 
 |   // As a space optimization, store an alias to the safe seed if the contents | 
 |   // are identical. | 
 |   std::string_view seed_data; | 
 |   if (safe_seed_read_result.result == LoadSeedResult::kSuccess && | 
 |       safe_seed_read_result.seed_data == seed.seed_data) { | 
 |     seed_data = kIdenticalToSafeSeedSentinel; | 
 |   } else { | 
 |     seed_data = seed.seed_data; | 
 |   } | 
 |   StoreSeedResult result = | 
 |       seed_reader_writer_->StoreValidatedSeedInfo(ValidatedSeedInfo{ | 
 |           .seed_data = seed_data, | 
 |           .signature = seed.base64_seed_signature, | 
 |           .milestone = milestone, | 
 |           .seed_date = date_fetched, | 
 |           .client_fetch_time = base::Time::Now(), | 
 |           .session_country_code = country_code, | 
 |       }); | 
 |   if (result == StoreSeedResult::kSuccess) { | 
 |     StoreLatestSerialNumber(seed.parsed.serial_number()); | 
 |   } | 
 |   RecordStoreSeedResult(result); | 
 |   std::move(done_callback).Run(true, std::move(seed.parsed)); | 
 | } | 
 |  | 
 | void VariationsSeedStore::StoreValidatedSafeSeed( | 
 |     base::OnceCallback<void(StoreSeedResult)> done_callback, | 
 |     ValidatedSeed seed, | 
 |     int seed_milestone, | 
 |     base::Time reference_date, | 
 |     std::string session_consistency_country, | 
 |     std::string permanent_consistency_country, | 
 |     std::string locale, | 
 |     base::Time seed_fetch_time, | 
 |     SeedReaderWriter::ReadSeedDataResult safe_seed_read_result, | 
 |     SeedReaderWriter::ReadSeedDataResult latest_seed_read_result) { | 
 |   // Before updating the safe seed, update the latest seed if the latest | 
 |   // seed's value is |kIdenticalToSafeSeedSentinel|. | 
 |   // | 
 |   // It's theoretically possible for the client to be in the following state: | 
 |   // 1. The client has safe seed A. | 
 |   // 2. The client is applying seed B. In other words, seed B was the latest | 
 |   //    seed when Chrome was started. | 
 |   // 3. The client has just successfully fetched a new latest seed that | 
 |   //    happens to be seed A—perhaps due to a rollback. In this case, | 
 |   //    |kIdenticalToSafeSeedSentinel| is stored as the latest seed value to | 
 |   //    avoid duplicating seed A in storage. | 
 |   // 4. The client is promoting seed B to safe seed. | 
 |   const StoredSeed latest_seed = seed_reader_writer_->GetSeedData(); | 
 |   if (safe_seed_read_result.result == LoadSeedResult::kSuccess && | 
 |       safe_seed_read_result.seed_data != seed.seed_data && | 
 |       seed_reader_writer_->IsIdenticalToSafeSeedSentinel()) { | 
 |     StoreSeedResult store_result = | 
 |         seed_reader_writer_->StoreValidatedSeedInfo(ValidatedSeedInfo{ | 
 |             .seed_data = safe_seed_read_result.seed_data, | 
 |             .signature = latest_seed.signature, | 
 |             .milestone = latest_seed.milestone, | 
 |             .seed_date = latest_seed.seed_date, | 
 |             .client_fetch_time = latest_seed.client_fetch_time, | 
 |         }); | 
 |     if (store_result != StoreSeedResult::kSuccess) { | 
 |       std::move(done_callback).Run(store_result); | 
 |       return; | 
 |     } | 
 |   } | 
 |  | 
 |   { | 
 |     StoreSeedResult store_result = | 
 |         safe_seed_store_->SetCompressedSeed(ValidatedSeedInfo{ | 
 |             .seed_data = seed.seed_data, | 
 |             .signature = seed.base64_seed_signature, | 
 |             .milestone = seed_milestone, | 
 |             .seed_date = reference_date, | 
 |             .client_fetch_time = seed_fetch_time, | 
 |             .session_country_code = session_consistency_country, | 
 |             .permanent_country_code = permanent_consistency_country, | 
 |             // The permanent version is not stored in the safe seed, only the | 
 |             // country. | 
 |             .permanent_country_version = "", | 
 |         }); | 
 |     if (store_result != StoreSeedResult::kSuccess) { | 
 |       std::move(done_callback).Run(store_result); | 
 |       return; | 
 |     } | 
 |   } | 
 |   safe_seed_store_->SetLocale(locale); | 
 |  | 
 |   // As a space optimization, overwrite the stored latest seed data with an | 
 |   // alias to the safe seed, if they are identical. | 
 |   if (latest_seed_read_result.result == LoadSeedResult::kSuccess && | 
 |       latest_seed_read_result.seed_data == seed.seed_data) { | 
 |     StoreSeedResult store_result = | 
 |         seed_reader_writer_->StoreValidatedSeedInfo(ValidatedSeedInfo{ | 
 |             .seed_data = kIdenticalToSafeSeedSentinel, | 
 |             .signature = latest_seed.signature, | 
 |             .milestone = latest_seed.milestone, | 
 |             .seed_date = latest_seed.seed_date, | 
 |             .client_fetch_time = latest_seed.client_fetch_time, | 
 |         }); | 
 |     if (store_result != StoreSeedResult::kSuccess) { | 
 |       std::move(done_callback).Run(store_result); | 
 |       return; | 
 |     } | 
 |  | 
 |     // Moreover, in this case, the last fetch time for the safe seed should | 
 |     // match the latest seed's. | 
 |     safe_seed_store_->SetFetchTime(latest_seed.client_fetch_time); | 
 |   } | 
 |   std::move(done_callback).Run(StoreSeedResult::kSuccess); | 
 | } | 
 |  | 
 | // static | 
 | VariationsSeedStore::SeedProcessingResult VariationsSeedStore::ProcessSeedData( | 
 |     bool signature_verification_enabled, | 
 |     SeedData seed_data) { | 
 |   const std::string* data = &seed_data.data; | 
 |  | 
 |   // If the data is gzip compressed, first uncompress it. | 
 |   std::string ungzipped_data; | 
 |   if (seed_data.is_gzip_compressed) { | 
 |     StoreSeedResult result = Uncompress(*data, &ungzipped_data); | 
 |     if (result != StoreSeedResult::kSuccess) { | 
 |       return {std::move(seed_data), result}; | 
 |     } | 
 |     data = &ungzipped_data; | 
 |   } | 
 |  | 
 |   // If the data is delta-compressed, apply the delta patch. | 
 |   std::string patched_data; | 
 |   if (seed_data.is_delta_compressed) { | 
 |     DCHECK(!seed_data.existing_seed_bytes.empty()); | 
 |     if (!ApplyDeltaPatch(seed_data.existing_seed_bytes, *data, &patched_data)) { | 
 |       return {std::move(seed_data), StoreSeedResult::kFailedDeltaApply}; | 
 |     } | 
 |     data = &patched_data; | 
 |   } | 
 |  | 
 |   ValidatedSeed validated; | 
 |   auto validate_result = VariationsSeedStore::ValidateSeedBytes( | 
 |       *data, seed_data.base64_seed_signature, | 
 |       VariationsSeedStore::SeedType::LATEST, signature_verification_enabled, | 
 |       &validated); | 
 |   // Important, this must come after the above call as `data` can point to a | 
 |   // member of `seed_data` which is being moved. | 
 |   SeedProcessingResult result(std::move(seed_data), StoreSeedResult::kSuccess); | 
 |   result.validate_result = validate_result; | 
 |   result.validated = std::move(validated); | 
 |   return result; | 
 | } | 
 |  | 
 | // static | 
 | StoreSeedResult VariationsSeedStore::ValidateSeedBytes( | 
 |     const std::string& seed_bytes, | 
 |     const std::string& base64_seed_signature, | 
 |     SeedType seed_type, | 
 |     bool signature_verification_enabled, | 
 |     ValidatedSeed* result) { | 
 |   DCHECK(result); | 
 |   if (seed_bytes.empty()) { | 
 |     return StoreSeedResult::kFailedEmptyGzipContents; | 
 |   } | 
 |  | 
 |   // Only store the seed data if it parses correctly. | 
 |   VariationsSeed seed; | 
 |   if (!seed.ParseFromString(seed_bytes)) { | 
 |     return StoreSeedResult::kFailedParse; | 
 |   } | 
 |  | 
 |   // TODO(crbug.com/40228403): get rid of |signature_verification_enabled| and | 
 |   // only support switches::kAcceptEmptySeedSignatureForTesting. | 
 |   if (signature_verification_enabled && | 
 |       !AcceptEmptySeedSignatureForTesting(base64_seed_signature)) { | 
 |     const VerifySignatureResult verify_result = | 
 |         VerifySeedSignature(seed_bytes, base64_seed_signature); | 
 |     switch (seed_type) { | 
 |       case SeedType::LATEST: | 
 |         UMA_HISTOGRAM_ENUMERATION("Variations.StoreSeedSignature", | 
 |                                   verify_result, | 
 |                                   VerifySignatureResult::ENUM_SIZE); | 
 |         break; | 
 |       case SeedType::SAFE: | 
 |         UMA_HISTOGRAM_ENUMERATION( | 
 |             "Variations.SafeMode.StoreSafeSeed.SignatureValidity", | 
 |             verify_result, VerifySignatureResult::ENUM_SIZE); | 
 |         break; | 
 |     } | 
 |  | 
 |     if (verify_result != VerifySignatureResult::VALID_SIGNATURE) | 
 |       return StoreSeedResult::kFailedSignature; | 
 |   } | 
 |  | 
 |   result->seed_data = seed_bytes; | 
 |   result->base64_seed_signature = base64_seed_signature; | 
 |   result->parsed.Swap(&seed); | 
 |   return StoreSeedResult::kSuccess; | 
 | } | 
 |  | 
 | // static | 
 | bool VariationsSeedStore::ApplyDeltaPatch(const std::string& existing_data, | 
 |                                           const std::string& patch, | 
 |                                           std::string* output) { | 
 |   output->clear(); | 
 |  | 
 |   google::protobuf::io::CodedInputStream in( | 
 |       reinterpret_cast<const uint8_t*>(patch.data()), patch.length()); | 
 |   // Temporary string declared outside the loop so it can be re-used between | 
 |   // different iterations (rather than allocating new ones). | 
 |   std::string temp; | 
 |  | 
 |   const uint32_t existing_data_size = | 
 |       static_cast<uint32_t>(existing_data.size()); | 
 |   while (in.CurrentPosition() != static_cast<int>(patch.length())) { | 
 |     uint32_t value; | 
 |     if (!in.ReadVarint32(&value)) { | 
 |       return false; | 
 |     } | 
 |  | 
 |     if (value != 0) { | 
 |       // A non-zero value indicates the number of bytes to copy from the patch | 
 |       // stream to the output. | 
 |  | 
 |       // No need to guard against bad data (i.e. very large |value|) because the | 
 |       // call below will fail if |value| is greater than the size of the patch. | 
 |       if (!in.ReadString(&temp, value)) { | 
 |         return false; | 
 |       } | 
 |       output->append(temp); | 
 |     } else { | 
 |       // Otherwise, when it's zero, it indicates that it's followed by a pair of | 
 |       // numbers - |offset| and |length| that specify a range of data to copy | 
 |       // from |existing_data|. | 
 |       uint32_t offset; | 
 |       uint32_t length; | 
 |       if (!in.ReadVarint32(&offset) || !in.ReadVarint32(&length)) { | 
 |         return false; | 
 |       } | 
 |  | 
 |       // Check for |offset + length| being out of range and for overflow. | 
 |       base::CheckedNumeric<uint32_t> end_offset(offset); | 
 |       end_offset += length; | 
 |       if (!end_offset.IsValid() || | 
 |           end_offset.ValueOrDie() > existing_data_size) { | 
 |         return false; | 
 |       } | 
 |       output->append(existing_data, offset, length); | 
 |     } | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | }  // namespace variations |