diff options
author | xhwang@chromium.org <xhwang@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-12-20 07:26:18 +0000 |
---|---|---|
committer | xhwang@chromium.org <xhwang@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-12-20 07:26:18 +0000 |
commit | 34afd587d396b13defb7cf900caf3b67769d0b1e (patch) | |
tree | 33cd6c505b3ed7732c64f74cd1731d88c60c1c73 | |
parent | 19d068c3f5a0ea6385cb4cd228ce8be1f135cd3d (diff) | |
download | chromium_src-34afd587d396b13defb7cf900caf3b67769d0b1e.zip chromium_src-34afd587d396b13defb7cf900caf3b67769d0b1e.tar.gz chromium_src-34afd587d396b13defb7cf900caf3b67769d0b1e.tar.bz2 |
Add CDM FileIO tests.
- Add CdmFileIOTest, which tests CdmFileIO in ClearKeyCdm.
- Update EncryptedMediaTest to check the result of CdmFileIOTest.
BUG=324134
TEST=Tests added pass on Linux.
Review URL: https://codereview.chromium.org/93243003
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@242027 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/media/encrypted_media_browsertest.cc | 15 | ||||
-rw-r--r-- | chrome/renderer/media/chrome_key_systems.cc | 14 | ||||
-rw-r--r-- | chrome/test/data/media/encrypted_media_utils.js | 49 | ||||
-rw-r--r-- | media/cdm/ppapi/cdm_file_io_test.cc | 437 | ||||
-rw-r--r-- | media/cdm/ppapi/cdm_file_io_test.h | 157 | ||||
-rw-r--r-- | media/cdm/ppapi/clear_key_cdm.cc | 49 | ||||
-rw-r--r-- | media/cdm/ppapi/clear_key_cdm.h | 15 | ||||
-rw-r--r-- | media/media_cdm.gypi | 2 |
8 files changed, 719 insertions, 19 deletions
diff --git a/chrome/browser/media/encrypted_media_browsertest.cc b/chrome/browser/media/encrypted_media_browsertest.cc index 14ebbb0..bedf5b4 100644 --- a/chrome/browser/media/encrypted_media_browsertest.cc +++ b/chrome/browser/media/encrypted_media_browsertest.cc @@ -36,6 +36,8 @@ const char kClearKeyKeySystem[] = "webkit-org.w3.clearkey"; const char kExternalClearKeyKeySystem[] = "org.chromium.externalclearkey"; const char kExternalClearKeyDecryptOnlyKeySystem[] = "org.chromium.externalclearkey.decryptonly"; +const char kExternalClearKeyFileIOTestKeySystem[] = + "org.chromium.externalclearkey.fileiotest"; const char kExternalClearKeyInitializeFailKeySystem[] = "org.chromium.externalclearkey.initializefail"; @@ -51,6 +53,7 @@ const char kMP4VideoOnly[] = "video/mp4; codecs=\"avc1.4D4041\""; // EME-specific test results and errors. const char kEmeKeyError[] = "KEYERROR"; const char kEmeNotSupportedError[] = "NOTSUPPORTEDERROR"; +const char kFileIOTestSuccess[] = "FILEIOTESTSUCCESS"; // The type of video src used to load media. enum SrcType { @@ -388,8 +391,7 @@ IN_PROC_BROWSER_TEST_F(WVEncryptedMediaTest, ParentThrowsException) { #endif // defined(WIDEVINE_CDM_AVAILABLE) #if defined(ENABLE_PEPPER_CDMS) -IN_PROC_BROWSER_TEST_F(ECKEncryptedMediaTest, - ExternalClearKeyInitializeCDMFail) { +IN_PROC_BROWSER_TEST_F(ECKEncryptedMediaTest, InitializeCDMFail) { RunEncryptedMediaTest("encrypted_media_player.html", "bear-a-enc_a.webm", kWebMAudioOnly, @@ -397,4 +399,13 @@ IN_PROC_BROWSER_TEST_F(ECKEncryptedMediaTest, SRC, kEmeKeyError); } + +IN_PROC_BROWSER_TEST_F(ECKEncryptedMediaTest, FileIOTest) { + RunEncryptedMediaTest("encrypted_media_player.html", + "bear-a-enc_a.webm", + kWebMAudioOnly, + kExternalClearKeyFileIOTestKeySystem, + SRC, + kFileIOTestSuccess); +} #endif // defined(ENABLE_PEPPER_CDMS) diff --git a/chrome/renderer/media/chrome_key_systems.cc b/chrome/renderer/media/chrome_key_systems.cc index 2b4ba65..bb59fae 100644 --- a/chrome/renderer/media/chrome_key_systems.cc +++ b/chrome/renderer/media/chrome_key_systems.cc @@ -66,6 +66,8 @@ static void AddExternalClearKey( "org.chromium.externalclearkey"; static const char kExternalClearKeyDecryptOnlyKeySystem[] = "org.chromium.externalclearkey.decryptonly"; + static const char kExternalClearKeyFileIOTestKeySystem[] = + "org.chromium.externalclearkey.fileiotest"; static const char kExternalClearKeyInitializeFailKeySystem[] = "org.chromium.externalclearkey.initializefail"; static const char kExternalClearKeyPepperType[] = @@ -91,15 +93,19 @@ static void AddExternalClearKey( concrete_key_systems->push_back(info); + // Add support of decrypt-only mode in ClearKeyCdm. + info.key_system = kExternalClearKeyDecryptOnlyKeySystem; + concrete_key_systems->push_back(info); + + // A key system that triggers FileIO test in ClearKeyCdm. + info.key_system = kExternalClearKeyFileIOTestKeySystem; + concrete_key_systems->push_back(info); + // A key system that Chrome thinks is supported by ClearKeyCdm, but actually // will be refused by ClearKeyCdm. This is to test the CDM initialization // failure case. info.key_system = kExternalClearKeyInitializeFailKeySystem; concrete_key_systems->push_back(info); - - // Add support of decrypt-only mode in ClearKeyCdm. - info.key_system = kExternalClearKeyDecryptOnlyKeySystem; - concrete_key_systems->push_back(info); } #endif // defined(ENABLE_PEPPER_CDMS) diff --git a/chrome/test/data/media/encrypted_media_utils.js b/chrome/test/data/media/encrypted_media_utils.js index 3486197..2848052 100644 --- a/chrome/test/data/media/encrypted_media_utils.js +++ b/chrome/test/data/media/encrypted_media_utils.js @@ -14,21 +14,32 @@ var KEY = new Uint8Array([0xeb, 0xdd, 0x62, 0xf1, 0x68, 0x14, 0xd2, 0x7b, var KEY_ID = getInitDataFromKeyId("0123456789012345"); // Heart beat message header. var HEART_BEAT_HEADER = 'HEARTBEAT'; +var FILE_IO_TEST_RESULT_HEADER = 'FILEIOTESTRESULT'; var EXTERNAL_CLEAR_KEY_KEY_SYSTEM = "org.chromium.externalclearkey"; +var EXTERNAL_CLEAR_KEY_FILE_IO_TEST_KEY_SYSTEM = + "org.chromium.externalclearkey.fileiotest"; // Note that his URL has been normalized from the one in clear_key_cdm.cc. var EXTERNAL_CLEAR_KEY_HEARTBEAT_URL = 'http://test.externalclearkey.chromium.org/'; -function isHeartbeatMessage(msg) { - if (msg.length < HEART_BEAT_HEADER.length) +function hasPrefix(msg, prefix) { + if (msg.length < prefix.length) return false; - for (var i = 0; i < HEART_BEAT_HEADER.length; ++i) { - if (String.fromCharCode(msg[i]) != HEART_BEAT_HEADER[i]) + for (var i = 0; i < prefix.length; ++i) { + if (String.fromCharCode(msg[i]) != prefix[i]) return false; } return true; } +function isHeartbeatMessage(msg) { + return hasPrefix(msg, HEART_BEAT_HEADER); +} + +function isFileIOTestMessage(msg) { + return hasPrefix(msg, FILE_IO_TEST_RESULT_HEADER); +} + function loadEncryptedMediaFromURL(video) { return loadEncryptedMedia(video, mediaFile, keySystem, KEY, useMSE); } @@ -89,6 +100,21 @@ function loadEncryptedMedia(video, mediaFile, keySystem, key, useMSE, return; } + if (isFileIOTestMessage(e.message)) { + var success = getFileIOTestResult(e); + console.log('onKeyMessage - CDM file IO test: ' + + (success ? 'Success' : 'Fail')); + if (success) + setResultInTitle("FILEIOTESTSUCCESS"); + else + setResultInTitle("FAILED"); + return; + } + + // For FileIOTest key system, no need to start playback. + if (e.keySystem == EXTERNAL_CLEAR_KEY_FILE_IO_TEST_KEY_SYSTEM) + return; + // No tested key system returns defaultURL in for key request messages. if (e.defaultURL) { failTest('keymessage unexpectedly has defaultURL: ' + e.defaultURL); @@ -125,6 +151,21 @@ function loadEncryptedMedia(video, mediaFile, keySystem, key, useMSE, } } + function getFileIOTestResult(e) { + // Only External Clear Key sends a FILEIOTESTRESULT message. + if (e.keySystem != EXTERNAL_CLEAR_KEY_FILE_IO_TEST_KEY_SYSTEM) { + failTest('Unexpected CDM file IO test result from ' + e.keySystem); + return false; + } + + // The test result is either '0' or '1' appended to the header. + if (e.message.length != FILE_IO_TEST_RESULT_HEADER.length + 1) + return false; + + var result_index = FILE_IO_TEST_RESULT_HEADER.length; + return String.fromCharCode(e.message[result_index]) == 1; + } + video.addEventListener('webkitneedkey', onNeedKey); video.addEventListener('webkitkeymessage', onKeyMessage); video.addEventListener('webkitkeyerror', function() { diff --git a/media/cdm/ppapi/cdm_file_io_test.cc b/media/cdm/ppapi/cdm_file_io_test.cc new file mode 100644 index 0000000..5bbcd8e --- /dev/null +++ b/media/cdm/ppapi/cdm_file_io_test.cc @@ -0,0 +1,437 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/cdm/ppapi/cdm_file_io_test.h" + +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "base/logging.h" + +namespace media { + +#define FILE_IO_DVLOG(level) DVLOG(level) << "File IO Test: " + +const uint8 kData[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }; +const uint32 kDataSize = arraysize(kData); + +const uint8 kBigData[] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, + 0x00 }; +const uint32 kBigDataSize = arraysize(kBigData); + +// Must be > kReadSize in cdm_file_io_impl.cc. +const uint32 kLargeDataSize = 9 * 1024 + 7; + +// Macros to help add test cases/steps. +#define START_TEST_CASE(test_name) \ + do { \ + FileIOTest test_case(create_file_io_cb_, "FileIOTest." test_name); \ + CREATE_FILE_IO // Create FileIO for each test case. + +#define ADD_TEST_STEP(type, status, data, data_size) \ + test_case.AddTestStep(FileIOTest::type, cdm::FileIOClient::status, \ + (data), (data_size)); + +#define END_TEST_CASE \ + remaining_tests_.push_back(test_case); \ + } while(0); + +#define CREATE_FILE_IO \ + ADD_TEST_STEP(ACTION_CREATE, kSuccess, NULL, 0) + +#define OPEN_FILE \ + ADD_TEST_STEP(ACTION_OPEN, kSuccess, NULL, 0) + +#define EXPECT_FILE_OPENED(status) \ + ADD_TEST_STEP(RESULT_OPEN, status, NULL, 0) + +#define READ_FILE \ + ADD_TEST_STEP(ACTION_READ, kSuccess, NULL, 0) + +#define EXPECT_FILE_READ(status, data, data_size) \ + ADD_TEST_STEP(RESULT_READ, status, data, data_size) + +#define WRITE_FILE(data, data_size) \ + ADD_TEST_STEP(ACTION_WRITE, kSuccess, data, data_size) + +#define EXPECT_FILE_WRITTEN(status) \ + ADD_TEST_STEP(RESULT_WRITE, status, NULL, 0) + +#define CLOSE_FILE \ + ADD_TEST_STEP(ACTION_CLOSE, kSuccess, NULL, 0) + +// FileIOTestRunner implementation. + +FileIOTestRunner::FileIOTestRunner(const CreateFileIOCB& create_file_io_cb) + : create_file_io_cb_(create_file_io_cb), + total_num_tests_(0), + num_passed_tests_(0) { + // Generate |large_data_|. + large_data_.resize(kLargeDataSize); + for (size_t i = 0; i < kLargeDataSize; ++i) + large_data_[i] = i % kuint8max; + + AddTests(); +} + +FileIOTestRunner::~FileIOTestRunner() { + if (remaining_tests_.empty()) + return; + + DCHECK_LT(num_passed_tests_, total_num_tests_); + FILE_IO_DVLOG(1) << "Not Finished (probably due to timeout). " + << num_passed_tests_ << " passed in " + << total_num_tests_ << " tests."; +} + +// Note: Consecutive expectations (EXPECT*) can happen in any order. +void FileIOTestRunner::AddTests() { + START_TEST_CASE("ReadBeforeOpeningFile") + READ_FILE + EXPECT_FILE_READ(kError, NULL, 0) + END_TEST_CASE + + START_TEST_CASE("WriteBeforeOpeningFile") + WRITE_FILE(kData, kDataSize) + EXPECT_FILE_WRITTEN(kError) + END_TEST_CASE + + START_TEST_CASE("ReadBeforeFileOpened") + OPEN_FILE + READ_FILE + EXPECT_FILE_OPENED(kSuccess) + EXPECT_FILE_READ(kError, NULL, 0) + END_TEST_CASE + + START_TEST_CASE("WriteBeforeFileOpened") + OPEN_FILE + WRITE_FILE(kData, kDataSize) + EXPECT_FILE_WRITTEN(kError) + EXPECT_FILE_OPENED(kSuccess) + END_TEST_CASE + + START_TEST_CASE("ReadDuringPendingRead") + OPEN_FILE + EXPECT_FILE_OPENED(kSuccess) + WRITE_FILE(kData, kDataSize) + EXPECT_FILE_WRITTEN(kSuccess) + READ_FILE + READ_FILE + EXPECT_FILE_READ(kInUse, NULL, 0) + EXPECT_FILE_READ(kSuccess, kData, kDataSize) + END_TEST_CASE + + START_TEST_CASE("ReadDuringPendingWrite") + OPEN_FILE + EXPECT_FILE_OPENED(kSuccess) + WRITE_FILE(kData, kDataSize) + READ_FILE + EXPECT_FILE_READ(kInUse, NULL, 0) + EXPECT_FILE_WRITTEN(kSuccess) + END_TEST_CASE + + START_TEST_CASE("WriteDuringPendingRead") + OPEN_FILE + EXPECT_FILE_OPENED(kSuccess) + READ_FILE + WRITE_FILE(kData, kDataSize) + EXPECT_FILE_WRITTEN(kInUse) + EXPECT_FILE_READ(kSuccess, NULL, 0) + END_TEST_CASE + + START_TEST_CASE("WriteDuringPendingWrite") + OPEN_FILE + EXPECT_FILE_OPENED(kSuccess) + WRITE_FILE(kData, kDataSize) + WRITE_FILE(kBigData, kBigDataSize) + EXPECT_FILE_WRITTEN(kInUse) + EXPECT_FILE_WRITTEN(kSuccess) + END_TEST_CASE + + START_TEST_CASE("ReadEmptyFile") + OPEN_FILE + EXPECT_FILE_OPENED(kSuccess) + READ_FILE + EXPECT_FILE_READ(kSuccess, NULL, 0) + END_TEST_CASE + + START_TEST_CASE("WriteAndRead") + OPEN_FILE + EXPECT_FILE_OPENED(kSuccess) + WRITE_FILE(kData, kDataSize) + EXPECT_FILE_WRITTEN(kSuccess) + READ_FILE + EXPECT_FILE_READ(kSuccess, kData, kDataSize) + END_TEST_CASE + + START_TEST_CASE("WriteZeroBytes") + OPEN_FILE + EXPECT_FILE_OPENED(kSuccess) + WRITE_FILE(NULL, 0) + EXPECT_FILE_WRITTEN(kSuccess) + READ_FILE + EXPECT_FILE_READ(kSuccess, NULL, 0) + END_TEST_CASE + + START_TEST_CASE("WriteAndReadLargeData") + OPEN_FILE + EXPECT_FILE_OPENED(kSuccess) + WRITE_FILE(&large_data_[0], kLargeDataSize) + EXPECT_FILE_WRITTEN(kSuccess) + READ_FILE + EXPECT_FILE_READ(kSuccess, &large_data_[0], kLargeDataSize) + END_TEST_CASE + + START_TEST_CASE("OverwriteZeroBytes") + OPEN_FILE + EXPECT_FILE_OPENED(kSuccess) + WRITE_FILE(kData, kDataSize) + EXPECT_FILE_WRITTEN(kSuccess) + READ_FILE + EXPECT_FILE_READ(kSuccess, kData, kDataSize) + WRITE_FILE(NULL, 0) + EXPECT_FILE_WRITTEN(kSuccess) + READ_FILE + EXPECT_FILE_READ(kSuccess, NULL, 0) + END_TEST_CASE + + START_TEST_CASE("OverwriteWithSmallerData") + OPEN_FILE + EXPECT_FILE_OPENED(kSuccess) + WRITE_FILE(kBigData, kBigDataSize) + EXPECT_FILE_WRITTEN(kSuccess) + WRITE_FILE(kData, kDataSize) + EXPECT_FILE_WRITTEN(kSuccess) + READ_FILE + EXPECT_FILE_READ(kSuccess, kData, kDataSize) + END_TEST_CASE + + START_TEST_CASE("OverwriteWithLargerData") + OPEN_FILE + EXPECT_FILE_OPENED(kSuccess) + WRITE_FILE(kData, kDataSize) + EXPECT_FILE_WRITTEN(kSuccess) + WRITE_FILE(kBigData, kBigDataSize) + EXPECT_FILE_WRITTEN(kSuccess) + READ_FILE + EXPECT_FILE_READ(kSuccess, kBigData, kBigDataSize) + END_TEST_CASE + + START_TEST_CASE("ReadExistingFile") + OPEN_FILE + EXPECT_FILE_OPENED(kSuccess) + WRITE_FILE(kData, kDataSize) + EXPECT_FILE_WRITTEN(kSuccess) + CLOSE_FILE + CREATE_FILE_IO + OPEN_FILE + EXPECT_FILE_OPENED(kSuccess) + READ_FILE + EXPECT_FILE_READ(kSuccess, kData, kDataSize) + END_TEST_CASE + + START_TEST_CASE("ReopenFileInTheSameFileIO") + OPEN_FILE + OPEN_FILE + EXPECT_FILE_OPENED(kError) // The second Open() failed. + EXPECT_FILE_OPENED(kSuccess) // The first Open() succeeded. + END_TEST_CASE + + // TODO(xhwang): This test should fail. But pp::FileIO doesn't support locking + // of opened files. We need to either workaround this or fix pp::FileIO + // implementation. + START_TEST_CASE("ReopenFileInSeparateFileIO") + OPEN_FILE + EXPECT_FILE_OPENED(kSuccess) + WRITE_FILE(kData, kDataSize) + EXPECT_FILE_WRITTEN(kSuccess) + CREATE_FILE_IO // Create a second FileIO without closing the first one. + OPEN_FILE + EXPECT_FILE_OPENED(kSuccess) + READ_FILE + EXPECT_FILE_READ(kSuccess, kData, kDataSize) + WRITE_FILE(kBigData, kBigDataSize) + EXPECT_FILE_WRITTEN(kSuccess) + END_TEST_CASE +} + +void FileIOTestRunner::RunAllTests(const CompletionCB& completion_cb) { + completion_cb_ = completion_cb; + total_num_tests_ = remaining_tests_.size(); + RunNextTest(); +} + +void FileIOTestRunner::RunNextTest() { + if (remaining_tests_.empty()) { + FILE_IO_DVLOG(1) << num_passed_tests_ << " passed and " + << (total_num_tests_ - num_passed_tests_) << " failed in " + << total_num_tests_ << " tests."; + bool success = (num_passed_tests_ == total_num_tests_); + base::ResetAndReturn(&completion_cb_).Run(success); + return; + } + + remaining_tests_.front().Run( + base::Bind(&FileIOTestRunner::OnTestComplete, base::Unretained(this))); +} + +void FileIOTestRunner::OnTestComplete(bool success) { + if (success) + num_passed_tests_++; + remaining_tests_.pop_front(); + RunNextTest(); +} + +// FileIOTest implementation. + +FileIOTest::FileIOTest(const CreateFileIOCB& create_file_io_cb, + const std::string& test_name) + : create_file_io_cb_(create_file_io_cb), + test_name_(test_name) {} + +FileIOTest::~FileIOTest() {} + +void FileIOTest::AddTestStep( + StepType type, Status status, const uint8* data, uint32 data_size) { + test_steps_.push_back(TestStep(type, status, data, data_size)); +} + +void FileIOTest::Run(const CompletionCB& completion_cb) { + FILE_IO_DVLOG(3) << "Run " << test_name_; + completion_cb_ = completion_cb; + DCHECK(!test_steps_.empty() && !IsResult(test_steps_.front())); + RunNextStep(); +} + +void FileIOTest::OnOpenComplete(Status status) { + OnResult(TestStep(RESULT_OPEN, status, NULL, 0)); +} + +void FileIOTest::OnReadComplete(Status status, + const uint8_t* data, + uint32_t data_size) { + OnResult(TestStep(RESULT_READ, status, data, data_size)); +} + +void FileIOTest::OnWriteComplete(Status status) { + OnResult(TestStep(RESULT_WRITE, status, NULL, 0)); +} + +bool FileIOTest::IsResult(const TestStep& test_step) { + switch (test_step.type) { + case RESULT_OPEN: + case RESULT_READ: + case RESULT_WRITE: + return true; + case ACTION_CREATE: + case ACTION_OPEN: + case ACTION_READ: + case ACTION_WRITE: + case ACTION_CLOSE: + return false; + } + NOTREACHED(); + return false; +} + +bool FileIOTest::MatchesResult(const TestStep& a, const TestStep& b) { + DCHECK(IsResult(a) && IsResult(b)); + if (a.type != b.type || a.status != b.status) + return false; + + if (a.type != RESULT_READ || a.status != cdm::FileIOClient::kSuccess) + return true; + + return (a.data_size == a.data_size && + std::equal(a.data, a.data + a.data_size, b.data)); +} + +void FileIOTest::RunNextStep() { + // Run all actions in the current action group. + while (!test_steps_.empty()) { + // Start to wait for test results when the next step is a test result. + if (IsResult(test_steps_.front())) + return; + + TestStep test_step = test_steps_.front(); + test_steps_.pop_front(); + + cdm::FileIO* file_io = file_io_stack_.empty()? NULL : file_io_stack_.top(); + + switch (test_step.type) { + case ACTION_CREATE: + file_io = create_file_io_cb_.Run(this); + if (!file_io) { + FILE_IO_DVLOG(3) << "Cannot create FileIO object."; + OnTestComplete(false); + return; + } + file_io_stack_.push(file_io); + break; + case ACTION_OPEN: + // Use test name as the test file name. + file_io->Open(test_name_.data(), test_name_.size()); + break; + case ACTION_READ: + file_io->Read(); + break; + case ACTION_WRITE: + file_io->Write(test_step.data, test_step.data_size); + break; + case ACTION_CLOSE: + file_io->Close(); + file_io_stack_.pop(); + break; + default: + NOTREACHED(); + } + } + + OnTestComplete(true); +} + +void FileIOTest::OnResult(const TestStep& result) { + DCHECK(IsResult(result)); + if (!CheckResult(result)) { + OnTestComplete(false); + return; + } + + RunNextStep(); +} + +bool FileIOTest::CheckResult(const TestStep& result) { + if (test_steps_.empty() || !IsResult(test_steps_.front())) + return false; + + // If there are multiple results expected, the order does not matter. + std::list<TestStep>::iterator iter = test_steps_.begin(); + for (; iter != test_steps_.end(); ++iter) { + if (!IsResult(*iter)) + return false; + + if (!MatchesResult(*iter, result)) + continue; + + test_steps_.erase(iter); + return true; + } + + return false; +} + +void FileIOTest::OnTestComplete(bool success) { + while (!file_io_stack_.empty()) { + cdm::FileIO* file_io = file_io_stack_.top(); + file_io->Close(); + file_io_stack_.pop(); + } + FILE_IO_DVLOG(3) << test_name_ << (success ? " PASSED" : " FAILED"); + base::ResetAndReturn(&completion_cb_).Run(success); +} + +} // namespace media diff --git a/media/cdm/ppapi/cdm_file_io_test.h b/media/cdm/ppapi/cdm_file_io_test.h new file mode 100644 index 0000000..3e0060c --- /dev/null +++ b/media/cdm/ppapi/cdm_file_io_test.h @@ -0,0 +1,157 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_CDM_PPAPI_CDM_FILE_IO_TEST_H_ +#define MEDIA_CDM_PPAPI_CDM_FILE_IO_TEST_H_ + +#include <list> +#include <stack> +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "media/cdm/ppapi/api/content_decryption_module.h" + +namespace media { + +typedef base::Callback<void(bool success)> CompletionCB; +typedef base::Callback<cdm::FileIO*(cdm::FileIOClient* client)> CreateFileIOCB; + +// A customizable test class that tests cdm::FileIO implementation. +// - To create a test, call AddTestStep() to add a test step. A test step can be +// either an action to make (use ACTION_* types), or a result to verify (use +// RESULT_* types). +// - To run the test, simply call Run() with a completion callback. The result +// will be reported in the completion callback when the test is finished. +// +// The following rules apply to the test steps: +// - Test steps are ordered (with the exception that results in a result group +// is not ordered). +// - Consecutive action steps form an "action group". Consecutively result +// steps form a "result group". An action group is followed by a result +// group and vice versa. +// - A test must start with an action group. +// - To process an action group, the test runner runs (and clears) all steps +// in the group in the order they were added. Then it waits for test +// results. +// - When a cdm::FileIOClient method is called, the test runner compares the +// result with all results in the current result group. If no result in that +// group matches the test result, the test fails. Otherwise, the matching +// result is cleared from the group. If the group is empty, the test runner +// starts to process the next action group. Otherwise, the test runner keeps +// waiting for the next test result. +// - After all steps are cleared, the test passes. +class FileIOTest : public cdm::FileIOClient { + public: + // Types of allowed test steps: + // - ACTION_* specifies the next step to test. + // - RESULT_* specifies the expected result of the previous step(s). + enum StepType { + ACTION_CREATE, + ACTION_OPEN, // |test_name_| will be used used as the file name to open. + RESULT_OPEN, + ACTION_READ, + RESULT_READ, + ACTION_WRITE, + RESULT_WRITE, + ACTION_CLOSE // If ACTION_CLOSE is not specified, FileIO::Close() will be + // automatically called at the end of the test. + }; + + FileIOTest(const CreateFileIOCB& create_file_io_cb, + const std::string& test_name); + ~FileIOTest(); + + // Adds a test step in this test. |this| object doesn't take the ownership of + // |data|, which should be valid throughout the lifetime of |this| object. + void AddTestStep( + StepType type, Status status, const uint8* data, uint32 data_size); + + // Runs this test case and returns the test result through |completion_cb|. + void Run(const CompletionCB& completion_cb); + + private: + struct TestStep { + // |this| object doesn't take the ownership of |data|, which should be valid + // throughout the lifetime of |this| object. + TestStep(StepType type, Status status, const uint8* data, uint32 data_size) + : type(type), status(status), data(data), data_size(data_size) {} + + StepType type; + + // Expected status for RESULT* steps. + Status status; + + // Data to write in ACTION_WRITE, or read data in RESULT_READ. + const uint8* data; + uint32 data_size; + }; + + // Returns whether |test_step| is a RESULT_* step. + static bool IsResult(const TestStep& test_step); + + // Returns whether two results match. + static bool MatchesResult(const TestStep& a, const TestStep& b); + + // cdm::FileIOClient implementation. + virtual void OnOpenComplete(Status status) OVERRIDE; + virtual void OnReadComplete(Status status, + const uint8_t* data, + uint32_t data_size) OVERRIDE; + virtual void OnWriteComplete(Status status) OVERRIDE; + + // Runs the next step in this test case. + void RunNextStep(); + + void OnResult(const TestStep& result); + + // Checks whether the test result matches this step. This can only be called + // when this step is a RESULT_* step. + bool CheckResult(const TestStep& result); + + void OnTestComplete(bool success); + + CreateFileIOCB create_file_io_cb_; + CompletionCB completion_cb_; + + std::string test_name_; + std::list<TestStep> test_steps_; + + // All opened cdm::FileIO objects. We keep multiple cdm::FileIO objects open + // so that we can test multiple cdm::FileIO objects accessing the same file. + // In the current implementation, all ACTION_* are performed on the latest + // opened cdm::FileIO object, hence the stack. + std::stack<cdm::FileIO*> file_io_stack_; +}; + +// Tests cdm::FileIO implementation. +class FileIOTestRunner { + public: + explicit FileIOTestRunner(const CreateFileIOCB& create_file_io_cb); + ~FileIOTestRunner(); + + void AddTests(); + + // Run all tests. When tests are completed, the result will be reported in the + // |completion_cb|. + void RunAllTests(const CompletionCB& completion_cb); + + private: + void OnTestComplete(bool success); + void RunNextTest(); + + CreateFileIOCB create_file_io_cb_; + CompletionCB completion_cb_; + std::list<FileIOTest> remaining_tests_; + std::vector<uint8> large_data_; + size_t total_num_tests_; // Total number of tests. + size_t num_passed_tests_; // Number of passed tests. + + DISALLOW_COPY_AND_ASSIGN (FileIOTestRunner); +}; + +} // namespace media + +#endif // MEDIA_CDM_PPAPI_CDM_FILE_IO_TEST_H_ diff --git a/media/cdm/ppapi/clear_key_cdm.cc b/media/cdm/ppapi/clear_key_cdm.cc index 16a00a5..ea89e82 100644 --- a/media/cdm/ppapi/clear_key_cdm.cc +++ b/media/cdm/ppapi/clear_key_cdm.cc @@ -15,6 +15,7 @@ #include "base/time/time.h" #include "media/base/decoder_buffer.h" #include "media/base/decrypt_config.h" +#include "media/cdm/ppapi/cdm_file_io_test.h" #include "media/cdm/ppapi/cdm_video_decoder.h" #if defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER) @@ -63,6 +64,8 @@ const char kClearKeyCdmVersion[] = "0.1.0.1"; const char kExternalClearKeyKeySystem[] = "org.chromium.externalclearkey"; const char kExternalClearKeyDecryptOnlyKeySystem[] = "org.chromium.externalclearkey.decryptonly"; +const char kExternalClearKeyFileIOTestKeySystem[] = + "org.chromium.externalclearkey.fileiotest"; const int64 kSecondsPerMinute = 60; const int64 kMsPerSecond = 1000; const int64 kInitialTimerDelayMs = 200; @@ -70,6 +73,8 @@ const int64 kMaxTimerDelayMs = 1 * kSecondsPerMinute * kMsPerSecond; // Heart beat message header. If a key message starts with |kHeartBeatHeader|, // it's a heart beat message. Otherwise, it's a key request. const char kHeartBeatHeader[] = "HEARTBEAT"; +// CDM file IO test result header. +const char kFileIOTestResultHeader[] = "FILEIOTESTRESULT"; // Copies |input_buffer| into a media::DecoderBuffer. If the |input_buffer| is // empty, an empty (end-of-stream) media::DecoderBuffer is returned. @@ -107,6 +112,12 @@ static scoped_refptr<media::DecoderBuffer> CopyDecoderBufferFrom( return output_buffer; } +static std::string GetFileIOTestResultMessage(bool success) { + std::string message(kFileIOTestResultHeader); + message += success ? '1' : '0'; + return message; +} + template<typename Type> class ScopedResetter { public: @@ -135,7 +146,8 @@ void* CreateCdmInstance(int cdm_interface_version, std::string key_system_string(key_system, key_system_size); if (key_system_string != kExternalClearKeyKeySystem && - key_system_string != kExternalClearKeyDecryptOnlyKeySystem) { + key_system_string != kExternalClearKeyDecryptOnlyKeySystem && + key_system_string != kExternalClearKeyFileIOTestKeySystem) { DVLOG(1) << "Unsupported key system:" << key_system_string; return NULL; } @@ -149,7 +161,9 @@ void* CreateCdmInstance(int cdm_interface_version, return NULL; return new media::ClearKeyCdm( - host, key_system_string == kExternalClearKeyDecryptOnlyKeySystem); + host, + key_system_string == kExternalClearKeyDecryptOnlyKeySystem, + key_system_string == kExternalClearKeyFileIOTestKeySystem); } const char* GetCdmVersion() { @@ -158,7 +172,9 @@ const char* GetCdmVersion() { namespace media { -ClearKeyCdm::ClearKeyCdm(ClearKeyCdmHost* host, bool is_decrypt_only) +ClearKeyCdm::ClearKeyCdm(ClearKeyCdmHost* host, + bool is_decrypt_only, + bool should_test_file_io) : decryptor_( base::Bind(&ClearKeyCdm::OnSessionCreated, base::Unretained(this)), base::Bind(&ClearKeyCdm::OnSessionMessage, base::Unretained(this)), @@ -167,7 +183,8 @@ ClearKeyCdm::ClearKeyCdm(ClearKeyCdmHost* host, bool is_decrypt_only) base::Bind(&ClearKeyCdm::OnSessionError, base::Unretained(this))), host_(host), is_decrypt_only_(is_decrypt_only), - heartbeat_session_id_(0), + should_test_file_io_(should_test_file_io), + last_session_id_(MediaKeys::kInvalidSessionId), timer_delay_ms_(kInitialTimerDelayMs), timer_set_(false) { #if defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER) @@ -190,8 +207,11 @@ void ClearKeyCdm::CreateSession(uint32 session_id, decryptor_.CreateSession( session_id, std::string(type, type_size), init_data, init_data_size); - // Only save the latest session ID for heartbeat messages. - heartbeat_session_id_ = session_id; + // Save the latest session ID for heartbeat and file IO test messages. + last_session_id_ = session_id; + + if (should_test_file_io_) + StartFileIOTest(); } void ClearKeyCdm::UpdateSession(uint32 session_id, @@ -224,7 +244,7 @@ void ClearKeyCdm::TimerExpired(void* context) { // There is no service at this URL, so applications should ignore it. const char url[] = "http://test.externalclearkey.chromium.org"; - host_->OnSessionMessage(heartbeat_session_id_, + host_->OnSessionMessage(last_session_id_, heartbeat_message.data(), heartbeat_message.size(), url, @@ -556,4 +576,19 @@ cdm::Status ClearKeyCdm::GenerateFakeAudioFrames( } #endif // CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER +void ClearKeyCdm::StartFileIOTest() { + file_io_test_runner_.reset(new FileIOTestRunner( + base::Bind(&ClearKeyCdmHost::CreateFileIO, base::Unretained(host_)))); + file_io_test_runner_->RunAllTests( + base::Bind(&ClearKeyCdm::OnFileIOTestComplete, base::Unretained(this))); +} + +void ClearKeyCdm::OnFileIOTestComplete(bool success) { + DVLOG(1) << __FUNCTION__ << ": " << success; + std::string message = GetFileIOTestResultMessage(success); + host_->OnSessionMessage( + last_session_id_, message.data(), message.size(), NULL, 0); + file_io_test_runner_.reset(); +} + } // namespace media diff --git a/media/cdm/ppapi/clear_key_cdm.h b/media/cdm/ppapi/clear_key_cdm.h index 87f45e1..79c8af0 100644 --- a/media/cdm/ppapi/clear_key_cdm.h +++ b/media/cdm/ppapi/clear_key_cdm.h @@ -23,6 +23,7 @@ #endif namespace media { +class FileIOTestRunner; class CdmVideoDecoder; class DecoderBuffer; class FFmpegCdmAudioDecoder; @@ -30,7 +31,9 @@ class FFmpegCdmAudioDecoder; // Clear key implementation of the cdm::ContentDecryptionModule interface. class ClearKeyCdm : public ClearKeyCdmInterface { public: - explicit ClearKeyCdm(Host* host, bool is_decrypt_only); + explicit ClearKeyCdm(Host* host, + bool is_decrypt_only, + bool should_test_file_io); virtual ~ClearKeyCdm(); // ContentDecryptionModule implementation. @@ -104,13 +107,19 @@ class ClearKeyCdm : public ClearKeyCdmInterface { cdm::AudioFrames* audio_frames); #endif // CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER + void StartFileIOTest(); + + // Callback for CDM File IO test. + void OnFileIOTestComplete(bool success); + AesDecryptor decryptor_; ClearKeyCdmHost* host_; const bool is_decrypt_only_; + const bool should_test_file_io_; - uint32 heartbeat_session_id_; + uint32 last_session_id_; std::string next_heartbeat_message_; // Timer delay in milliseconds for the next host_->SetTimer() call. @@ -134,6 +143,8 @@ class ClearKeyCdm : public ClearKeyCdmInterface { scoped_ptr<CdmVideoDecoder> video_decoder_; + scoped_ptr<FileIOTestRunner> file_io_test_runner_; + DISALLOW_COPY_AND_ASSIGN(ClearKeyCdm); }; diff --git a/media/media_cdm.gypi b/media/media_cdm.gypi index 553d360..e064d8b 100644 --- a/media/media_cdm.gypi +++ b/media/media_cdm.gypi @@ -81,6 +81,8 @@ '<(DEPTH)/base/base.gyp:base', ], 'sources': [ + 'cdm/ppapi/cdm_file_io_test.cc', + 'cdm/ppapi/cdm_file_io_test.h', 'cdm/ppapi/cdm_video_decoder.cc', 'cdm/ppapi/cdm_video_decoder.h', 'cdm/ppapi/clear_key_cdm.cc', |