910e62b5创建于 1月15日历史提交
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/updater/persisted_data.h"

#include <optional>
#include <string>
#include <vector>

#include "base/base64.h"
#include "base/check.h"
#include "base/files/file_path.h"
#include "base/json/values_util.h"
#include "base/logging.h"
#include "base/sequence_checker.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/values.h"
#include "base/version.h"
#include "build/build_config.h"
#include "chrome/updater/branded_constants.h"
#include "chrome/updater/event_history.h"
#include "chrome/updater/registration_data.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/update_client/activity_data_service.h"
#include "components/update_client/persisted_data.h"
#include "components/update_client/update_client_errors.h"

#if BUILDFLAG(IS_WIN)
#include <windows.h>

#include "base/containers/span.h"
#include "base/containers/span_reader.h"
#include "base/numerics/byte_conversions.h"
#include "base/win/registry.h"
#include "chrome/updater/util/win_util.h"
#include "chrome/updater/win/win_constants.h"
#endif

namespace {

// PersistedData keys.
constexpr char kVersionPath[] = "pv_path";
constexpr char kVersionKey[] = "pv_key";
constexpr char kECP[] = "ecp";
constexpr char kBC[] = "bc";
constexpr char kBP[] = "bp";
constexpr char kAP[] = "ap";
constexpr char kAPPath[] = "ap_path";
constexpr char kAPKey[] = "ap_key";
constexpr char kLang[] = "lang";

constexpr char kHadApps[] = "had_apps";
constexpr char kUsageStatsEnabledKey[] = "usage_stats_enabled";
constexpr char kRemoteLoggingCookie[] = "remote_logging_cookie";
constexpr char kNextAllowedLoggingAttemptTime[] = "next_logging_attempt_time";
constexpr char kEulaRequired[] = "eula_required";

constexpr char kLastChecked[] = "last_checked";
constexpr char kLastStarted[] = "last_started";
constexpr char kLastOSVersion[] = "last_os_version";

constexpr char kCookieValueKey[] = "value";
constexpr char kCookieExpirationKey[] = "expiration";

}  // namespace

namespace updater {

PersistedData::PersistedData(
    UpdaterScope scope,
    PrefService* pref_service,
    std::unique_ptr<update_client::ActivityDataService> activity_service)
    : scope_(scope),
      pref_service_(pref_service),
      delegate_(update_client::CreatePersistedData(
          base::BindRepeating(
              [](PrefService* pref_service) { return pref_service; },
              pref_service),
          std::move(activity_service))) {
  CHECK(pref_service_);

  PersistedDataEvent event;
  for (const std::string& app_id : GetAppIds()) {
    PersistedDataEvent::RegisteredApp app;
    app.app_id = app_id;
    app.brand_code = GetBrandCode(app_id);
    app.cohort = GetCohort(app_id);
    app.version = GetProductVersion(app_id).GetString();
    event.AddRegisteredApp(app);
  }
  event.SetEulaRequired(GetEulaRequired())
      .SetLastChecked(GetLastChecked())
      .SetLastStarted(GetLastStarted())
      .WriteAsync();
}

PersistedData::~PersistedData() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

base::Version PersistedData::GetProductVersion(const std::string& id) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return delegate_->GetProductVersion(id);
}

void PersistedData::SetProductVersion(const std::string& id,
                                      const base::Version& pv) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(pv.IsValid());
  delegate_->SetProductVersion(id, pv);

#if BUILDFLAG(IS_WIN)
  // For backwards compatibility, we record the PV in ClientState as well.
  // (Some applications read it from there.) This has the side effect of
  // creating the ClientState key, which is read to sense for application
  // uninstallation.
  SetRegistryKey(UpdaterScopeToHKeyRoot(scope_), GetAppClientStateKey(id),
                 kRegValuePV, base::UTF8ToWide(pv.GetString()));
#endif
}

base::Version PersistedData::GetMaxPreviousProductVersion(
    const std::string& id) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return delegate_->GetMaxPreviousProductVersion(id);
}

void PersistedData::SetMaxPreviousProductVersion(
    const std::string& id,
    const base::Version& max_version) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(max_version.IsValid());
  delegate_->SetMaxPreviousProductVersion(id, max_version);
}

base::FilePath PersistedData::GetProductVersionPath(
    const std::string& id) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return base::FilePath::FromUTF8Unsafe(GetString(id, kVersionPath));
}

void PersistedData::SetProductVersionPath(const std::string& id,
                                          const base::FilePath& path) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  SetString(id, kVersionPath, path.AsUTF8Unsafe());
}

std::string PersistedData::GetProductVersionKey(const std::string& id) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return GetString(id, kVersionKey);
}

void PersistedData::SetProductVersionKey(const std::string& id,
                                         const std::string& key) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  SetString(id, kVersionKey, key);
}

std::string PersistedData::GetFingerprint(const std::string& id) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return delegate_->GetFingerprint(id);
}

void PersistedData::SetFingerprint(const std::string& id,
                                   const std::string& fingerprint) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  delegate_->SetFingerprint(id, fingerprint);
}

base::FilePath PersistedData::GetExistenceCheckerPath(
    const std::string& id) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return base::FilePath::FromUTF8Unsafe(GetString(id, kECP));
}

void PersistedData::SetExistenceCheckerPath(const std::string& id,
                                            const base::FilePath& ecp) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  SetString(id, kECP, ecp.AsUTF8Unsafe());
}

std::string PersistedData::GetBrandCode(const std::string& id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  const std::string bc = GetString(id, kBC);

#if BUILDFLAG(IS_WIN)
  // For backwards compatibility, if there is a brand code in the registry
  // ClientState, that brand code is considered authoritative, and overrides any
  // brand code that is already in `prefs`.
  std::wstring registry_bc;
  if (base::win::RegKey(UpdaterScopeToHKeyRoot(scope_),
                        GetAppClientStateKey(id).c_str(),
                        Wow6432(KEY_QUERY_VALUE))
          .ReadValue(kRegValueBrandCode, &registry_bc) == ERROR_SUCCESS) {
    const std::string registry_brand_code = base::WideToUTF8(registry_bc);
    if (!registry_brand_code.empty() && registry_brand_code != bc) {
      SetString(id, kBC, registry_brand_code);
      return registry_brand_code;
    }
  }
#endif

  if (bc.empty()) {
    return {};
  }

#if BUILDFLAG(IS_WIN)
  // For backwards compatibility, record the brand code in ClientState, since
  // some applications read it from there.
  SetRegistryKey(UpdaterScopeToHKeyRoot(scope_), GetAppClientStateKey(id),
                 kRegValueBrandCode, base::UTF8ToWide(bc));
#endif
  return bc;
}

void PersistedData::SetBrandCode(const std::string& id, const std::string& bc) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // If there is already an existing brand code, do not overwrite it.
  if (!GetBrandCode(id).empty()) {
    return;
  }

  SetString(id, kBC, bc);

#if BUILDFLAG(IS_WIN)
  // For backwards compatibility, record the brand code in ClientState, since
  // some applications read it from there.
  SetRegistryKey(UpdaterScopeToHKeyRoot(scope_), GetAppClientStateKey(id),
                 kRegValueBrandCode, base::UTF8ToWide(bc));
#endif
}

base::FilePath PersistedData::GetBrandPath(const std::string& id) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return base::FilePath::FromUTF8Unsafe(GetString(id, kBP));
}

void PersistedData::SetBrandPath(const std::string& id,
                                 const base::FilePath& bp) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  SetString(id, kBP, bp.AsUTF8Unsafe());
}

std::string PersistedData::GetAP(const std::string& id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

#if BUILDFLAG(IS_WIN)
  // For backwards compatibility, we read AP from ClientState first, since some
  // applications write to it there.
  if (const std::string ap(GetAppAPValue(scope_, id)); !ap.empty()) {
    SetAP(id, ap);
    return ap;
  }
#endif

  return GetString(id, kAP);
}

void PersistedData::SetAP(const std::string& id, const std::string& ap) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  SetString(id, kAP, ap);

#if BUILDFLAG(IS_WIN)
  // For backwards compatibility, we record the AP in ClientState as well.
  // (Some applications read it from there.)

  // Chromium Updater has both local and global pref stores. In practice, if
  // this `PersistedData` is using a local pref store, `id` will be the
  // qualification app and the ClientState value is not important, so it is
  // acceptable for each instance of the updater to overwrite it with various
  // values. Else, this is the global pref store and reflecting the value in
  // registry is correct. Clients should transition to requesting the
  // registration info for their application via RPC.
  SetRegistryKey(UpdaterScopeToHKeyRoot(scope_), GetAppClientStateKey(id),
                 kRegValueAP, base::UTF8ToWide(ap));
#endif
}

base::FilePath PersistedData::GetAPPath(const std::string& id) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return base::FilePath::FromUTF8Unsafe(GetString(id, kAPPath));
}

void PersistedData::SetAPPath(const std::string& id,
                              const base::FilePath& path) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  SetString(id, kAPPath, path.AsUTF8Unsafe());
}

std::string PersistedData::GetAPKey(const std::string& id) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return GetString(id, kAPKey);
}

void PersistedData::SetAPKey(const std::string& id, const std::string& key) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  SetString(id, kAPKey, key);
}

std::string PersistedData::GetLang(const std::string& id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  const std::string lang = GetString(id, kLang);

#if BUILDFLAG(IS_WIN)
  // For backwards compatibility, if there is a lang in the registry Clients or
  // ClientState, that lang is considered authoritative, and overrides any lang
  // that is already in `prefs`.
  for (const auto& subkey : {GetAppClientsKey(id), GetAppClientStateKey(id)}) {
    std::wstring registry_lang_w;
    if (base::win::RegKey(UpdaterScopeToHKeyRoot(scope_), subkey.c_str(),
                          Wow6432(KEY_QUERY_VALUE))
            .ReadValue(kRegValueLang, &registry_lang_w) == ERROR_SUCCESS) {
      const std::string registry_lang = base::WideToUTF8(registry_lang_w);
      if (!registry_lang.empty() && registry_lang != lang) {
        SetString(id, kLang, registry_lang);
        return registry_lang;
      }
    }
  }
#endif

  return lang;
}

void PersistedData::SetLang(const std::string& id, const std::string& lang) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  SetString(id, kLang, lang);

#if BUILDFLAG(IS_WIN)
  // For backwards compatibility, record the lang in ClientState, since some
  // applications read it from there.
  SetRegistryKey(UpdaterScopeToHKeyRoot(scope_), GetAppClientStateKey(id),
                 kRegValueLang, base::UTF8ToWide(lang));
#endif
}

int PersistedData::GetDateLastActive(const std::string& id) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return delegate_->GetDateLastActive(id);
}

int PersistedData::GetDaysSinceLastActive(const std::string& id) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return delegate_->GetDaysSinceLastActive(id);
}

void PersistedData::SetDateLastActive(const std::string& id, int dla) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  delegate_->SetDateLastActive(id, dla);
}

int PersistedData::GetDateLastRollCall(const std::string& id) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return delegate_->GetDateLastRollCall(id);
}

int PersistedData::GetDaysSinceLastRollCall(const std::string& id) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return delegate_->GetDaysSinceLastRollCall(id);
}

void PersistedData::SetDateLastRollCall(const std::string& id, int dlrc) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  delegate_->SetDateLastRollCall(id, dlrc);
}

std::string PersistedData::GetCohort(const std::string& id) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return delegate_->GetCohort(id);
}

void PersistedData::SetCohort(const std::string& id,
                              const std::string& cohort) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  delegate_->SetCohort(id, cohort);

#if BUILDFLAG(IS_WIN)
  // For backwards compatibility, we record the Cohort in ClientState as well.
  // (Some applications read it from there.)
  SetRegistryKey(UpdaterScopeToHKeyRoot(scope_), GetAppCohortKey(id), L"",
                 base::UTF8ToWide(cohort));
#endif
}

std::string PersistedData::GetCohortName(const std::string& id) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return delegate_->GetCohortName(id);
}

void PersistedData::SetCohortName(const std::string& id,
                                  const std::string& cohort_name) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  delegate_->SetCohortName(id, cohort_name);

#if BUILDFLAG(IS_WIN)
  // For backwards compatibility, we record the Cohort in ClientState as well.
  // (Some applications read it from there.)
  SetRegistryKey(UpdaterScopeToHKeyRoot(scope_), GetAppCohortKey(id),
                 kRegValueCohortName, base::UTF8ToWide(cohort_name));
#endif
}

std::string PersistedData::GetCohortHint(const std::string& id) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return delegate_->GetCohortHint(id);
}

void PersistedData::SetCohortHint(const std::string& id,
                                  const std::string& cohort_hint) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  delegate_->SetCohortHint(id, cohort_hint);
}

std::string PersistedData::GetPingFreshness(const std::string& id) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return delegate_->GetPingFreshness(id);
}

void PersistedData::SetDateLastData(const std::vector<std::string>& ids,
                                    int datenum,
                                    base::OnceClosure callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  delegate_->SetDateLastData(ids, datenum, std::move(callback));
}

int PersistedData::GetInstallDate(const std::string& id) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return delegate_->GetInstallDate(id);
}

void PersistedData::SetInstallDate(const std::string& id, int install_date) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  delegate_->SetInstallDate(id, install_date);
}

std::string PersistedData::GetInstallId(const std::string& app_id) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return delegate_->GetInstallId(app_id);
}

void PersistedData::SetInstallId(const std::string& app_id,
                                 const std::string& install_id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  delegate_->SetInstallId(app_id, install_id);
}

void PersistedData::GetActiveBits(
    const std::vector<std::string>& ids,
    base::OnceCallback<void(const std::set<std::string>&)> callback) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  delegate_->GetActiveBits(ids, std::move(callback));
}

base::Time PersistedData::GetThrottleUpdatesUntil() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return delegate_->GetThrottleUpdatesUntil();
}

void PersistedData::SetLastUpdateCheckError(
    const update_client::CategorizedError& error) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  delegate_->SetLastUpdateCheckError(error);
}

void PersistedData::SetThrottleUpdatesUntil(base::Time time) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  delegate_->SetThrottleUpdatesUntil(time);
}

void PersistedData::RegisterApp(const RegistrationRequest& rq) {
  VLOG(2) << __func__ << ": Registering " << rq.app_id;
  if (base::Version(rq.version).IsValid()) {
    VLOG(2) << __func__ << ": app version " << rq.version;
    SetProductVersion(rq.app_id, base::Version(rq.version));
  }
  if (rq.version_path && !rq.version_path->empty()) {
    SetProductVersionPath(rq.app_id, *rq.version_path);
  }
  if (rq.version_key && !rq.version_key->empty()) {
    SetProductVersionKey(rq.app_id, *rq.version_key);
  }
  if (!rq.existence_checker_path.empty()) {
    SetExistenceCheckerPath(rq.app_id, rq.existence_checker_path);
  }
  if (rq.lang && !rq.lang->empty()) {
    SetLang(rq.app_id, *rq.lang);
  }
  if (!rq.brand_code.empty()) {
    SetBrandCode(rq.app_id, rq.brand_code);
  }
  if (!rq.brand_path.empty()) {
    SetBrandPath(rq.app_id, rq.brand_path);
  }
  if (!rq.ap.empty()) {
    SetAP(rq.app_id, rq.ap);
  }
  if (rq.ap_path && !rq.ap_path->empty()) {
    SetAPPath(rq.app_id, *rq.ap_path);
  }
  if (rq.ap_key && !rq.ap_key->empty()) {
    SetAPKey(rq.app_id, *rq.ap_key);
  }
  if (rq.dla) {
    SetDateLastActive(rq.app_id, rq.dla.value());
  } else if (GetDateLastActive(rq.app_id) == update_client::kDateUnknown) {
    SetDateLastActive(rq.app_id, update_client::kDateFirstTime);
  }
  if (rq.dlrc) {
    SetDateLastRollCall(rq.app_id, rq.dlrc.value());
  } else if (GetDateLastRollCall(rq.app_id) == update_client::kDateUnknown) {
    SetDateLastRollCall(rq.app_id, update_client::kDateFirstTime);
  }
  if (rq.install_date) {
    SetInstallDate(rq.app_id, *rq.install_date);
  }
  if (rq.cohort && !rq.cohort->empty()) {
    SetCohort(rq.app_id, *rq.cohort);
  }
  if (rq.cohort_name && !rq.cohort_name->empty()) {
    SetCohortName(rq.app_id, *rq.cohort_name);
  }
  if (rq.cohort_hint && !rq.cohort_hint->empty()) {
    SetCohortHint(rq.app_id, *rq.cohort_hint);
  }
  if (rq.install_id && !rq.install_id->empty()) {
    SetInstallId(rq.app_id, *rq.install_id);
  }
}

bool PersistedData::HasApp(const std::string& id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  const base::Value::Dict* apps =
      pref_service_->GetDict(update_client::kPersistedDataPreference)
          .FindDict("apps");
  return apps && apps->Find(base::ToLowerASCII(id)) != nullptr;
}

bool PersistedData::RemoveApp(const std::string& id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

#if BUILDFLAG(IS_WIN)
  // For backwards compatibility, the `ClientState` and (for system installs)
  // `ClientStateMedium` entries for the app are also removed.
  for (const auto& subkey : [&] {
         std::vector<std::wstring> subkeys = {GetAppClientStateKey(id)};
         if (IsSystemInstall(scope_)) {
           subkeys.push_back(GetAppClientStateMediumKey(id));
         }
         return subkeys;
       }()) {
    base::win::RegKey(UpdaterScopeToHKeyRoot(scope_), L"", Wow6432(DELETE))
        .DeleteKey(subkey.c_str());
  }
#endif

  if (!pref_service_) {
    return false;
  }

  ScopedDictPrefUpdate update(pref_service_,
                              update_client::kPersistedDataPreference);
  base::Value::Dict* apps = update->FindDict("apps");

  return apps ? apps->Remove(base::ToLowerASCII(id)) : false;
}

std::vector<std::string> PersistedData::GetAppIds() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // The prefs is a dictionary of dictionaries, where each inner dictionary
  // corresponds to an app:
  // {"updateclientdata":{"apps":{"{44FC7FE2-65CE-487C-93F4-EDEE46EEAAAB}":{...
  const base::Value::Dict& dict =
      pref_service_->GetDict(update_client::kPersistedDataPreference);
  const base::Value::Dict* apps = dict.FindDict("apps");
  if (!apps) {
    return {};
  }
  std::vector<std::string> app_ids;
  for (auto it = apps->begin(); it != apps->end(); ++it) {
    const auto& app_id = it->first;
    const auto pv = GetProductVersion(app_id);
    if (pv.IsValid()) {
      app_ids.push_back(app_id);
    }
  }
  return app_ids;
}

const base::Value::Dict* PersistedData::GetAppKey(const std::string& id) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!pref_service_) {
    return nullptr;
  }
  const base::Value::Dict& dict =
      pref_service_->GetDict(update_client::kPersistedDataPreference);
  const base::Value::Dict* apps = dict.FindDict("apps");
  if (!apps) {
    return nullptr;
  }
  return apps->FindDict(base::ToLowerASCII(id));
}

std::string PersistedData::GetString(const std::string& id,
                                     const std::string& key) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  const base::Value::Dict* app_key = GetAppKey(id);
  if (!app_key) {
    return {};
  }
  const std::string* value = app_key->FindString(key);
  if (!value) {
    return {};
  }
  return *value;
}

base::Value::Dict* PersistedData::GetOrCreateAppKey(const std::string& id,
                                                    base::Value::Dict& root) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  base::Value::Dict* apps = root.EnsureDict("apps");
  base::Value::Dict* app = apps->EnsureDict(base::ToLowerASCII(id));
  return app;
}

std::optional<int> PersistedData::GetInteger(const std::string& id,
                                             const std::string& key) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!pref_service_) {
    return std::nullopt;
  }
  ScopedDictPrefUpdate update(pref_service_,
                              update_client::kPersistedDataPreference);
  base::Value::Dict* apps = update->FindDict("apps");
  if (!apps) {
    return std::nullopt;
  }
  base::Value::Dict* app = apps->FindDict(base::ToLowerASCII(id));
  if (!app) {
    return std::nullopt;
  }
  return app->FindInt(key);
}

void PersistedData::SetInteger(const std::string& id,
                               const std::string& key,
                               int value) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!pref_service_) {
    return;
  }
  ScopedDictPrefUpdate update(pref_service_,
                              update_client::kPersistedDataPreference);
  GetOrCreateAppKey(id, update.Get())->Set(key, value);
}

void PersistedData::SetString(const std::string& id,
                              const std::string& key,
                              const std::string& value) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!pref_service_) {
    return;
  }
  ScopedDictPrefUpdate update(pref_service_,
                              update_client::kPersistedDataPreference);
  GetOrCreateAppKey(id, update.Get())->Set(key, value);
}

bool PersistedData::GetHadApps() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return pref_service_ && pref_service_->GetBoolean(kHadApps);
}

void PersistedData::SetHadApps() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (pref_service_) {
    pref_service_->SetBoolean(kHadApps, true);
  }
}

std::optional<PersistedData::Cookie> PersistedData::GetRemoteLoggingCookie()
    const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!pref_service_) {
    return std::nullopt;
  }

  const base::Value::Dict& cookie =
      pref_service_->GetDict(kRemoteLoggingCookie);
  const std::string* value = cookie.FindString(kCookieValueKey);
  std::optional<base::Time> expiration =
      base::ValueToTime(cookie.Find(kCookieExpirationKey));
  if (!value || !expiration) {
    return std::nullopt;
  }

  return Cookie{
      .value = *value,
      .expiration = *expiration,
  };
}

void PersistedData::SetRemoteLoggingCookie(const Cookie& logging_cookie) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (pref_service_) {
    pref_service_->SetDict(
        kRemoteLoggingCookie,
        base::Value::Dict()
            .Set(kCookieValueKey, logging_cookie.value)
            .Set(kCookieExpirationKey,
                 base::TimeToValue(logging_cookie.expiration)));
  }
}

void PersistedData::ClearRemoteLoggingCookie() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (pref_service_) {
    pref_service_->ClearPref(kRemoteLoggingCookie);
  }
}

base::Time PersistedData::GetNextAllowedLoggingAttemptTime() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!pref_service_) {
    return base::Time();
  }
  return pref_service_->GetTime(kNextAllowedLoggingAttemptTime);
}

void PersistedData::SetNextAllowedLoggingAttemptTime(base::Time time) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (pref_service_) {
    pref_service_->SetTime(kNextAllowedLoggingAttemptTime, time);
  }
}

bool PersistedData::GetEulaRequired() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return pref_service_ && pref_service_->GetBoolean(kEulaRequired);
}

void PersistedData::SetEulaRequired(bool eula_required) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (pref_service_) {
    pref_service_->SetBoolean(kEulaRequired, eula_required);
  }
#if BUILDFLAG(IS_WIN)
  // For backwards compatibility, `eulaaccepted` is recorded in the registry,
  // since some applications read it from there.
  SetEulaAccepted(scope_, !eula_required);
#endif
}

base::Time PersistedData::GetLastChecked() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return pref_service_->GetTime(kLastChecked);
}

void PersistedData::SetLastChecked(base::Time time) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (pref_service_) {
    pref_service_->SetTime(kLastChecked, time);
  }
}

base::Time PersistedData::GetLastStarted() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return pref_service_->GetTime(kLastStarted);
}

void PersistedData::SetLastStarted(base::Time time) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (pref_service_) {
    pref_service_->SetTime(kLastStarted, time);
  }
}

#if BUILDFLAG(IS_WIN)
std::optional<OSVERSIONINFOEX> PersistedData::GetLastOSVersion() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // Unpacks the os version from a base-64-encoded string internally.
  const std::string encoded_os_version =
      pref_service_->GetString(kLastOSVersion);

  if (encoded_os_version.empty()) {
    return std::nullopt;
  }

  const std::optional<std::vector<uint8_t>> decoded_os_version =
      base::Base64Decode(encoded_os_version);
  if (!decoded_os_version ||
      decoded_os_version->size() != sizeof(OSVERSIONINFOEX)) {
    return std::nullopt;
  }

  auto reader = base::SpanReader(base::span(*decoded_os_version));
  OSVERSIONINFOEX info;
  info.dwOSVersionInfoSize =
      base::U32FromNativeEndian(*reader.Read<sizeof(DWORD)>());
  info.dwMajorVersion =
      base::U32FromNativeEndian(*reader.Read<sizeof(DWORD)>());
  info.dwMinorVersion =
      base::U32FromNativeEndian(*reader.Read<sizeof(DWORD)>());
  info.dwBuildNumber = base::U32FromNativeEndian(*reader.Read<sizeof(DWORD)>());
  info.dwPlatformId = base::U32FromNativeEndian(*reader.Read<sizeof(DWORD)>());
  base::as_writable_byte_span(info.szCSDVersion)
      .copy_from(*reader.Read<sizeof((OSVERSIONINFOEX){}.szCSDVersion)>());
  info.wServicePackMajor =
      base::U16FromNativeEndian(*reader.Read<sizeof(WORD)>());
  info.wServicePackMinor =
      base::U16FromNativeEndian(*reader.Read<sizeof(WORD)>());
  info.wSuiteMask = base::U16FromNativeEndian(*reader.Read<sizeof(WORD)>());
  info.wProductType = base::U8FromNativeEndian(*reader.Read<sizeof(BYTE)>());
  info.wReserved = base::U8FromNativeEndian(*reader.Read<sizeof(BYTE)>());

  CHECK_EQ(reader.remaining(), 0u);
  return info;
}

void PersistedData::SetLastOSVersion() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!pref_service_) {
    return;
  }

  // Get and set the current OS version.
  std::optional<OSVERSIONINFOEX> os_version = GetOSVersion();
  if (!os_version) {
    return;
  }

  // The os version is internally stored as a base-64-encoded string.
  std::string encoded_os_version =
      base::Base64Encode(base::byte_span_from_ref(os_version.value()));

  return pref_service_->SetString(kLastOSVersion, encoded_os_version);
}
#endif

// Register persisted data prefs, except for kPersistedDataPreference.
// kPersistedDataPreference is registered by update_client::RegisterPrefs.
void RegisterPersistedDataPrefs(scoped_refptr<PrefRegistrySimple> registry) {
  registry->RegisterBooleanPref(kHadApps, false);
  registry->RegisterBooleanPref(kEulaRequired, false);
  registry->RegisterTimePref(kNextAllowedLoggingAttemptTime, {});
  registry->RegisterTimePref(kLastChecked, {});
  registry->RegisterTimePref(kLastStarted, {});
  registry->RegisterStringPref(kLastOSVersion, {});
  registry->RegisterDictionaryPref(kRemoteLoggingCookie, {});

  // TODO(crbug.com/422187975): Remove obsolete pref no earlier than 6/3/2026.
  registry->RegisterBooleanPref(kUsageStatsEnabledKey, false);
}

void MigrateObsoletePersistedDataPrefs(PrefService* pref_service) {
  // TODO(crbug.com/422187975): Remove obsolete pref no earlier than 6/3/2026.
  pref_service->ClearPref(kUsageStatsEnabledKey);
}

}  // namespace updater