File: cdm_document_service_impl_test.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (369 lines) | stat: -rw-r--r-- 13,936 bytes parent folder | download | duplicates (7)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/media/cdm_document_service_impl.h"

#include <memory>
#include <tuple>

#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/json/values_util.h"
#include "base/logging.h"
#include "base/run_loop.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/mock_callback.h"
#include "base/time/time.h"
#include "base/unguessable_token.h"
#include "base/values.h"
#include "chrome/browser/media/cdm_pref_service_helper.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "components/user_prefs/user_prefs.h"
#include "content/public/browser/web_contents.h"
#include "media/cdm/win/media_foundation_cdm.h"
#include "media/mojo/mojom/cdm_document_service.mojom.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"

using testing::_;
using testing::DoAll;
using testing::SaveArg;

namespace {
// copied from cdm_pref_service_helper.cc for testing
const char kOriginId[] = "origin_id";

base::FilePath CreateDummyCdmDataFile(const base::FilePath& cdm_store_path_root,
                                      const base::UnguessableToken& origin_id) {
  // Create a fake CDM file
  auto cdm_store_path = cdm_store_path_root.AppendASCII(origin_id.ToString());
  base::CreateDirectory(cdm_store_path);
  auto cdm_data_file_path = cdm_store_path.AppendASCII("cdm_data_file.txt");
  base::File file(cdm_data_file_path,
                  base::File::FLAG_CREATE | base::File::FLAG_WRITE);
  return cdm_data_file_path;
}
}  // namespace

namespace content {

const char kTestOrigin[] = "https://foo.bar";
const char kTestOrigin2[] = "https://bar.foo";

using GetMediaFoundationCdmDataMockCB = base::MockOnceCallback<void(
    std::unique_ptr<media::MediaFoundationCdmData>)>;

class CdmDocumentServiceImplTest : public ChromeRenderViewHostTestHarness {
 public:
  void SetUp() override {
    ChromeRenderViewHostTestHarness::SetUp();
    // The Media Foundation CDM depends on functionalities only available in
    // Windows 10 and newer versions.
    if (!media::MediaFoundationCdm::IsAvailable()) {
      GTEST_SKIP() << "skipping all test for this fixture when not running on "
                      "Windows 10.";
    }

    // Set up a testing profile manager.
    profile_manager_ = std::make_unique<TestingProfileManager>(
        TestingBrowserProcess::GetGlobal());
  }

  void NavigateToUrlAndCreateCdmDocumentService(GURL url) {
    // The lifetime of `cdm_document_service_` is tied to the lifetime of the
    // Frame. When changing URL we need to unbind `cdm_document_service_` before
    // we can bind it to the new frame.
    if (cdm_document_service_.is_bound())
      ASSERT_TRUE(cdm_document_service_.Unbind());
    NavigateAndCommit(url);
    CdmDocumentServiceImpl::Create(
        web_contents()->GetPrimaryMainFrame(),
        cdm_document_service_.BindNewPipeAndPassReceiver());
  }

  std::unique_ptr<media::MediaFoundationCdmData> GetMediaFoundationCdmData() {
    std::unique_ptr<media::MediaFoundationCdmData> media_foundation_cdm_data;
    GetMediaFoundationCdmDataMockCB mock_cb;
    base::RunLoop run_loop;
    EXPECT_CALL(mock_cb, Run(_))
        .WillOnce([&media_foundation_cdm_data, &run_loop](
                      std::unique_ptr<media::MediaFoundationCdmData> ptr) {
          media_foundation_cdm_data = std::move(ptr);
          run_loop.Quit();
        });

    cdm_document_service_->GetMediaFoundationCdmData(mock_cb.Get());
    run_loop.Run();

    return media_foundation_cdm_data;
  }

  void SetCdmClientToken(const std::vector<uint8_t>& client_token) {
    cdm_document_service_->SetCdmClientToken(client_token);
    base::RunLoop().RunUntilIdle();
  }

  void CorruptCdmPreference() {
    PrefService* user_prefs = profile()->GetPrefs();

    // Create (or overwrite) an entry with only an origin id to simulate some
    // kind of corruption or simply an update to the preference format.
    auto entry = base::Value::Dict().Set(
        kOriginId,
        base::UnguessableTokenToValue(base::UnguessableToken::Create()));

    ScopedDictPrefUpdate update(user_prefs, prefs::kMediaCdmOriginData);
    base::Value::Dict& dict = update.Get();
    const std::string serialized_origin = web_contents()
                                              ->GetPrimaryMainFrame()
                                              ->GetLastCommittedOrigin()
                                              .Serialize();
    dict.Set(serialized_origin, std::move(entry));
  }

 protected:
  mojo::Remote<media::mojom::CdmDocumentService> cdm_document_service_;
  std::unique_ptr<TestingProfileManager> profile_manager_;
};

// Verify that we get a non null origin id.
TEST_F(CdmDocumentServiceImplTest, GetOriginId) {
  NavigateToUrlAndCreateCdmDocumentService(GURL(kTestOrigin));
  auto data = GetMediaFoundationCdmData();
  ASSERT_FALSE(data->origin_id.is_empty());
}

// Verify that we get a non null and different origin id if the preference gets
// corrupted.
TEST_F(CdmDocumentServiceImplTest, GetOriginIdAfterCorruption) {
  NavigateToUrlAndCreateCdmDocumentService(GURL(kTestOrigin));
  auto data_before = GetMediaFoundationCdmData();

  CorruptCdmPreference();
  auto data_after = GetMediaFoundationCdmData();
  ASSERT_FALSE(data_after->origin_id.is_empty());
  ASSERT_NE(data_before->origin_id, data_after->origin_id);
}

// Verify that we can correctly get an existing origin id.
TEST_F(CdmDocumentServiceImplTest, GetSameOriginId) {
  NavigateToUrlAndCreateCdmDocumentService(GURL(kTestOrigin));
  base::UnguessableToken origin_id1 = GetMediaFoundationCdmData()->origin_id;

  // Create an unrelated origin id
  NavigateToUrlAndCreateCdmDocumentService(GURL(kTestOrigin2));
  base::UnguessableToken origin_id2 = GetMediaFoundationCdmData()->origin_id;

  // Get the origin id for the first origin
  NavigateToUrlAndCreateCdmDocumentService(GURL(kTestOrigin));
  base::UnguessableToken origin_id3 = GetMediaFoundationCdmData()->origin_id;

  ASSERT_NE(origin_id2, origin_id1);
  ASSERT_EQ(origin_id1, origin_id3);
}

TEST_F(CdmDocumentServiceImplTest, GetNullClientToken) {
  NavigateToUrlAndCreateCdmDocumentService(GURL(kTestOrigin));
  auto media_foundation_cdm_data = GetMediaFoundationCdmData();

  ASSERT_FALSE(media_foundation_cdm_data->client_token);
}

TEST_F(CdmDocumentServiceImplTest, SetClientToken) {
  NavigateToUrlAndCreateCdmDocumentService(GURL(kTestOrigin));
  // Call GetMediaFoundationCdmData to create the origin id first, otherwise
  // `SetCdmClientToken()` will assume the preference data associated with the
  // origin was recently cleared and will not save the client token.
  std::ignore = GetMediaFoundationCdmData();

  std::vector<uint8_t> expected_client_token = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
  SetCdmClientToken(expected_client_token);

  auto media_foundation_cdm_data = GetMediaFoundationCdmData();

  ASSERT_EQ(media_foundation_cdm_data->client_token, expected_client_token);
}

// Sets a client token for one origin and check that we get the same
// client token after navigating back to that origin.
TEST_F(CdmDocumentServiceImplTest, GetSameClientToken) {
  const auto kOrigin = url::Origin::Create(GURL(kTestOrigin));
  const auto kOtherOrigin = url::Origin::Create(GURL(kTestOrigin2));

  NavigateToUrlAndCreateCdmDocumentService(GURL(kTestOrigin));
  // Call GetMediaFoundationCdmData to create the origin id first, otherwise
  // `SetCdmClientToken()` will assume the preference data associated with the
  // origin was recently cleared and will not save the client token.
  std::ignore = GetMediaFoundationCdmData();
  std::vector<uint8_t> expected_client_token = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
  SetCdmClientToken(expected_client_token);

  NavigateToUrlAndCreateCdmDocumentService(GURL(kTestOrigin2));
  std::ignore = GetMediaFoundationCdmData();
  SetCdmClientToken({1, 2, 3, 4, 5});

  NavigateToUrlAndCreateCdmDocumentService(GURL(kTestOrigin));
  auto media_foundation_cdm_data = GetMediaFoundationCdmData();

  ASSERT_EQ(media_foundation_cdm_data->client_token, expected_client_token);
}

// If an entry cannot be parsed correctly, `SetCdmClientToken` should simply
// remove that entry and return without saving the client token.
TEST_F(CdmDocumentServiceImplTest, SetClientTokenAfterCorruption) {
  NavigateToUrlAndCreateCdmDocumentService(GURL(kTestOrigin));
  std::ignore = GetMediaFoundationCdmData();
  CorruptCdmPreference();

  std::vector<uint8_t> expected_client_token = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
  SetCdmClientToken(expected_client_token);

  auto media_foundation_cdm_data = GetMediaFoundationCdmData();
  ASSERT_FALSE(media_foundation_cdm_data->client_token.has_value());
}

// Check that we can clear the CDM preferences. `GetMediaFoundationCdmData()`
// should return a new origin_id after the clearing operation.
TEST_F(CdmDocumentServiceImplTest, ClearCdmPreferenceData) {
  const auto kOrigin = url::Origin::Create(GURL(kTestOrigin));

  NavigateToUrlAndCreateCdmDocumentService(GURL(kTestOrigin));
  auto cdm_data = GetMediaFoundationCdmData();
  base::UnguessableToken origin_id = cdm_data->origin_id;

  base::FilePath cdm_data_file_path = CreateDummyCdmDataFile(
      cdm_data->cdm_store_path_root, cdm_data->origin_id);

  base::Time start = base::Time::Now() - base::Hours(1);
  base::Time end;  // null time

  base::RunLoop loop1;

  // With the filter returning false, the origin id should not be destroyed.
  CdmDocumentServiceImpl::ClearCdmData(
      profile(), start, end,
      base::BindRepeating([](const GURL& url) { return false; }),
      loop1.QuitClosure());

  loop1.Run();
  base::UnguessableToken same_origin_id =
      GetMediaFoundationCdmData()->origin_id;
  ASSERT_EQ(origin_id, same_origin_id);
  ASSERT_TRUE(base::PathExists(cdm_data_file_path));

  base::RunLoop loop2;

  CdmDocumentServiceImpl::ClearCdmData(
      profile(), start, end,
      base::BindRepeating([](const GURL& url) { return true; }),
      loop2.QuitClosure());

  loop2.Run();

  base::UnguessableToken new_origin_id = GetMediaFoundationCdmData()->origin_id;
  ASSERT_NE(origin_id, new_origin_id);
  ASSERT_FALSE(base::PathExists(cdm_data_file_path));
}

TEST_F(CdmDocumentServiceImplTest, ClearCdmPreferenceDataAfterCorruption) {
  const auto kOrigin = url::Origin::Create(GURL(kTestOrigin));

  NavigateToUrlAndCreateCdmDocumentService(GURL(kTestOrigin));
  auto cdm_data = GetMediaFoundationCdmData();
  base::UnguessableToken origin_id = cdm_data->origin_id;

  base::FilePath cdm_data_file_path = CreateDummyCdmDataFile(
      cdm_data->cdm_store_path_root, cdm_data->origin_id);

  CorruptCdmPreference();

  base::UnguessableToken new_origin_id = GetMediaFoundationCdmData()->origin_id;
  ASSERT_NE(origin_id, new_origin_id);

  // Path should still exist even though prefs were corrupted.
  ASSERT_TRUE(base::PathExists(cdm_data_file_path));

  base::Time start = base::Time::Now() - base::Hours(1);
  base::Time end;  // null time

  base::RunLoop loop1;

  // With the filter returning true, the path should no longer exist.
  CdmDocumentServiceImpl::ClearCdmData(
      profile(), start, end,
      base::BindRepeating([](const GURL& url) { return true; }),
      loop1.QuitClosure());

  loop1.Run();

  // Path should no longer exist
  ASSERT_FALSE(base::PathExists(cdm_data_file_path));
}

// Check that we only clear the CDM preference that were set between start and
// end.
TEST_F(CdmDocumentServiceImplTest, ClearCdmPreferenceDataWrongTime) {
  const auto kOrigin = url::Origin::Create(GURL(kTestOrigin));

  NavigateToUrlAndCreateCdmDocumentService(GURL(kTestOrigin));
  auto cdm_data = GetMediaFoundationCdmData();
  base::UnguessableToken origin_id = cdm_data->origin_id;
  base::FilePath cdm_data_file_path =
      CreateDummyCdmDataFile(cdm_data->cdm_store_path_root, origin_id);

  base::Time start = base::Time::Now() - base::Hours(4);
  base::Time end = start - base::Hours(2);

  auto null_filter = base::RepeatingCallback<bool(const GURL&)>();

  base::RunLoop loop;

  CdmDocumentServiceImpl::ClearCdmData(profile(), start, end, null_filter,
                                       loop.QuitClosure());

  loop.Run();

  base::UnguessableToken new_origin_id = GetMediaFoundationCdmData()->origin_id;
  ASSERT_EQ(origin_id, new_origin_id);
  ASSERT_TRUE(base::PathExists(cdm_data_file_path));
}

TEST_F(CdmDocumentServiceImplTest, ClearCdmPreferenceDataNullFilter) {
  const auto kOrigin = url::Origin::Create(GURL(kTestOrigin));

  NavigateToUrlAndCreateCdmDocumentService(GURL(kTestOrigin));
  base::UnguessableToken origin_id_1 = GetMediaFoundationCdmData()->origin_id;

  NavigateToUrlAndCreateCdmDocumentService(GURL(kTestOrigin2));
  base::UnguessableToken origin_id_2 = GetMediaFoundationCdmData()->origin_id;

  base::Time start = base::Time::Now() - base::Hours(1);
  base::Time end;  // null time

  auto null_filter = base::RepeatingCallback<bool(const GURL&)>();

  base::RunLoop loop;

  CdmDocumentServiceImpl::ClearCdmData(profile(), start, end, null_filter,
                                       loop.QuitClosure());

  loop.Run();

  base::UnguessableToken new_origin_id = GetMediaFoundationCdmData()->origin_id;
  ASSERT_NE(origin_id_2, new_origin_id);

  NavigateToUrlAndCreateCdmDocumentService(GURL(kTestOrigin));
  new_origin_id = GetMediaFoundationCdmData()->origin_id;
  ASSERT_NE(origin_id_1, new_origin_id);
}

}  // namespace content