1 // Copyright 2020 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.
5 #include "media/mojo/services/fuchsia_cdm_manager.h"
7 #include <fuchsia/media/drm/cpp/fidl.h>
8 #include <fuchsia/media/drm/cpp/fidl_test_base.h>
9 #include <lib/fidl/cpp/binding_set.h>
10 #include <lib/fidl/cpp/interface_request.h>
11 #include <lib/fpromise/promise.h>
14 #include "base/files/file_util.h"
15 #include "base/files/scoped_temp_dir.h"
16 #include "base/functional/bind.h"
17 #include "base/functional/callback.h"
18 #include "base/run_loop.h"
19 #include "base/test/bind.h"
20 #include "base/test/task_environment.h"
21 #include "testing/gmock/include/gmock/gmock.h"
22 #include "testing/gtest/include/gtest/gtest.h"
24 #include "url/origin.h"
29 namespace drm = ::fuchsia::media::drm;
33 using ::testing::Invoke;
34 using ::testing::SaveArg;
35 using ::testing::WithArgs;
37 // This is a mock for the Chromium media::ProvisionFetcher (and not Fuchsia's
38 // similarly named ProvisioningFetcher protocol).
39 class MockProvisionFetcher : public ProvisionFetcher {
41 MockProvisionFetcher() = default;
42 ~MockProvisionFetcher() override = default;
46 (const GURL& default_url,
47 const std::string& request_data,
48 ResponseCB response_cb),
52 std::unique_ptr<ProvisionFetcher> CreateMockProvisionFetcher() {
53 auto mock_provision_fetcher = std::make_unique<MockProvisionFetcher>();
54 ON_CALL(*mock_provision_fetcher, Retrieve(_, _, _))
55 .WillByDefault(WithArgs<2>(
56 Invoke([](ProvisionFetcher::ResponseCB response_callback) {
57 std::move(response_callback).Run(true, "response");
59 return mock_provision_fetcher;
62 class MockKeySystem : public drm::testing::KeySystem_TestBase {
64 MockKeySystem() = default;
65 ~MockKeySystem() override = default;
67 drm::KeySystemHandle AddBinding() { return bindings_.AddBinding(this); }
68 fidl::BindingSet<drm::KeySystem>& bindings() { return bindings_; }
70 void NotImplemented_(const std::string& name) override { FAIL() << name; }
74 (uint32_t data_store_id,
75 drm::DataStoreParams params,
76 AddDataStoreCallback callback),
80 CreateContentDecryptionModule2,
81 (uint32_t data_store_id,
82 fidl::InterfaceRequest<drm::ContentDecryptionModule> cdm_request),
86 fidl::BindingSet<drm::KeySystem> bindings_;
89 class FuchsiaCdmManagerTest : public ::testing::Test {
91 FuchsiaCdmManagerTest() { EXPECT_TRUE(temp_dir_.CreateUniqueTempDir()); }
93 std::unique_ptr<FuchsiaCdmManager> CreateFuchsiaCdmManager(
94 std::vector<base::StringPiece> key_systems,
95 absl::optional<uint64_t> cdm_data_quota_bytes = absl::nullopt) {
96 FuchsiaCdmManager::CreateKeySystemCallbackMap create_key_system_callbacks;
98 for (const base::StringPiece& name : key_systems) {
99 MockKeySystem& key_system = mock_key_systems_[name];
100 create_key_system_callbacks.emplace(
101 name, base::BindRepeating(&MockKeySystem::AddBinding,
102 base::Unretained(&key_system)));
104 return std::make_unique<FuchsiaCdmManager>(
105 std::move(create_key_system_callbacks), temp_dir_.GetPath(),
106 cdm_data_quota_bytes);
110 using MockKeySystemMap = std::map<base::StringPiece, MockKeySystem>;
112 MockKeySystem& mock_key_system(const base::StringPiece& key_system_name) {
113 return mock_key_systems_[key_system_name];
116 base::test::TaskEnvironment task_environment_{
117 base::test::TaskEnvironment::MainThreadType::IO};
119 MockKeySystemMap mock_key_systems_;
120 base::ScopedTempDir temp_dir_;
123 TEST_F(FuchsiaCdmManagerTest, NoKeySystems) {
124 std::unique_ptr<FuchsiaCdmManager> cdm_manager = CreateFuchsiaCdmManager({});
126 base::RunLoop run_loop;
127 drm::ContentDecryptionModulePtr cdm_ptr;
128 cdm_ptr.set_error_handler([&](zx_status_t status) {
129 EXPECT_EQ(status, ZX_ERR_NOT_FOUND);
133 cdm_manager->CreateAndProvision(
134 "com.key_system", url::Origin(),
135 base::BindRepeating(&CreateMockProvisionFetcher), cdm_ptr.NewRequest());
139 TEST_F(FuchsiaCdmManagerTest, CreateAndProvision) {
140 constexpr char kKeySystem[] = "com.key_system.a";
141 std::unique_ptr<FuchsiaCdmManager> cdm_manager =
142 CreateFuchsiaCdmManager({kKeySystem});
144 base::RunLoop run_loop;
145 drm::ContentDecryptionModulePtr cdm_ptr;
146 cdm_ptr.set_error_handler([&](zx_status_t status) { run_loop.Quit(); });
148 uint32_t added_data_store_id = 0;
149 uint32_t cdm_data_store_id = 0;
150 EXPECT_CALL(mock_key_system(kKeySystem), AddDataStore(_, _, _))
151 .WillOnce(WithArgs<0, 2>(
152 Invoke([&](uint32_t data_store_id,
153 drm::KeySystem::AddDataStoreCallback callback) {
154 added_data_store_id = data_store_id;
155 callback(fpromise::ok());
158 EXPECT_CALL(mock_key_system(kKeySystem), CreateContentDecryptionModule2(_, _))
159 .WillOnce(SaveArg<0>(&cdm_data_store_id));
161 cdm_manager->CreateAndProvision(
162 kKeySystem, url::Origin(),
163 base::BindRepeating(&CreateMockProvisionFetcher), cdm_ptr.NewRequest());
166 EXPECT_NE(added_data_store_id, 0u);
167 EXPECT_EQ(added_data_store_id, cdm_data_store_id);
170 TEST_F(FuchsiaCdmManagerTest, RecreateAfterDisconnect) {
171 constexpr char kKeySystem[] = "com.key_system.a";
172 std::unique_ptr<FuchsiaCdmManager> cdm_manager =
173 CreateFuchsiaCdmManager({kKeySystem});
175 uint32_t added_data_store_id = 0;
176 EXPECT_CALL(mock_key_system(kKeySystem), AddDataStore(_, _, _))
177 .WillOnce(WithArgs<0, 2>(
178 Invoke([&](uint32_t data_store_id,
179 drm::KeySystem::AddDataStoreCallback callback) {
180 added_data_store_id = data_store_id;
181 callback(fpromise::ok());
184 // Create a CDM to force a KeySystem binding
185 base::RunLoop create_run_loop;
186 drm::ContentDecryptionModulePtr cdm_ptr;
187 cdm_ptr.set_error_handler(
188 [&](zx_status_t status) { create_run_loop.Quit(); });
189 cdm_manager->CreateAndProvision(
190 kKeySystem, url::Origin(),
191 base::BindRepeating(&CreateMockProvisionFetcher), cdm_ptr.NewRequest());
192 create_run_loop.Run();
193 ASSERT_EQ(mock_key_system(kKeySystem).bindings().size(), 1u);
195 // Close the KeySystem's bindings and wait until empty
196 base::RunLoop disconnect_run_loop;
197 cdm_manager->set_on_key_system_disconnect_for_test_callback(
198 base::BindLambdaForTesting([&](const std::string& key_system_name) {
199 if (key_system_name == kKeySystem) {
200 disconnect_run_loop.Quit();
203 mock_key_system(kKeySystem).bindings().CloseAll();
204 disconnect_run_loop.Run();
205 ASSERT_EQ(mock_key_system(kKeySystem).bindings().size(), 0u);
207 EXPECT_CALL(mock_key_system(kKeySystem),
208 AddDataStore(Eq(added_data_store_id), _, _))
210 WithArgs<2>(Invoke([](drm::KeySystem::AddDataStoreCallback callback) {
211 callback(fpromise::ok());
214 base::RunLoop recreate_run_loop;
215 cdm_ptr.set_error_handler(
216 [&](zx_status_t status) { recreate_run_loop.Quit(); });
217 cdm_manager->CreateAndProvision(
218 kKeySystem, url::Origin(),
219 base::BindRepeating(&CreateMockProvisionFetcher), cdm_ptr.NewRequest());
220 recreate_run_loop.Run();
221 EXPECT_EQ(mock_key_system(kKeySystem).bindings().size(), 1u);
224 TEST_F(FuchsiaCdmManagerTest, SameOriginShareDataStore) {
225 constexpr char kKeySystem[] = "com.key_system.a";
226 std::unique_ptr<FuchsiaCdmManager> cdm_manager =
227 CreateFuchsiaCdmManager({kKeySystem});
229 base::RunLoop run_loop;
230 drm::ContentDecryptionModulePtr cdm1, cdm2;
231 auto error_handler = [&](zx_status_t status) {
232 EXPECT_EQ(status, ZX_ERR_PEER_CLOSED);
233 if (!cdm1.is_bound() && !cdm2.is_bound()) {
237 cdm1.set_error_handler(error_handler);
238 cdm2.set_error_handler(error_handler);
240 EXPECT_CALL(mock_key_system(kKeySystem), AddDataStore(Eq(1u), _, _))
242 WithArgs<2>(Invoke([](drm::KeySystem::AddDataStoreCallback callback) {
243 callback(fpromise::ok());
245 EXPECT_CALL(mock_key_system(kKeySystem),
246 CreateContentDecryptionModule2(Eq(1u), _))
249 url::Origin origin = url::Origin::Create(GURL("http://origin_a.com"));
250 cdm_manager->CreateAndProvision(
251 kKeySystem, origin, base::BindRepeating(&CreateMockProvisionFetcher),
253 cdm_manager->CreateAndProvision(
254 kKeySystem, origin, base::BindRepeating(&CreateMockProvisionFetcher),
260 TEST_F(FuchsiaCdmManagerTest, DifferentOriginDoNotShareDataStore) {
261 constexpr char kKeySystem[] = "com.key_system.a";
262 std::unique_ptr<FuchsiaCdmManager> cdm_manager =
263 CreateFuchsiaCdmManager({kKeySystem});
265 base::RunLoop run_loop;
266 drm::ContentDecryptionModulePtr cdm1, cdm2;
267 auto error_handler = [&](zx_status_t status) {
268 EXPECT_EQ(status, ZX_ERR_PEER_CLOSED);
269 if (!cdm1.is_bound() && !cdm2.is_bound()) {
273 cdm1.set_error_handler(error_handler);
274 cdm2.set_error_handler(error_handler);
276 EXPECT_CALL(mock_key_system(kKeySystem), AddDataStore(Eq(1u), _, _))
278 WithArgs<2>(Invoke([](drm::KeySystem::AddDataStoreCallback callback) {
279 callback(fpromise::ok());
281 EXPECT_CALL(mock_key_system(kKeySystem), AddDataStore(Eq(2u), _, _))
283 WithArgs<2>(Invoke([](drm::KeySystem::AddDataStoreCallback callback) {
284 callback(fpromise::ok());
286 EXPECT_CALL(mock_key_system(kKeySystem),
287 CreateContentDecryptionModule2(Eq(1u), _))
289 EXPECT_CALL(mock_key_system(kKeySystem),
290 CreateContentDecryptionModule2(Eq(2u), _))
293 url::Origin origin_a = url::Origin::Create(GURL("http://origin_a.com"));
294 url::Origin origin_b = url::Origin::Create(GURL("http://origin_b.com"));
295 cdm_manager->CreateAndProvision(
296 kKeySystem, origin_a, base::BindRepeating(&CreateMockProvisionFetcher),
298 cdm_manager->CreateAndProvision(
299 kKeySystem, origin_b, base::BindRepeating(&CreateMockProvisionFetcher),
305 void CreateDummyCdmDirectory(const base::FilePath& cdm_data_path,
306 base::StringPiece origin,
307 base::StringPiece key_system,
309 const base::FilePath path = cdm_data_path.Append(origin).Append(key_system);
310 CHECK(base::CreateDirectory(path));
312 std::vector<uint8_t> zeroes(size);
313 CHECK(base::WriteFile(path.Append("zeroes"), zeroes));
317 // Verify that the least recently used CDM data directories are removed, until
318 // the quota is met. Also verify that old directories are removed regardless
319 // of whether they are empty or not.
320 TEST_F(FuchsiaCdmManagerTest, CdmDataQuotaBytes) {
321 constexpr uint64_t kTestQuotaBytes = 1024;
322 constexpr char kOriginDirectory1[] = "origin1";
323 constexpr char kOriginDirectory2[] = "origin2";
324 constexpr char kKeySystemDirectory1[] = "key_system1";
325 constexpr char kKeySystemDirectory2[] = "key_system2";
326 constexpr char kEmptyKeySystemDirectory[] = "empty_key_system";
328 // Create fake CDM data directories for two origins, each with two key
329 // systems, with each directory consuming 50% of the total quota, so that
330 // two directories must be removed to meet quota.
332 // Create least-recently-used directories & their contents.
333 const base::FilePath temp_path = temp_dir_.GetPath();
334 CreateDummyCdmDirectory(temp_path, kOriginDirectory1, kKeySystemDirectory1,
335 kTestQuotaBytes / 2);
336 CreateDummyCdmDirectory(temp_path, kOriginDirectory2, kKeySystemDirectory2,
337 kTestQuotaBytes / 2);
338 CreateDummyCdmDirectory(temp_path, kOriginDirectory1,
339 kEmptyKeySystemDirectory, 0);
341 // Sleep to account for coarse-grained filesystem timestamps.
342 base::PlatformThread::Sleep(base::Seconds(1));
344 // Create the recently-used directories.
345 CreateDummyCdmDirectory(temp_path, kOriginDirectory1, kKeySystemDirectory2,
346 kTestQuotaBytes / 2);
347 CreateDummyCdmDirectory(temp_path, kOriginDirectory2, kKeySystemDirectory1,
348 kTestQuotaBytes / 2);
349 CreateDummyCdmDirectory(temp_path, kOriginDirectory2,
350 kEmptyKeySystemDirectory, 0);
352 // Create the CDM manager, to run the data directory quota enforcement.
353 std::unique_ptr<FuchsiaCdmManager> cdm_manager =
354 CreateFuchsiaCdmManager({}, kTestQuotaBytes);
356 // Use a CreateAndProvision() request as a proxy to wait for quota enforcement
357 // to finish being applied.
358 base::RunLoop run_loop;
359 drm::ContentDecryptionModulePtr cdm_ptr;
360 cdm_ptr.set_error_handler([&](zx_status_t status) {
361 EXPECT_EQ(status, ZX_ERR_NOT_FOUND);
365 cdm_manager->CreateAndProvision(
366 "com.key_system", url::Origin(),
367 base::BindRepeating(&CreateMockProvisionFetcher), cdm_ptr.NewRequest());
370 EXPECT_FALSE(base::PathExists(
371 temp_path.Append(kOriginDirectory1).Append(kKeySystemDirectory1)));
372 EXPECT_FALSE(base::PathExists(
373 temp_path.Append(kOriginDirectory2).Append(kKeySystemDirectory2)));
375 EXPECT_TRUE(base::PathExists(
376 temp_path.Append(kOriginDirectory1).Append(kKeySystemDirectory2)));
377 EXPECT_TRUE(base::PathExists(
378 temp_path.Append(kOriginDirectory2).Append(kKeySystemDirectory1)));
380 // Empty directories are currently always treated as old, causing them all to
381 // be deleted if the CDM data directory exceeds its quota.
382 EXPECT_FALSE(base::PathExists(
383 temp_path.Append(kOriginDirectory1).Append(kEmptyKeySystemDirectory)));
384 EXPECT_FALSE(base::PathExists(
385 temp_path.Append(kOriginDirectory2).Append(kEmptyKeySystemDirectory)));
388 // Verify that if all key-system sub-directories for a given origin have been
389 // deleted then the origin's directory is also deleted.
390 TEST_F(FuchsiaCdmManagerTest, EmptyOriginDirectory) {
391 constexpr uint64_t kTestQuotaBytes = 1024;
392 constexpr char kInactiveOriginDirectory[] = "origin1";
393 constexpr char kActiveOriginDirectory[] = "origin2";
394 constexpr char kKeySystemDirectory1[] = "key_system1";
395 constexpr char kKeySystemDirectory2[] = "key_system2";
397 // Create dummy data for an inactive origin.
398 const base::FilePath temp_path = temp_dir_.GetPath();
399 CreateDummyCdmDirectory(temp_path, kInactiveOriginDirectory,
400 kKeySystemDirectory1, kTestQuotaBytes / 2);
401 CreateDummyCdmDirectory(temp_path, kInactiveOriginDirectory,
402 kKeySystemDirectory2, kTestQuotaBytes / 2);
404 // Sleep to account for coarse-grained filesystem timestamps.
405 base::PlatformThread::Sleep(base::Seconds(1));
407 // Create dummy data for a recently-used, active origin.
408 CreateDummyCdmDirectory(temp_path, kActiveOriginDirectory,
409 kKeySystemDirectory2, kTestQuotaBytes);
411 // Create the CDM manager, to run the data directory quota enforcement.
412 std::unique_ptr<FuchsiaCdmManager> cdm_manager =
413 CreateFuchsiaCdmManager({}, kTestQuotaBytes);
415 // Use a CreateAndProvision() request as a proxy to wait for quota enforcement
416 // to finish being applied.
417 base::RunLoop run_loop;
418 drm::ContentDecryptionModulePtr cdm_ptr;
419 cdm_ptr.set_error_handler([&](zx_status_t status) {
420 EXPECT_EQ(status, ZX_ERR_NOT_FOUND);
424 cdm_manager->CreateAndProvision(
425 "com.key_system", url::Origin(),
426 base::BindRepeating(&CreateMockProvisionFetcher), cdm_ptr.NewRequest());
429 EXPECT_FALSE(base::PathExists(temp_path.Append(kInactiveOriginDirectory)));
430 EXPECT_TRUE(base::PathExists(
431 temp_path.Append(kActiveOriginDirectory).Append(kKeySystemDirectory2)));