Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(200)

Side by Side Diff: media/cdm/cdm_adapter_unittest.cc

Issue 1428753010: Add unit tests for CdmAdapter (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: more tests Created 5 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « media/BUILD.gn ('k') | media/media.gyp » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2015 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 "base/bind.h"
6 #include "base/files/file_path.h"
7 #include "base/files/file_util.h"
8 #include "base/logging.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/path_service.h"
11 #include "base/run_loop.h"
12 #include "base/scoped_native_library.h"
13 #include "media/base/cdm_callback_promise.h"
14 #include "media/base/cdm_key_information.h"
15 #include "media/base/media_keys.h"
16 #include "media/cdm/api/content_decryption_module.h"
17 #include "media/cdm/cdm_adapter.h"
18 #include "testing/gmock/include/gmock/gmock.h"
19 #include "testing/gtest/include/gtest/gtest.h"
20
21 using ::testing::_;
22 MATCHER(IsNotEmpty, "") {
23 return !arg.empty();
24 }
25
26 // TODO(jrummell): These tests are a subset of those in aes_decryptor_unittest.
27 // Refactor aes_decryptor_unittest.cc to handle AesDecryptor directly and
28 // via CdmAdapter once CdmAdapter supports decrypting functionality. There
29 // will also be tests that only CdmAdapter supports, like file IO, which
30 // will need to be handled separately.
31
32 namespace media {
33
34 // INITIALIZE_CDM_MODULE is a macro in api/content_decryption_module.h.
35 // However, we need to pass it as a string to GetFunctionPointer() once it
36 // is expanded.
37 #define STRINGIFY(X) #X
38 #define MAKE_STRING(X) STRINGIFY(X)
39
40 const char kExternalClearKeyKeySystem[] = "org.chromium.externalclearkey";
41
42 // File name of the External ClearKey CDM on different platforms.
43 const base::FilePath::CharType kExternalClearKeyCdmFileName[] =
44 #if defined(OS_MACOSX)
45 FILE_PATH_LITERAL("libclearkeycdm.dylib");
46 #elif defined(OS_WIN)
47 FILE_PATH_LITERAL("clearkeycdm.dll");
48 #else // OS_LINUX, etc.
49 FILE_PATH_LITERAL("libclearkeycdm.so");
50 #endif
51
52 // Random key ID used to create a session.
53 const uint8 kKeyId[] = {
54 // base64 equivalent is AQIDBAUGBwgJCgsMDQ4PEA
55 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
56 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
57 };
58
59 const char kKeyIdAsJWK[] = "{\"kids\": [\"AQIDBAUGBwgJCgsMDQ4PEA\"]}";
60
61 const uint8 kKeyIdAsPssh[] = {
62 0x00, 0x00, 0x00, 0x00, 'p', 's', 's', 'h', // size = 0
63 0x01, // version = 1
64 0x00, 0x00, 0x00, // flags
65 0x10, 0x77, 0xEF, 0xEC, 0xC0, 0xB2, 0x4D, 0x02, // Common SystemID
66 0xAC, 0xE3, 0x3C, 0x1E, 0x52, 0xE2, 0xFB, 0x4B,
67 0x00, 0x00, 0x00, 0x01, // key count
68 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // key
69 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
70 0x00, 0x00, 0x00, 0x00, // datasize
71 };
72
73 // Key is 0x0405060708090a0b0c0d0e0f10111213,
74 // base64 equivalent is BAUGBwgJCgsMDQ4PEBESEw.
75 const char kKeyAsJWK[] =
76 "{"
77 " \"keys\": ["
78 " {"
79 " \"kty\": \"oct\","
80 " \"alg\": \"A128KW\","
81 " \"kid\": \"AQIDBAUGBwgJCgsMDQ4PEA\","
82 " \"k\": \"BAUGBwgJCgsMDQ4PEBESEw\""
83 " }"
84 " ],"
85 " \"type\": \"temporary\""
86 "}";
87
88 class CdmAdapterTest : public testing::Test {
89 public:
90 enum PromiseResult { RESOLVED, REJECTED };
91
92 CdmAdapterTest() { LoadAndInitializeCdm(); }
93
94 ~CdmAdapterTest() override { DeinitializeCdm(); }
xhwang 2015/11/10 22:58:58 Call Init and deinit in Setup() and Teardown()?
jrummell 2015/11/12 02:30:32 Done.
95
96 protected:
97 // Initializes the adapter. |expected_result| tests that the call succeeds
98 // or generates an error.
99 void InitializeAndExpect(base::FilePath library_path,
100 PromiseResult expected_result) {
101 CdmConfig cdm_config; // default settings of false are sufficient.
102
103 CdmAdapter::Create(
104 kExternalClearKeyKeySystem, library_path, cdm_config,
105 base::Bind(&CdmAdapterTest::OnSessionMessage, base::Unretained(this)),
106 base::Bind(&CdmAdapterTest::OnSessionClosed, base::Unretained(this)),
107 base::Bind(&CdmAdapterTest::OnLegacySessionError,
108 base::Unretained(this)),
109 base::Bind(&CdmAdapterTest::OnSessionKeysChange,
110 base::Unretained(this)),
111 base::Bind(&CdmAdapterTest::OnSessionExpirationUpdate,
112 base::Unretained(this)),
113 base::Bind(&CdmAdapterTest::OnCdmCreated, base::Unretained(this),
114 expected_result));
115 }
116
117 // Creates a new session using |key_id|. |session_id_| will be set
118 // when the promise is resolved. |expected_result| tests that
119 // CreateSessionAndGenerateRequest() succeeds or generates an error.
120 void CreateSessionAndExpect(EmeInitDataType data_type,
121 const std::vector<uint8>& key_id,
122 PromiseResult expected_result) {
123 DCHECK(!key_id.empty());
124
125 if (expected_result == RESOLVED) {
126 EXPECT_CALL(*this,
127 OnSessionMessage(IsNotEmpty(), _, _, GURL::EmptyGURL()));
128 }
129
130 adapter_->CreateSessionAndGenerateRequest(
131 MediaKeys::TEMPORARY_SESSION, data_type, key_id,
132 CreateSessionPromise(expected_result));
133 }
134
135 // Loads the session specified by |session_id|. |expected_result| tests
136 // that LoadSession() succeeds or generates an error.
137 void LoadSessionAndExpect(const std::string& session_id,
138 PromiseResult expected_result) {
139 DCHECK(!session_id.empty());
140 ASSERT_EQ(expected_result, REJECTED) << "LoadSession not supported.";
141
142 adapter_->LoadSession(MediaKeys::TEMPORARY_SESSION, session_id,
143 CreateSessionPromise(expected_result));
144 }
145
146 // Updates the session specified by |session_id| with |key|. |expected_result|
147 // tests that the update succeeds or generates an error. |new_key_expected|
148 // is the expected parameter when the SessionKeysChange event happens.
149 void UpdateSessionAndExpect(std::string session_id,
150 const std::string& key,
151 PromiseResult expected_result,
152 bool new_key_expected) {
153 DCHECK(!key.empty());
154
155 if (expected_result == RESOLVED) {
156 EXPECT_CALL(*this,
157 OnSessionKeysChangeCalled(session_id, new_key_expected));
158 } else {
159 EXPECT_CALL(*this, OnSessionKeysChangeCalled(_, _)).Times(0);
160 }
161
162 adapter_->UpdateSession(session_id,
163 std::vector<uint8>(key.begin(), key.end()),
164 CreatePromise(expected_result));
165 }
166
167 base::FilePath ExternalClearKeyLibrary() { return library_path_; }
168
169 std::string SessionId() { return session_id_; }
170
171 void RunUntilIdle() { message_loop_.RunUntilIdle(); }
172
173 private:
174 void LoadAndInitializeCdm() {
175 // Determine the location of the CDM. It is expected to be in the same
176 // directory as the current module.
177 base::FilePath current_module_dir;
178 ASSERT_TRUE(PathService::Get(base::DIR_MODULE, &current_module_dir));
179 library_path_ =
180 current_module_dir.Append(base::FilePath(kExternalClearKeyCdmFileName));
181 ASSERT_TRUE(base::PathExists(library_path_)) << library_path_.value();
182
183 // Now load the CDM library.
184 base::NativeLibraryLoadError error;
185 library_.Reset(base::LoadNativeLibrary(library_path_, &error));
186 ASSERT_TRUE(library_.is_valid()) << error.ToString();
187
188 // Call INITIALIZE_CDM_MODULE()
189 typedef void* (*InitializeCdmFunc)();
190 InitializeCdmFunc initialize_cdm_func = reinterpret_cast<InitializeCdmFunc>(
191 library_.GetFunctionPointer(MAKE_STRING(INITIALIZE_CDM_MODULE)));
192 ASSERT_TRUE(initialize_cdm_func) << "No INITIALIZE_CDM_MODULE in library";
193 initialize_cdm_func();
194 }
195
196 void DeinitializeCdm() {
197 // Call DeinitializeCdmModule()
198 typedef void* (*DeinitializeCdmFunc)();
199 DeinitializeCdmFunc deinitialize_cdm_func =
200 reinterpret_cast<DeinitializeCdmFunc>(
201 library_.GetFunctionPointer("DeinitializeCdmModule"));
202 ASSERT_TRUE(deinitialize_cdm_func)
203 << "No DeinitializeCdmModule() in library";
204 deinitialize_cdm_func();
205 }
206
207 void OnCdmCreated(PromiseResult expected_result,
xhwang 2015/11/10 22:58:58 CdmAdapter::Create() takes a CdmCreatedCB instead
jrummell 2015/11/12 02:30:32 Like SUCCESS/FAILURE better, since the methods are
208 const scoped_refptr<MediaKeys>& cdm,
209 const std::string& error_message) {
210 if (cdm) {
211 EXPECT_EQ(expected_result, RESOLVED) << "Unable to create CDM: "
212 << error_message;
213 adapter_ = cdm;
214 } else {
215 EXPECT_EQ(expected_result, REJECTED) << "CDM should not have loaded.";
216 }
217 }
218
219 // Called when a promise is resolved. |expected_result| should be RESOLVED.
220 void OnResolve(PromiseResult expected_result) {
221 EXPECT_EQ(expected_result, RESOLVED) << "Unexpectedly resolved.";
222 }
223
224 // Called when a new session promise is resolved. |expected_result|
225 // should be RESOLVED.
226 void OnResolveWithSession(PromiseResult expected_result,
227 const std::string& session_id) {
228 EXPECT_EQ(expected_result, RESOLVED) << "Unexpectedly resolved.";
229 EXPECT_GT(session_id.length(), 0ul);
230 session_id_ = session_id;
231 }
232
233 // Called when a promise fails. |expected_result| should be REJECTED if the
234 // failure is expected.
235 void OnReject(PromiseResult expected_result,
236 MediaKeys::Exception exception_code,
237 uint32 system_code,
238 const std::string& error_message) {
239 EXPECT_EQ(expected_result, REJECTED)
240 << "Unexpectedly rejected with message: " << error_message;
241 }
242
243 // Create a promise. |expected_result| is used to verify that the promise
244 // is expected to succeed or fail.
245 scoped_ptr<SimpleCdmPromise> CreatePromise(PromiseResult expected_result) {
246 scoped_ptr<SimpleCdmPromise> promise(new CdmCallbackPromise<>(
247 base::Bind(&CdmAdapterTest::OnResolve, base::Unretained(this),
248 expected_result),
249 base::Bind(&CdmAdapterTest::OnReject, base::Unretained(this),
250 expected_result)));
xhwang 2015/11/10 22:58:58 It's odd that you pass the same |expected_result|
jrummell 2015/11/12 02:30:32 Done.
251 return promise.Pass();
252 }
253
254 // Create a promise to be used when a new session is created.
255 // |expected_result| is used to verify that the promise is expected to
256 // succeed or fail.
257 scoped_ptr<NewSessionCdmPromise> CreateSessionPromise(
258 PromiseResult expected_result) {
259 scoped_ptr<NewSessionCdmPromise> promise(
260 new CdmCallbackPromise<std::string>(
261 base::Bind(&CdmAdapterTest::OnResolveWithSession,
262 base::Unretained(this), expected_result),
263 base::Bind(&CdmAdapterTest::OnReject, base::Unretained(this),
264 expected_result)));
265 return promise.Pass();
266 }
267
268 // Methods used for the events possibly generated by CdmAdapater.
269 MOCK_METHOD4(OnSessionMessage,
270 void(const std::string& session_id,
271 MediaKeys::MessageType message_type,
272 const std::vector<uint8_t>& message,
273 const GURL& legacy_destination_url));
274 MOCK_METHOD1(OnSessionClosed, void(const std::string& session_id));
275 MOCK_METHOD4(OnLegacySessionError,
276 void(const std::string& session_id,
277 MediaKeys::Exception exception,
278 uint32_t system_code,
279 const std::string& error_message));
280 MOCK_METHOD2(OnSessionKeysChangeCalled,
281 void(const std::string& session_id,
282 bool has_additional_usable_key));
283 void OnSessionKeysChange(const std::string& session_id,
284 bool has_additional_usable_key,
285 CdmKeysInfo keys_info) {
286 // MOCK methods don't like CdmKeysInfo.
287 OnSessionKeysChangeCalled(session_id, has_additional_usable_key);
288 }
289 MOCK_METHOD2(OnSessionExpirationUpdate,
290 void(const std::string& session_id,
291 const base::Time& new_expiry_time));
292
293 // Keep a reference to the CDM.
294 base::FilePath library_path_;
295 base::ScopedNativeLibrary library_;
296
297 scoped_refptr<MediaKeys> adapter_;
298
299 // |session_id_| is the latest result of calling CreateSession().
300 std::string session_id_;
301
302 base::MessageLoop message_loop_;
303
304 DISALLOW_COPY_AND_ASSIGN(CdmAdapterTest);
305 };
306
307 TEST_F(CdmAdapterTest, Initialize) {
308 InitializeAndExpect(ExternalClearKeyLibrary(), RESOLVED);
309 RunUntilIdle();
xhwang 2015/11/10 22:58:58 Can you call RunUntilIdle() within InitializeAndEx
jrummell 2015/11/12 02:30:32 Done.
310 }
311
312 TEST_F(CdmAdapterTest, BadLibraryPath) {
313 InitializeAndExpect(base::FilePath(FILE_PATH_LITERAL("no_library_here")),
314 REJECTED);
315 RunUntilIdle();
316 }
317
318 TEST_F(CdmAdapterTest, CreateWebmSession) {
319 InitializeAndExpect(ExternalClearKeyLibrary(), RESOLVED);
320 RunUntilIdle();
321
322 std::vector<uint8> key_id(kKeyId, kKeyId + arraysize(kKeyId));
323 CreateSessionAndExpect(EmeInitDataType::WEBM, key_id, RESOLVED);
324 RunUntilIdle();
325 }
326
327 TEST_F(CdmAdapterTest, CreateKeyIdsSession) {
328 InitializeAndExpect(ExternalClearKeyLibrary(), RESOLVED);
329 RunUntilIdle();
330
331 // Don't include the trailing /0 from the string in the data passed in.
332 std::vector<uint8> key_id(kKeyIdAsJWK,
333 kKeyIdAsJWK + arraysize(kKeyIdAsJWK) - 1);
334 CreateSessionAndExpect(EmeInitDataType::KEYIDS, key_id, RESOLVED);
335 RunUntilIdle();
336 }
337
338 TEST_F(CdmAdapterTest, CreateCencSession) {
339 InitializeAndExpect(ExternalClearKeyLibrary(), RESOLVED);
340 RunUntilIdle();
341
342 std::vector<uint8> key_id(kKeyIdAsPssh,
343 kKeyIdAsPssh + arraysize(kKeyIdAsPssh));
344 #if defined(USE_PROPRIETARY_CODECS)
345 CreateSessionAndExpect(EmeInitDataType::CENC, key_id, RESOLVED);
346 #else
347 CreateSessionAndExpect(EmeInitDataType::CENC, key_id, REJECTED);
348 #endif
349 RunUntilIdle();
350 }
351
352 TEST_F(CdmAdapterTest, CreateSessionWithBadData) {
353 InitializeAndExpect(ExternalClearKeyLibrary(), RESOLVED);
354 RunUntilIdle();
355
356 // Use |kKeyId| but specify KEYIDS format.
357 std::vector<uint8> key_id(kKeyId, kKeyId + arraysize(kKeyId));
358 CreateSessionAndExpect(EmeInitDataType::KEYIDS, key_id, REJECTED);
359 RunUntilIdle();
360 }
361
362 TEST_F(CdmAdapterTest, LoadSession) {
363 InitializeAndExpect(ExternalClearKeyLibrary(), RESOLVED);
364 RunUntilIdle();
365
366 // LoadSession() is not supported by AesDecryptor.
367 std::vector<uint8> key_id(kKeyId, kKeyId + arraysize(kKeyId));
368 CreateSessionAndExpect(EmeInitDataType::KEYIDS, key_id, REJECTED);
369 RunUntilIdle();
370 }
371
372 TEST_F(CdmAdapterTest, UpdateSession) {
373 InitializeAndExpect(ExternalClearKeyLibrary(), RESOLVED);
374 RunUntilIdle();
375
376 std::vector<uint8> key_id(kKeyId, kKeyId + arraysize(kKeyId));
377 CreateSessionAndExpect(EmeInitDataType::WEBM, key_id, RESOLVED);
378 RunUntilIdle();
379
380 UpdateSessionAndExpect(SessionId(), kKeyAsJWK, RESOLVED, true);
381 RunUntilIdle();
382 }
383
384 TEST_F(CdmAdapterTest, UpdateSessionWithBadData) {
385 InitializeAndExpect(ExternalClearKeyLibrary(), RESOLVED);
386 RunUntilIdle();
387
388 std::vector<uint8> key_id(kKeyId, kKeyId + arraysize(kKeyId));
389 CreateSessionAndExpect(EmeInitDataType::WEBM, key_id, RESOLVED);
390 RunUntilIdle();
391
392 UpdateSessionAndExpect(SessionId(), "random data", REJECTED, true);
393 RunUntilIdle();
394 }
395
396 } // namespace media
OLDNEW
« no previous file with comments | « media/BUILD.gn ('k') | media/media.gyp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698