chromium/chrome/browser/component_updater/smart_dim_component_installer.cc

// 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.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chrome/browser/component_updater/smart_dim_component_installer.h"

#include <cstddef>
#include <optional>
#include <tuple>

#include "ash/constants/ash_features.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/metrics/field_trial_params.h"
#include "base/task/thread_pool.h"
#include "base/values.h"
#include "base/version.h"
#include "chrome/browser/ash/power/ml/smart_dim/metrics.h"
#include "chrome/browser/ash/power/ml/smart_dim/ml_agent.h"
#include "components/component_updater/component_updater_service.h"
#include "components/crx_file/id_util.h"
#include "content/public/browser/browser_thread.h"

namespace {

using ::ash::power::ml::ComponentFileContents;
using ::ash::power::ml::ComponentVersionType;
using ::ash::power::ml::LoadComponentEvent;
using ::ash::power::ml::LogComponentVersionType;
using ::ash::power::ml::LogLoadComponentEvent;

const base::FilePath::CharType kSmartDimFeaturePreprocessorConfigFileName[] =
    FILE_PATH_LITERAL("example_preprocessor_config.pb");
const base::FilePath::CharType kSmartDimModelFileName[] =
    FILE_PATH_LITERAL("mlservice-model-smart_dim.tflite");
const base::FilePath::CharType kSmartDimMetaJsonFileName[] =
    FILE_PATH_LITERAL("smart_dim_meta.json");

const char kDefaultVersion[] = "20210201.1";

constexpr base::FeatureParam<std::string> kVersion{
    &ash::features::kSmartDimExperimentalComponent,
    "smart_dim_experimental_version", kDefaultVersion};

// The SHA256 of the SubjectPublicKeyInfo used to sign the extension.
// The extension id is: ghiclnejioiofblmbphpgbhaojnkempa
const uint8_t kSmartDimPublicKeySHA256[32] = {
    0x67, 0x82, 0xbd, 0x49, 0x8e, 0x8e, 0x51, 0xbc, 0x1f, 0x7f, 0x61,
    0x70, 0xe9, 0xda, 0x4c, 0xf0, 0x30, 0x2e, 0x24, 0x4d, 0x68, 0x17,
    0x19, 0xad, 0x26, 0x6e, 0xd0, 0x33, 0x03, 0xb3, 0xe5, 0xff};

const char kMLSmartDimManifestName[] = "Smart Dim";

// Read files from the component to strings, should be called from a blocking
// task runner.
std::optional<ComponentFileContents> ReadComponentFiles(
    const base::FilePath& meta_json_path,
    const base::FilePath& preprocessor_pb_path,
    const base::FilePath& model_path) {
  std::string metadata_json, preprocessor_proto, model_flatbuffer;
  if (!base::ReadFileToString(meta_json_path, &metadata_json) ||
      !base::ReadFileToString(preprocessor_pb_path, &preprocessor_proto) ||
      !base::ReadFileToString(model_path, &model_flatbuffer)) {
    DLOG(ERROR) << "Failed reading component files.";
    return std::nullopt;
  }

  return std::make_tuple(std::move(metadata_json),
                         std::move(preprocessor_proto),
                         std::move(model_flatbuffer));
}

void UpdateSmartDimMlAgent(const std::optional<ComponentFileContents>& result) {
  if (result == std::nullopt) {
    LogLoadComponentEvent(LoadComponentEvent::kReadComponentFilesError);
    return;
  }

  ash::power::ml::SmartDimMlAgent::GetInstance()->OnComponentReady(
      result.value());
}

}  // namespace

namespace component_updater {

SmartDimComponentInstallerPolicy::SmartDimComponentInstallerPolicy(
    std::string expected_version)
    : expected_version_(expected_version) {}

SmartDimComponentInstallerPolicy::~SmartDimComponentInstallerPolicy() = default;

const std::string SmartDimComponentInstallerPolicy::GetExtensionId() {
  return crx_file::id_util::GenerateIdFromHash(kSmartDimPublicKeySHA256);
}

bool SmartDimComponentInstallerPolicy::
    SupportsGroupPolicyEnabledComponentUpdates() const {
  return true;
}

bool SmartDimComponentInstallerPolicy::RequiresNetworkEncryption() const {
  return false;
}

update_client::CrxInstaller::Result
SmartDimComponentInstallerPolicy::OnCustomInstall(
    const base::Value::Dict& manifest,
    const base::FilePath& install_dir) {
  return update_client::CrxInstaller::Result(0);  // Nothing custom here.
}

void SmartDimComponentInstallerPolicy::OnCustomUninstall() {}

void SmartDimComponentInstallerPolicy::ComponentReady(
    const base::Version& version,
    const base::FilePath& install_dir,
    base::Value::Dict manifest) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  // If IsDownloadWorkerReady(), newly downloaded components will take effect
  // on next reboot. This makes sure the updating happens at most once.
  if (ash::power::ml::SmartDimMlAgent::GetInstance()->IsDownloadWorkerReady()) {
    DVLOG(1) << "Download_worker in SmartDimMlAgent is ready, does nothing.";
    return;
  }

  DCHECK(!install_dir.empty());
  DVLOG(1) << "Component ready, version " << version.GetString() << " in "
           << install_dir.value();

  base::ThreadPool::PostTaskAndReplyWithResult(
      FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
      base::BindOnce(
          &ReadComponentFiles, install_dir.Append(kSmartDimMetaJsonFileName),
          install_dir.Append(kSmartDimFeaturePreprocessorConfigFileName),
          install_dir.Append(kSmartDimModelFileName)),
      base::BindOnce(&UpdateSmartDimMlAgent));
}

// Called during startup and installation before ComponentReady().
bool SmartDimComponentInstallerPolicy::VerifyInstallation(
    const base::Value::Dict& manifest,
    const base::FilePath& install_dir) const {
  // Get component version from manifest and compare to the expected_version_.
  // Note: versions should not be treated as simple strings, for example,
  // base::Version("2020.02.06") == base::Version("2020.2.6").
  const std::string* version_string = manifest.FindString("version");
  DCHECK(version_string);
  const base::Version component_version(*version_string);
  const base::Version expected_version(expected_version_);
  if (component_version != expected_version) {
    DVLOG(1) << "Version " << component_version
             << " doesn't match expected_version " << expected_version;
    return false;
  }
  // No need to actually validate the pb and tflite files here, since we'll do
  // the checking in UpdateSmartDimMlAgent.
  return base::PathExists(
             install_dir.Append(kSmartDimFeaturePreprocessorConfigFileName)) &&
         base::PathExists(install_dir.Append(kSmartDimModelFileName)) &&
         base::PathExists(install_dir.Append(kSmartDimMetaJsonFileName));
}

base::FilePath SmartDimComponentInstallerPolicy::GetRelativeInstallDir() const {
  return base::FilePath(FILE_PATH_LITERAL("SmartDim"));
}

void SmartDimComponentInstallerPolicy::GetHash(
    std::vector<uint8_t>* hash) const {
  DCHECK(hash);
  hash->assign(kSmartDimPublicKeySHA256,
               kSmartDimPublicKeySHA256 + std::size(kSmartDimPublicKeySHA256));
}

std::string SmartDimComponentInstallerPolicy::GetName() const {
  return kMLSmartDimManifestName;
}

update_client::InstallerAttributes
SmartDimComponentInstallerPolicy::GetInstallerAttributes() const {
  update_client::InstallerAttributes attrs;
  // Append a '$' for exact matching.
  attrs["targetversionprefix"] = expected_version_ + "$";
  return attrs;
}

void RegisterSmartDimComponent(ComponentUpdateService* cus,
                               base::OnceClosure callback) {
  DVLOG(1) << "Registering smart dim component.";
  const std::string expected_version = kVersion.Get();

  if (expected_version.empty()) {
    LogComponentVersionType(ComponentVersionType::kEmpty);
    DLOG(ERROR) << "expected_version is empty.";
    return;
  }

  if (expected_version == kDefaultVersion) {
    LogComponentVersionType(ComponentVersionType::kDefault);
  } else {
    LogComponentVersionType(ComponentVersionType::kExperimental);
  }

  auto installer = base::MakeRefCounted<ComponentInstaller>(
      std::make_unique<SmartDimComponentInstallerPolicy>(expected_version));
  installer->Register(cus, std::move(callback));
}

}  // namespace component_updater