1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/component_updater/smart_dim_component_installer.h"
6 
7 #include <cstddef>
8 #include <tuple>
9 
10 #include "base/bind.h"
11 #include "base/feature_list.h"
12 #include "base/files/file_path.h"
13 #include "base/files/file_util.h"
14 #include "base/logging.h"
15 #include "base/memory/ref_counted.h"
16 #include "base/metrics/field_trial_params.h"
17 #include "base/optional.h"
18 #include "base/task/post_task.h"
19 #include "base/task/thread_pool.h"
20 #include "base/version.h"
21 #include "chrome/browser/chromeos/power/ml/smart_dim/metrics.h"
22 #include "chrome/browser/chromeos/power/ml/smart_dim/ml_agent.h"
23 #include "chromeos/constants/chromeos_features.h"
24 #include "components/component_updater/component_updater_service.h"
25 #include "content/public/browser/browser_thread.h"
26 
27 namespace {
28 
29 using ::chromeos::power::ml::ComponentFileContents;
30 using ::chromeos::power::ml::ComponentVersionType;
31 using ::chromeos::power::ml::LoadComponentEvent;
32 using ::chromeos::power::ml::LogComponentVersionType;
33 using ::chromeos::power::ml::LogLoadComponentEvent;
34 
35 const base::FilePath::CharType kSmartDimFeaturePreprocessorConfigFileName[] =
36     FILE_PATH_LITERAL("example_preprocessor_config.pb");
37 const base::FilePath::CharType kSmartDimModelFileName[] =
38     FILE_PATH_LITERAL("mlservice-model-smart_dim.tflite");
39 const base::FilePath::CharType kSmartDimMetaJsonFileName[] =
40     FILE_PATH_LITERAL("smart_dim_meta.json");
41 
42 const char kDefaultVersion[] = "20200601.0";
43 
44 constexpr base::FeatureParam<std::string> kVersion{
45     &chromeos::features::kSmartDimExperimentalComponent,
46     "smart_dim_experimental_version", kDefaultVersion};
47 
48 // The SHA256 of the SubjectPublicKeyInfo used to sign the extension.
49 // The extension id is: ghiclnejioiofblmbphpgbhaojnkempa
50 const uint8_t kSmartDimPublicKeySHA256[32] = {
51     0x67, 0x82, 0xbd, 0x49, 0x8e, 0x8e, 0x51, 0xbc, 0x1f, 0x7f, 0x61,
52     0x70, 0xe9, 0xda, 0x4c, 0xf0, 0x30, 0x2e, 0x24, 0x4d, 0x68, 0x17,
53     0x19, 0xad, 0x26, 0x6e, 0xd0, 0x33, 0x03, 0xb3, 0xe5, 0xff};
54 
55 const char kMLSmartDimManifestName[] = "Smart Dim";
56 
57 
58 // Read files from the component to strings, should be called from a blocking
59 // task runner.
ReadComponentFiles(const base::FilePath & meta_json_path,const base::FilePath & preprocessor_pb_path,const base::FilePath & model_path)60 base::Optional<ComponentFileContents> ReadComponentFiles(
61     const base::FilePath& meta_json_path,
62     const base::FilePath& preprocessor_pb_path,
63     const base::FilePath& model_path) {
64   std::string metadata_json, preprocessor_proto, model_flatbuffer;
65   if (!base::ReadFileToString(meta_json_path, &metadata_json) ||
66       !base::ReadFileToString(preprocessor_pb_path, &preprocessor_proto) ||
67       !base::ReadFileToString(model_path, &model_flatbuffer)) {
68     DLOG(ERROR) << "Failed reading component files.";
69     return base::nullopt;
70   }
71 
72   return std::make_tuple(std::move(metadata_json),
73                          std::move(preprocessor_proto),
74                          std::move(model_flatbuffer));
75 }
76 
UpdateSmartDimMlAgent(const base::Optional<ComponentFileContents> & result)77 void UpdateSmartDimMlAgent(
78     const base::Optional<ComponentFileContents>& result) {
79   if (result == base::nullopt) {
80     LogLoadComponentEvent(LoadComponentEvent::kReadComponentFilesError);
81     return;
82   }
83 
84   chromeos::power::ml::SmartDimMlAgent::GetInstance()->OnComponentReady(
85       result.value());
86 }
87 
88 }  // namespace
89 
90 namespace component_updater {
91 
SmartDimComponentInstallerPolicy(std::string expected_version)92 SmartDimComponentInstallerPolicy::SmartDimComponentInstallerPolicy(
93     std::string expected_version)
94     : expected_version_(expected_version) {}
95 
96 SmartDimComponentInstallerPolicy::~SmartDimComponentInstallerPolicy() = default;
97 
98 bool SmartDimComponentInstallerPolicy::
SupportsGroupPolicyEnabledComponentUpdates() const99     SupportsGroupPolicyEnabledComponentUpdates() const {
100   return false;
101 }
102 
RequiresNetworkEncryption() const103 bool SmartDimComponentInstallerPolicy::RequiresNetworkEncryption() const {
104   return false;
105 }
106 
107 update_client::CrxInstaller::Result
OnCustomInstall(const base::DictionaryValue & manifest,const base::FilePath & install_dir)108 SmartDimComponentInstallerPolicy::OnCustomInstall(
109     const base::DictionaryValue& manifest,
110     const base::FilePath& install_dir) {
111   return update_client::CrxInstaller::Result(0);  // Nothing custom here.
112 }
113 
OnCustomUninstall()114 void SmartDimComponentInstallerPolicy::OnCustomUninstall() {}
115 
ComponentReady(const base::Version & version,const base::FilePath & install_dir,std::unique_ptr<base::DictionaryValue> manifest)116 void SmartDimComponentInstallerPolicy::ComponentReady(
117     const base::Version& version,
118     const base::FilePath& install_dir,
119     std::unique_ptr<base::DictionaryValue> manifest) {
120   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
121   // If IsDownloadWorkerReady(), newly downloaded components will take effect
122   // on next reboot. This makes sure the updating happens at most once.
123   if (chromeos::power::ml::SmartDimMlAgent::GetInstance()
124           ->IsDownloadWorkerReady()) {
125     DVLOG(1) << "Download_worker in SmartDimMlAgent is ready, does nothing.";
126     return;
127   }
128 
129   DCHECK(!install_dir.empty());
130   DVLOG(1) << "Component ready, version " << version.GetString() << " in "
131            << install_dir.value();
132 
133   base::ThreadPool::PostTaskAndReplyWithResult(
134       FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
135       base::BindOnce(
136           &ReadComponentFiles, install_dir.Append(kSmartDimMetaJsonFileName),
137           install_dir.Append(kSmartDimFeaturePreprocessorConfigFileName),
138           install_dir.Append(kSmartDimModelFileName)),
139       base::BindOnce(&UpdateSmartDimMlAgent));
140 }
141 
142 // Called during startup and installation before ComponentReady().
VerifyInstallation(const base::DictionaryValue & manifest,const base::FilePath & install_dir) const143 bool SmartDimComponentInstallerPolicy::VerifyInstallation(
144     const base::DictionaryValue& manifest,
145     const base::FilePath& install_dir) const {
146   // Get component version from manifest and compare to the expected_version_.
147   // Note: versions should not be treated as simple strings, for example,
148   // base::Version("2020.02.06") == base::Version("2020.2.6").
149   const auto* version_value = manifest.FindKey("version");
150   DCHECK(version_value);
151   const base::Version component_version(version_value->GetString());
152   const base::Version expected_version(expected_version_);
153   if (component_version != expected_version) {
154     DVLOG(1) << "Version " << component_version
155              << " doesn't match expected_version " << expected_version;
156     return false;
157   }
158   // No need to actually validate the pb and tflite files here, since we'll do
159   // the checking in UpdateSmartDimMlAgent.
160   return base::PathExists(
161              install_dir.Append(kSmartDimFeaturePreprocessorConfigFileName)) &&
162          base::PathExists(install_dir.Append(kSmartDimModelFileName)) &&
163          base::PathExists(install_dir.Append(kSmartDimMetaJsonFileName));
164 }
165 
GetRelativeInstallDir() const166 base::FilePath SmartDimComponentInstallerPolicy::GetRelativeInstallDir() const {
167   return base::FilePath(FILE_PATH_LITERAL("SmartDim"));
168 }
169 
GetHash(std::vector<uint8_t> * hash) const170 void SmartDimComponentInstallerPolicy::GetHash(
171     std::vector<uint8_t>* hash) const {
172   DCHECK(hash);
173   hash->assign(kSmartDimPublicKeySHA256,
174                kSmartDimPublicKeySHA256 + base::size(kSmartDimPublicKeySHA256));
175 }
176 
GetName() const177 std::string SmartDimComponentInstallerPolicy::GetName() const {
178   return kMLSmartDimManifestName;
179 }
180 
181 update_client::InstallerAttributes
GetInstallerAttributes() const182 SmartDimComponentInstallerPolicy::GetInstallerAttributes() const {
183   update_client::InstallerAttributes attrs;
184   // Append a '$' for exact matching.
185   attrs["targetversionprefix"] = expected_version_ + "$";
186   return attrs;
187 }
188 
GetMimeTypes() const189 std::vector<std::string> SmartDimComponentInstallerPolicy::GetMimeTypes()
190     const {
191   return std::vector<std::string>();
192 }
193 
RegisterSmartDimComponent(ComponentUpdateService * cus)194 void RegisterSmartDimComponent(ComponentUpdateService* cus) {
195   if (!base::FeatureList::IsEnabled(chromeos::features::kSmartDimNewMlAgent))
196     return;
197 
198   DVLOG(1) << "Registering smart dim component.";
199   const std::string expected_version = kVersion.Get();
200 
201   if (expected_version.empty()) {
202     LogComponentVersionType(ComponentVersionType::kEmpty);
203     DLOG(ERROR) << "expected_version is empty.";
204     return;
205   }
206 
207   if (expected_version == kDefaultVersion)
208     LogComponentVersionType(ComponentVersionType::kDefault);
209   else
210     LogComponentVersionType(ComponentVersionType::kExperimental);
211 
212   auto installer = base::MakeRefCounted<ComponentInstaller>(
213       std::make_unique<SmartDimComponentInstallerPolicy>(expected_version));
214   installer->Register(cus, base::OnceClosure());
215 }
216 
217 }  // namespace component_updater
218