[M120 Migration][MM][CAPI] Fix the logic for media using capi player.
[platform/framework/web/chromium-efl.git] / media / mojo / services / fuchsia_cdm_manager.cc
1 // 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
30 namespace media {
31
32 namespace {
33
34 struct 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.
45 CdmDirectoryInfo 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
65 void 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
131 std::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.
137 absl::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
146 FuchsiaCdmManager* 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.
153 class 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
276 FuchsiaCdmManager* FuchsiaCdmManager::GetInstance() {
277   return g_fuchsia_cdm_manager_instance;
278 }
279
280 FuchsiaCdmManager::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
302 FuchsiaCdmManager::~FuchsiaCdmManager() {
303   DCHECK_EQ(g_fuchsia_cdm_manager_instance, this);
304   g_fuchsia_cdm_manager_instance = nullptr;
305 }
306
307 void 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
325 void 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
330 FuchsiaCdmManager::KeySystemClient*
331 FuchsiaCdmManager::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
341 FuchsiaCdmManager::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
366 base::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
372 void 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
401 void 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