summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorxhwang@chromium.org <xhwang@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-12-20 07:26:18 +0000
committerxhwang@chromium.org <xhwang@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-12-20 07:26:18 +0000
commit34afd587d396b13defb7cf900caf3b67769d0b1e (patch)
tree33cd6c505b3ed7732c64f74cd1731d88c60c1c73
parent19d068c3f5a0ea6385cb4cd228ce8be1f135cd3d (diff)
downloadchromium_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.cc15
-rw-r--r--chrome/renderer/media/chrome_key_systems.cc14
-rw-r--r--chrome/test/data/media/encrypted_media_utils.js49
-rw-r--r--media/cdm/ppapi/cdm_file_io_test.cc437
-rw-r--r--media/cdm/ppapi/cdm_file_io_test.h157
-rw-r--r--media/cdm/ppapi/clear_key_cdm.cc49
-rw-r--r--media/cdm/ppapi/clear_key_cdm.h15
-rw-r--r--media/media_cdm.gypi2
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',