blob: f6efd400491b2210085096de2838a0c5ff2f9b2a [file] [log] [blame]
Kaido Kert25902c62024-06-17 17:10:28 -07001// Copyright 2019 The Chromium Authors
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 "media/mojo/services/fuchsia_cdm_manager.h"
6
7#include <fuchsia/media/drm/cpp/fidl.h>
8#include <lib/fidl/cpp/binding_set.h>
9#include <lib/fpromise/promise.h>
10
11#include "base/containers/flat_set.h"
12#include "base/containers/unique_ptr_adapters.h"
13#include "base/files/file_enumerator.h"
14#include "base/files/file_path.h"
15#include "base/files/file_util.h"
16#include "base/fuchsia/file_utils.h"
17#include "base/fuchsia/fuchsia_logging.h"
18#include "base/functional/bind.h"
19#include "base/functional/callback.h"
20#include "base/hash/hash.h"
21#include "base/logging.h"
22#include "base/strings/string_number_conversions.h"
23#include "base/task/task_traits.h"
24#include "base/task/thread_pool.h"
25#include "base/time/time.h"
26#include "media/mojo/services/fuchsia_cdm_provisioning_fetcher_impl.h"
27#include "third_party/abseil-cpp/absl/types/optional.h"
28#include "url/origin.h"
29
30namespace media {
31
32namespace {
33
34struct CdmDirectoryInfo {
35 base::FilePath path;
36 base::Time last_used;
37 uint64_t size_bytes;
38};
39
40// Enumerates all the files in the directory to determine its size and
41// the most recent "last used" time.
42// The implementation is based on base::ComputeDirectorySize(), with the
43// addition of most-recently-modified calculation, and inclusion of directory
44// node sizes toward the total.
45CdmDirectoryInfo GetCdmDirectoryInfo(const base::FilePath& path) {
46 uint64_t directory_size = 0;
47 base::Time last_used;
48 base::FileEnumerator enumerator(
49 path, true /* recursive */,
50 base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES);
51 while (!enumerator.Next().empty()) {
52 const base::FileEnumerator::FileInfo info = enumerator.GetInfo();
53 if (info.GetSize() > 0) {
54 directory_size += info.GetSize();
55 }
56 last_used = std::max(last_used, info.GetLastModifiedTime());
57 }
58 return {
59 .path = path,
60 .last_used = last_used,
61 .size_bytes = directory_size,
62 };
63}
64
65void ApplyCdmStorageQuota(base::FilePath cdm_data_path,
66 uint64_t cdm_data_quota_bytes) {
67 // TODO(crbug.com/1148334): Migrate to using a platform-provided quota
68 // mechanism to manage CDM storage.
69 VLOG(2) << "Enumerating CDM data directories.";
70
71 uint64_t directories_size_bytes = 0;
72 std::vector<CdmDirectoryInfo> directories_info;
73
74 // CDM storage consistes of per-origin directories, each containing one or
75 // more per-key-system sub-directories. Each per-origin-per-key-system
76 // directory is assumed to be independent of other CDM data.
77 base::FileEnumerator by_origin(cdm_data_path, false /* recursive */,
78 base::FileEnumerator::DIRECTORIES);
79 for (;;) {
80 const base::FilePath origin_directory = by_origin.Next();
81 if (origin_directory.empty()) {
82 break;
83 }
84 base::FileEnumerator by_key_system(origin_directory, false /* recursive */,
85 base::FileEnumerator::DIRECTORIES);
86 for (;;) {
87 const base::FilePath key_system_directory = by_key_system.Next();
88 if (key_system_directory.empty()) {
89 break;
90 }
91 directories_info.push_back(GetCdmDirectoryInfo(key_system_directory));
92 directories_size_bytes += directories_info.back().size_bytes;
93 }
94 }
95
96 if (directories_size_bytes <= cdm_data_quota_bytes) {
97 return;
98 }
99
100 VLOG(1) << "Removing least recently accessed CDM data.";
101
102 // Enumerate directories starting with the least most recently "used",
103 // deleting them until the the total amount of CDM data is within quota.
104 std::sort(directories_info.begin(), directories_info.end(),
105 [](const CdmDirectoryInfo& lhs, const CdmDirectoryInfo& rhs) {
106 return lhs.last_used < rhs.last_used;
107 });
108 base::flat_set<base::FilePath> affected_origin_directories;
109 for (const auto& directory_info : directories_info) {
110 if (directories_size_bytes <= cdm_data_quota_bytes) {
111 break;
112 }
113
114 VLOG(1) << "Removing " << directory_info.path;
115 base::DeletePathRecursively(directory_info.path);
116 affected_origin_directories.insert(directory_info.path.DirName());
117
118 DCHECK_GE(directories_size_bytes, directory_info.size_bytes);
119 directories_size_bytes -= directory_info.size_bytes;
120 }
121
122 // Enumerate all the origin directories that had sub-directories deleted,
123 // and delete any that are now empty.
124 for (const auto& origin_directory : affected_origin_directories) {
125 if (base::IsDirectoryEmpty(origin_directory)) {
126 base::DeleteFile(origin_directory);
127 }
128 }
129}
130
131std::string HexEncodeHash(const std::string& name) {
132 uint32_t hash = base::PersistentHash(name);
133 return base::HexEncode(&hash, sizeof(uint32_t));
134}
135
136// Returns a nullopt if storage was created successfully.
137absl::optional<base::File::Error> CreateStorageDirectory(base::FilePath path) {
138 base::File::Error error;
139 bool success = base::CreateDirectoryAndGetError(path, &error);
140 if (!success) {
141 return error;
142 }
143 return {};
144}
145
146FuchsiaCdmManager* g_fuchsia_cdm_manager_instance = nullptr;
147
148} // namespace
149
150// Manages individual KeySystem connections. Provides data stores and
151// ProvisioningFetchers to the KeySystem server and associating CDM requests
152// with the appropriate data store.
153class FuchsiaCdmManager::KeySystemClient {
154 public:
155 // Construct an unbound KeySystemClient. The |name| field should be the EME
156 // name of the key system, such as org.w3.clearkey. It is only used for
157 // logging purposes.
158 explicit KeySystemClient(std::string name) : name_(std::move(name)) {}
159 ~KeySystemClient() = default;
160
161 // Registers an error handler and binds the KeySystem handle. If Bind returns
162 // an error, the error handler will not be called.
163 zx_status_t Bind(
164 fidl::InterfaceHandle<fuchsia::media::drm::KeySystem> key_system_handle,
165 base::OnceClosure error_callback) {
166 key_system_.set_error_handler(
167 [name = name_, error_callback = std::move(error_callback)](
168 zx_status_t status) mutable {
169 ZX_LOG(ERROR, status) << "KeySystem " << name << " closed channel.";
170 std::move(error_callback).Run();
171 });
172
173 return key_system_.Bind(std::move(key_system_handle));
174 }
175
176 void CreateCdm(
177 base::FilePath storage_path,
178 CreateFetcherCB create_fetcher_callback,
179 fidl::InterfaceRequest<fuchsia::media::drm::ContentDecryptionModule>
180 request) {
181 absl::optional<DataStoreId> data_store_id = GetDataStoreIdForPath(
182 std::move(storage_path), std::move(create_fetcher_callback));
183 if (!data_store_id) {
184 request.Close(ZX_ERR_NO_RESOURCES);
185 return;
186 }
187
188 // If this request triggered an AddDataStore() request, then that will be
189 // processed before this call. If AddDataStore() fails, then the
190 // |data_store_id| will not be valid and the create call will close the
191 // |request| with a ZX_ERR_NOT_FOUND epitaph.
192 key_system_->CreateContentDecryptionModule2(data_store_id.value(),
193 std::move(request));
194 }
195
196 private:
197 using DataStoreId = uint32_t;
198
199 absl::optional<DataStoreId> GetDataStoreIdForPath(
200 base::FilePath storage_path,
201 CreateFetcherCB create_fetcher_callback) {
202 // If we have already added a data store id for that path, just use that
203 // one.
204 auto it = data_store_ids_by_path_.find(storage_path);
205 if (it != data_store_ids_by_path_.end()) {
206 return it->second;
207 }
208
209 fidl::InterfaceHandle<fuchsia::io::Directory> data_directory =
210 base::OpenDirectoryHandle(storage_path);
211 if (!data_directory.is_valid()) {
212 DLOG(ERROR) << "Unable to OpenDirectory " << storage_path;
213 return absl::nullopt;
214 }
215
216 auto provisioning_fetcher =
217 std::make_unique<FuchsiaCdmProvisioningFetcherImpl>(
218 std::move(create_fetcher_callback));
219
220 DataStoreId data_store_id = next_data_store_id_++;
221
222 fuchsia::media::drm::DataStoreParams params;
223 params.set_data_directory(std::move(data_directory));
224 params.set_provisioning_fetcher(provisioning_fetcher->Bind(
225 base::BindOnce(&KeySystemClient::OnProvisioningFetcherError,
226 base::Unretained(this), provisioning_fetcher.get())));
227
228 key_system_->AddDataStore(
229 data_store_id, std::move(params),
230 [this, data_store_id, storage_path](
231 fpromise::result<void, fuchsia::media::drm::Error> result) {
232 if (result.is_error()) {
233 DLOG(ERROR) << "Failed to add data store " << data_store_id
234 << ", path: " << storage_path;
235 data_store_ids_by_path_.erase(storage_path);
236 return;
237 }
238 });
239
240 provisioning_fetchers_.insert(std::move(provisioning_fetcher));
241 data_store_ids_by_path_.emplace(std::move(storage_path), data_store_id);
242 return data_store_id;
243 }
244
245 void OnProvisioningFetcherError(
246 FuchsiaCdmProvisioningFetcherImpl* provisioning_fetcher) {
247 provisioning_fetchers_.erase(provisioning_fetcher);
248 }
249
250 // The EME name of the key system, such as org.w3.clearkey
251 std::string name_;
252
253 // FIDL InterfacePtr to the platform provided KeySystem
254 fuchsia::media::drm::KeySystemPtr key_system_;
255
256 // A set of ProvisioningFetchers, one for each data store that gets added.
257 // The KeySystem might close the channel even if the data store remains in
258 // use.
259 base::flat_set<std::unique_ptr<FuchsiaCdmProvisioningFetcherImpl>,
260 base::UniquePtrComparator>
261 provisioning_fetchers_;
262
263 // The next data store id to use when registering data stores with the
264 // KeySystem. Data store ids are scoped to the KeySystem channel. Value starts
265 // at 1 because 0 is a reserved sentinel value for
266 // fuchsia::media::drm::NO_DATA_STORE. The value will be incremented each time
267 // we add a DataStore.
268 DataStoreId next_data_store_id_ = 1;
269
270 // A map of directory paths to data store ids that have been added to the
271 // KeySystem.
272 base::flat_map<base::FilePath, DataStoreId> data_store_ids_by_path_;
273};
274
275// static
276FuchsiaCdmManager* FuchsiaCdmManager::GetInstance() {
277 return g_fuchsia_cdm_manager_instance;
278}
279
280FuchsiaCdmManager::FuchsiaCdmManager(
281 CreateKeySystemCallbackMap create_key_system_callbacks_by_name,
282 base::FilePath cdm_data_path,
283 absl::optional<uint64_t> cdm_data_quota_bytes)
284 : create_key_system_callbacks_by_name_(
285 std::move(create_key_system_callbacks_by_name)),
286 cdm_data_path_(std::move(cdm_data_path)),
287 cdm_data_quota_bytes_(std::move(cdm_data_quota_bytes)),
288 storage_task_runner_(
289 base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})) {
290 // To avoid potential for the CDM directory "cleanup" task removing
291 // CDM data directories that are in active use, the |storage_task_runner_| is
292 // sequenced, thereby ensuring cleanup completes before any CDM activities
293 // start.
294 if (cdm_data_quota_bytes_) {
295 ApplyCdmStorageQuota(cdm_data_path_, *cdm_data_quota_bytes_);
296 }
297
298 DCHECK(!g_fuchsia_cdm_manager_instance);
299 g_fuchsia_cdm_manager_instance = this;
300}
301
302FuchsiaCdmManager::~FuchsiaCdmManager() {
303 DCHECK_EQ(g_fuchsia_cdm_manager_instance, this);
304 g_fuchsia_cdm_manager_instance = nullptr;
305}
306
307void FuchsiaCdmManager::CreateAndProvision(
308 const std::string& key_system,
309 const url::Origin& origin,
310 CreateFetcherCB create_fetcher_cb,
311 fidl::InterfaceRequest<fuchsia::media::drm::ContentDecryptionModule>
312 request) {
313 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
314
315 base::FilePath storage_path = GetStoragePath(key_system, origin);
316
317 auto task = base::BindOnce(&CreateStorageDirectory, storage_path);
318 storage_task_runner_->PostTaskAndReplyWithResult(
319 FROM_HERE, std::move(task),
320 base::BindOnce(&FuchsiaCdmManager::CreateCdm, weak_factory_.GetWeakPtr(),
321 key_system, std::move(create_fetcher_cb),
322 std::move(request), std::move(storage_path)));
323}
324
325void FuchsiaCdmManager::set_on_key_system_disconnect_for_test_callback(
326 base::RepeatingCallback<void(const std::string&)> disconnect_callback) {
327 on_key_system_disconnect_for_test_callback_ = std::move(disconnect_callback);
328}
329
330FuchsiaCdmManager::KeySystemClient*
331FuchsiaCdmManager::GetOrCreateKeySystemClient(
332 const std::string& key_system_name) {
333 auto client_it = active_key_system_clients_by_name_.find(key_system_name);
334 if (client_it == active_key_system_clients_by_name_.end()) {
335 // If there is no active one, attempt to create one.
336 return CreateKeySystemClient(key_system_name);
337 }
338 return client_it->second.get();
339}
340
341FuchsiaCdmManager::KeySystemClient* FuchsiaCdmManager::CreateKeySystemClient(
342 const std::string& key_system_name) {
343 const auto create_callback_it =
344 create_key_system_callbacks_by_name_.find(key_system_name);
345 if (create_callback_it == create_key_system_callbacks_by_name_.cend()) {
346 DLOG(ERROR) << "Key system is not supported: " << key_system_name;
347 return nullptr;
348 }
349
350 auto key_system_client = std::make_unique<KeySystemClient>(key_system_name);
351 zx_status_t status = key_system_client->Bind(
352 create_callback_it->second.Run(),
353 base::BindOnce(&FuchsiaCdmManager::OnKeySystemClientError,
354 base::Unretained(this), key_system_name));
355 if (status != ZX_OK) {
356 ZX_DLOG(ERROR, status) << "Unable to bind to KeySystem";
357 return nullptr;
358 }
359
360 KeySystemClient* key_system_client_ptr = key_system_client.get();
361 active_key_system_clients_by_name_.emplace(key_system_name,
362 std::move(key_system_client));
363 return key_system_client_ptr;
364}
365
366base::FilePath FuchsiaCdmManager::GetStoragePath(const std::string& key_system,
367 const url::Origin& origin) {
368 return cdm_data_path_.Append(HexEncodeHash(origin.Serialize()))
369 .Append(HexEncodeHash(key_system));
370}
371
372void FuchsiaCdmManager::CreateCdm(
373 const std::string& key_system_name,
374 CreateFetcherCB create_fetcher_cb,
375 fidl::InterfaceRequest<fuchsia::media::drm::ContentDecryptionModule>
376 request,
377 base::FilePath storage_path,
378 absl::optional<base::File::Error> storage_creation_error) {
379 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
380
381 if (storage_creation_error) {
382 DLOG(ERROR) << "Failed to create directory: " << storage_path
383 << ", error: " << *storage_creation_error;
384 request.Close(ZX_ERR_NO_RESOURCES);
385 return;
386 }
387
388 KeySystemClient* key_system_client =
389 GetOrCreateKeySystemClient(key_system_name);
390 if (!key_system_client) {
391 // GetOrCreateKeySystemClient will log the reason for failure.
392 request.Close(ZX_ERR_NOT_FOUND);
393 return;
394 }
395
396 key_system_client->CreateCdm(std::move(storage_path),
397 std::move(create_fetcher_cb),
398 std::move(request));
399}
400
401void FuchsiaCdmManager::OnKeySystemClientError(
402 const std::string& key_system_name) {
403 if (on_key_system_disconnect_for_test_callback_) {
404 on_key_system_disconnect_for_test_callback_.Run(key_system_name);
405 }
406
407 active_key_system_clients_by_name_.erase(key_system_name);
408}
409
410} // namespace media