| Index: chrome/browser/metrics/variations/variations_seed_store.cc
|
| diff --git a/chrome/browser/metrics/variations/variations_seed_store.cc b/chrome/browser/metrics/variations/variations_seed_store.cc
|
| index 6e83ab6bcfc8d25a3ed1723ac434431617503e0d..b4e51a8c9c246b613bb84f7ad92a1bdf199815d7 100644
|
| --- a/chrome/browser/metrics/variations/variations_seed_store.cc
|
| +++ b/chrome/browser/metrics/variations/variations_seed_store.cc
|
| @@ -6,6 +6,7 @@
|
|
|
| #include "base/base64.h"
|
| #include "base/metrics/histogram_macros.h"
|
| +#include "base/numerics/safe_math.h"
|
| #include "base/prefs/pref_registry_simple.h"
|
| #include "base/prefs/pref_service.h"
|
| #include "base/sha1.h"
|
| @@ -14,6 +15,7 @@
|
| #include "components/metrics/compression_utils.h"
|
| #include "components/variations/proto/variations_seed.pb.h"
|
| #include "crypto/signature_verifier.h"
|
| +#include "third_party/protobuf/src/google/protobuf/io/coded_stream.h"
|
|
|
| namespace chrome_variations {
|
|
|
| @@ -82,10 +84,17 @@ enum VariationsSeedStoreResult {
|
| VARIATIONS_SEED_STORE_FAILED_PARSE,
|
| VARIATIONS_SEED_STORE_FAILED_SIGNATURE,
|
| VARIATIONS_SEED_STORE_FAILED_GZIP,
|
| + // DELTA_COUNT is not so much a result of the seed store, but rather counting
|
| + // the number of delta-compressed seeds the SeedStore() function saw. Kept in
|
| + // the same histogram for convenience of comparing against the other values.
|
| + VARIATIONS_SEED_STORE_DELTA_COUNT,
|
| + VARIATIONS_SEED_STORE_FAILED_DELTA_READ_SEED,
|
| + VARIATIONS_SEED_STORE_FAILED_DELTA_APPLY,
|
| + VARIATIONS_SEED_STORE_FAILED_DELTA_STORE,
|
| VARIATIONS_SEED_STORE_RESULT_ENUM_SIZE,
|
| };
|
|
|
| -void RecordVariationsSeedStoreHistogram(VariationsSeedStoreResult result) {
|
| +void RecordSeedStoreHistogram(VariationsSeedStoreResult result) {
|
| UMA_HISTOGRAM_ENUMERATION("Variations.SeedStoreResult", result,
|
| VARIATIONS_SEED_STORE_RESULT_ENUM_SIZE);
|
| }
|
| @@ -131,7 +140,7 @@ VariationsSeedDateChangeState GetSeedDateChangeState(
|
| } // namespace
|
|
|
| VariationsSeedStore::VariationsSeedStore(PrefService* local_state)
|
| - : local_state_(local_state) {
|
| + : local_state_(local_state), seed_has_country_code_(false) {
|
| }
|
|
|
| VariationsSeedStore::~VariationsSeedStore() {
|
| @@ -174,24 +183,78 @@ bool VariationsSeedStore::LoadSeed(variations::VariationsSeed* seed) {
|
| local_state_->SetString(prefs::kVariationsCountry, seed->country_code());
|
| }
|
| variations_serial_number_ = seed->serial_number();
|
| + seed_has_country_code_ = seed->has_country_code();
|
| RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_NOT_EMPTY);
|
| return true;
|
| }
|
|
|
| bool VariationsSeedStore::StoreSeedData(
|
| + const std::string& data,
|
| + const std::string& base64_seed_signature,
|
| + const std::string& country_code,
|
| + const base::Time& date_fetched,
|
| + bool is_delta_compressed,
|
| + variations::VariationsSeed* parsed_seed) {
|
| + if (!is_delta_compressed) {
|
| + const bool result =
|
| + StoreSeedDataNoDelta(data, base64_seed_signature, country_code,
|
| + date_fetched, parsed_seed);
|
| + if (result) {
|
| + UMA_HISTOGRAM_COUNTS_1000("Variations.StoreSeed.Size",
|
| + data.length() / 1024);
|
| + }
|
| + return result;
|
| + }
|
| +
|
| + // If the data is delta compressed, first decode it.
|
| + DCHECK(invalid_base64_signature_.empty());
|
| + RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_DELTA_COUNT);
|
| +
|
| + std::string existing_seed_data;
|
| + std::string updated_seed_data;
|
| + if (!ReadSeedData(&existing_seed_data)) {
|
| + RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_DELTA_READ_SEED);
|
| + return false;
|
| + }
|
| + if (!ApplyDeltaPatch(existing_seed_data, data, &updated_seed_data)) {
|
| + RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_DELTA_APPLY);
|
| + return false;
|
| + }
|
| +
|
| + const bool result =
|
| + StoreSeedDataNoDelta(updated_seed_data, base64_seed_signature,
|
| + country_code, date_fetched, parsed_seed);
|
| + if (result) {
|
| + // Note: |updated_seed_data.length()| is guaranteed to be non-zero, else
|
| + // result would be false.
|
| + int size_reduction = updated_seed_data.length() - data.length();
|
| + UMA_HISTOGRAM_PERCENTAGE("Variations.StoreSeed.DeltaSize.ReductionPercent",
|
| + 100 * size_reduction / updated_seed_data.length());
|
| + UMA_HISTOGRAM_COUNTS_1000("Variations.StoreSeed.DeltaSize",
|
| + data.length() / 1024);
|
| + } else {
|
| + RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_DELTA_STORE);
|
| + }
|
| + return result;
|
| +}
|
| +
|
| +// TODO(asvitkine): Move this method down to match declaration order in a
|
| +// follow-up CL.
|
| +bool VariationsSeedStore::StoreSeedDataNoDelta(
|
| const std::string& seed_data,
|
| const std::string& base64_seed_signature,
|
| + const std::string& country_code,
|
| const base::Time& date_fetched,
|
| variations::VariationsSeed* parsed_seed) {
|
| if (seed_data.empty()) {
|
| - RecordVariationsSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_EMPTY);
|
| + RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_EMPTY);
|
| return false;
|
| }
|
|
|
| // Only store the seed data if it parses correctly.
|
| variations::VariationsSeed seed;
|
| if (!seed.ParseFromString(seed_data)) {
|
| - RecordVariationsSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_PARSE);
|
| + RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_PARSE);
|
| return false;
|
| }
|
|
|
| @@ -201,8 +264,7 @@ bool VariationsSeedStore::StoreSeedData(
|
| UMA_HISTOGRAM_ENUMERATION("Variations.StoreSeedSignature", result,
|
| VARIATIONS_SEED_SIGNATURE_ENUM_SIZE);
|
| if (result != VARIATIONS_SEED_SIGNATURE_VALID) {
|
| - RecordVariationsSeedStoreHistogram(
|
| - VARIATIONS_SEED_STORE_FAILED_SIGNATURE);
|
| + RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_SIGNATURE);
|
| return false;
|
| }
|
| }
|
| @@ -210,7 +272,7 @@ bool VariationsSeedStore::StoreSeedData(
|
| // Compress the seed before base64-encoding and storing.
|
| std::string compressed_seed_data;
|
| if (!metrics::GzipCompress(seed_data, &compressed_seed_data)) {
|
| - RecordVariationsSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_GZIP);
|
| + RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_GZIP);
|
| return false;
|
| }
|
|
|
| @@ -222,7 +284,11 @@ bool VariationsSeedStore::StoreSeedData(
|
| local_state_->ClearPref(prefs::kVariationsSeed);
|
|
|
| // Update the saved country code only if one was returned from the server.
|
| - if (seed.has_country_code())
|
| + // Prefer the country code that was transmitted in the header over the one in
|
| + // the seed (which is deprecated).
|
| + if (!country_code.empty())
|
| + local_state_->SetString(prefs::kVariationsCountry, country_code);
|
| + else if (seed.has_country_code())
|
| local_state_->SetString(prefs::kVariationsCountry, seed.country_code());
|
|
|
| local_state_->SetString(prefs::kVariationsCompressedSeed, base64_seed_data);
|
| @@ -233,7 +299,7 @@ bool VariationsSeedStore::StoreSeedData(
|
| if (parsed_seed)
|
| seed.Swap(parsed_seed);
|
|
|
| - RecordVariationsSeedStoreHistogram(VARIATIONS_SEED_STORE_SUCCESS);
|
| + RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_SUCCESS);
|
| return true;
|
| }
|
|
|
| @@ -257,6 +323,11 @@ void VariationsSeedStore::UpdateSeedDateAndLogDayChange(
|
| server_date_fetched.ToInternalValue());
|
| }
|
|
|
| +
|
| +std::string VariationsSeedStore::GetInvalidSignature() const {
|
| + return invalid_base64_signature_;
|
| +}
|
| +
|
| // static
|
| void VariationsSeedStore::RegisterPrefs(PrefRegistrySimple* registry) {
|
| registry->RegisterStringPref(prefs::kVariationsCompressedSeed, std::string());
|
| @@ -335,8 +406,51 @@ VariationsSeedStore::VerifySeedSignature(
|
| return VARIATIONS_SEED_SIGNATURE_INVALID_SEED;
|
| }
|
|
|
| -std::string VariationsSeedStore::GetInvalidSignature() const {
|
| - return invalid_base64_signature_;
|
| +// 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*>(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 existing_data_size = static_cast<uint32>(existing_data.size());
|
| + while (in.CurrentPosition() != static_cast<int>(patch.length())) {
|
| + uint32 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 offset;
|
| + uint32 length;
|
| + if (!in.ReadVarint32(&offset) || !in.ReadVarint32(&length))
|
| + return false;
|
| +
|
| + // Check for |offset + length| being out of range and for overflow.
|
| + base::CheckedNumeric<uint32> 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 chrome_variations
|
|
|