summaryrefslogtreecommitdiffstats
path: root/dom/media/fake-cdm
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/fake-cdm')
-rw-r--r--dom/media/fake-cdm/cdm-fake.cpp63
-rw-r--r--dom/media/fake-cdm/cdm-test-decryptor.cpp433
-rw-r--r--dom/media/fake-cdm/cdm-test-decryptor.h106
-rw-r--r--dom/media/fake-cdm/cdm-test-output-protection.h126
-rw-r--r--dom/media/fake-cdm/cdm-test-storage.cpp195
-rw-r--r--dom/media/fake-cdm/cdm-test-storage.h45
-rw-r--r--dom/media/fake-cdm/manifest.json9
-rw-r--r--dom/media/fake-cdm/moz.build33
8 files changed, 1010 insertions, 0 deletions
diff --git a/dom/media/fake-cdm/cdm-fake.cpp b/dom/media/fake-cdm/cdm-fake.cpp
new file mode 100644
index 0000000000..40f62c30d4
--- /dev/null
+++ b/dom/media/fake-cdm/cdm-fake.cpp
@@ -0,0 +1,63 @@
+/*!
+ * \copy
+ * Copyright (c) 2009-2014, Cisco Systems
+ * Copyright (c) 2014, Mozilla
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ *
+ *************************************************************************************
+ */
+
+#include "cdm-test-decryptor.h"
+#include "content_decryption_module.h"
+#include "content_decryption_module_ext.h"
+
+extern "C" {
+
+CDM_API
+void INITIALIZE_CDM_MODULE() {}
+
+CDM_API
+void* CreateCdmInstance(int cdm_interface_version, const char* key_system,
+ uint32_t key_system_size,
+ GetCdmHostFunc get_cdm_host_func, void* user_data) {
+ if (cdm_interface_version != cdm::ContentDecryptionModule_10::kVersion) {
+ // Only support CDM version 10 currently.
+ return nullptr;
+ }
+ cdm::Host_10* host = static_cast<cdm::Host_10*>(
+ get_cdm_host_func(cdm_interface_version, user_data));
+ return new FakeDecryptor(host);
+}
+
+CDM_API
+bool VerifyCdmHost_0(const cdm::HostFile* aHostFiles, uint32_t aNumFiles) {
+ return true;
+}
+
+} // extern "C"
diff --git a/dom/media/fake-cdm/cdm-test-decryptor.cpp b/dom/media/fake-cdm/cdm-test-decryptor.cpp
new file mode 100644
index 0000000000..3029fccdc4
--- /dev/null
+++ b/dom/media/fake-cdm/cdm-test-decryptor.cpp
@@ -0,0 +1,433 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "cdm-test-decryptor.h"
+#include "cdm-test-storage.h"
+#include "cdm-test-output-protection.h"
+
+#include <mutex>
+#include <string>
+#include <vector>
+#include <istream>
+#include <iterator>
+#include <set>
+#include <sstream>
+
+#include "mozilla/Assertions.h"
+
+FakeDecryptor* FakeDecryptor::sInstance = nullptr;
+
+class TestManager {
+ public:
+ TestManager() = default;
+
+ // Register a test with the test manager.
+ void BeginTest(const std::string& aTestID) {
+ std::lock_guard<std::mutex> lock(mMutex);
+ auto found = mTestIDs.find(aTestID);
+ if (found == mTestIDs.end()) {
+ mTestIDs.insert(aTestID);
+ } else {
+ Error("FAIL BeginTest test already existed: " + aTestID);
+ }
+ }
+
+ // Notify the test manager that the test is finished. If all tests are done,
+ // test manager will send "test-storage complete" to notify the parent that
+ // all tests are finished and also delete itself.
+ void EndTest(const std::string& aTestID) {
+ bool isEmpty = false;
+ {
+ std::lock_guard<std::mutex> lock(mMutex);
+ auto found = mTestIDs.find(aTestID);
+ if (found != mTestIDs.end()) {
+ mTestIDs.erase(aTestID);
+ isEmpty = mTestIDs.empty();
+ } else {
+ Error("FAIL EndTest test not existed: " + aTestID);
+ return;
+ }
+ }
+ if (isEmpty) {
+ Finish();
+ delete this;
+ }
+ }
+
+ private:
+ ~TestManager() = default;
+
+ static void Error(const std::string& msg) { FakeDecryptor::Message(msg); }
+
+ static void Finish() { FakeDecryptor::Message("test-storage complete"); }
+
+ std::mutex mMutex;
+ std::set<std::string> mTestIDs;
+};
+
+FakeDecryptor::FakeDecryptor(cdm::Host_10* aHost) : mHost(aHost) {
+ MOZ_ASSERT(!sInstance);
+ sInstance = this;
+}
+
+void FakeDecryptor::Message(const std::string& aMessage) {
+ MOZ_ASSERT(sInstance);
+ const static std::string sid("fake-session-id");
+ sInstance->mHost->OnSessionMessage(sid.c_str(), sid.size(),
+ cdm::MessageType::kLicenseRequest,
+ aMessage.c_str(), aMessage.size());
+}
+
+std::vector<std::string> Tokenize(const std::string& aString) {
+ std::stringstream strstr(aString);
+ std::istream_iterator<std::string> it(strstr), end;
+ return std::vector<std::string>(it, end);
+}
+
+static const std::string TruncateRecordId = "truncate-record-id";
+static const std::string TruncateRecordData = "I will soon be truncated";
+
+template <class Continuation>
+class WriteRecordSuccessTask {
+ public:
+ WriteRecordSuccessTask(std::string aId, Continuation aThen)
+ : mId(aId), mThen(std::move(aThen)) {}
+
+ void operator()() { ReadRecord(FakeDecryptor::sInstance->mHost, mId, mThen); }
+
+ std::string mId;
+ Continuation mThen;
+};
+
+class WriteRecordFailureTask {
+ public:
+ explicit WriteRecordFailureTask(const std::string& aMessage,
+ TestManager* aTestManager = nullptr,
+ const std::string& aTestID = "")
+ : mMessage(aMessage), mTestmanager(aTestManager), mTestID(aTestID) {}
+
+ void operator()() {
+ FakeDecryptor::Message(mMessage);
+ if (mTestmanager) {
+ mTestmanager->EndTest(mTestID);
+ }
+ }
+
+ private:
+ std::string mMessage;
+ TestManager* const mTestmanager;
+ const std::string mTestID;
+};
+
+class TestEmptyContinuation : public ReadContinuation {
+ public:
+ TestEmptyContinuation(TestManager* aTestManager, const std::string& aTestID)
+ : mTestmanager(aTestManager), mTestID(aTestID) {}
+
+ virtual void operator()(bool aSuccess, const uint8_t* aData,
+ uint32_t aDataSize) override {
+ if (aDataSize) {
+ FakeDecryptor::Message(
+ "FAIL TestEmptyContinuation record was not truncated");
+ }
+ mTestmanager->EndTest(mTestID);
+ }
+
+ private:
+ TestManager* const mTestmanager;
+ const std::string mTestID;
+};
+
+class TruncateContinuation : public ReadContinuation {
+ public:
+ TruncateContinuation(const std::string& aID, TestManager* aTestManager,
+ const std::string& aTestID)
+ : mID(aID), mTestmanager(aTestManager), mTestID(aTestID) {}
+
+ virtual void operator()(bool aSuccess, const uint8_t* aData,
+ uint32_t aDataSize) override {
+ if (std::string(reinterpret_cast<const char*>(aData), aDataSize) !=
+ TruncateRecordData) {
+ FakeDecryptor::Message(
+ "FAIL TruncateContinuation read data doesn't match written data");
+ }
+ auto cont = TestEmptyContinuation(mTestmanager, mTestID);
+ auto msg = "FAIL in TruncateContinuation write.";
+ WriteRecord(FakeDecryptor::sInstance->mHost, mID, nullptr, 0,
+ WriteRecordSuccessTask<TestEmptyContinuation>(mID, cont),
+ WriteRecordFailureTask(msg, mTestmanager, mTestID));
+ }
+
+ private:
+ const std::string mID;
+ TestManager* const mTestmanager;
+ const std::string mTestID;
+};
+
+class VerifyAndFinishContinuation : public ReadContinuation {
+ public:
+ explicit VerifyAndFinishContinuation(std::string aValue,
+ TestManager* aTestManager,
+ const std::string& aTestID)
+ : mValue(aValue), mTestmanager(aTestManager), mTestID(aTestID) {}
+
+ virtual void operator()(bool aSuccess, const uint8_t* aData,
+ uint32_t aDataSize) override {
+ if (std::string(reinterpret_cast<const char*>(aData), aDataSize) !=
+ mValue) {
+ FakeDecryptor::Message(
+ "FAIL VerifyAndFinishContinuation read data doesn't match expected "
+ "data");
+ }
+ mTestmanager->EndTest(mTestID);
+ }
+
+ private:
+ std::string mValue;
+ TestManager* const mTestmanager;
+ const std::string mTestID;
+};
+
+class VerifyAndOverwriteContinuation : public ReadContinuation {
+ public:
+ VerifyAndOverwriteContinuation(std::string aId, std::string aValue,
+ std::string aOverwrite,
+ TestManager* aTestManager,
+ const std::string& aTestID)
+ : mId(aId),
+ mValue(aValue),
+ mOverwrite(aOverwrite),
+ mTestmanager(aTestManager),
+ mTestID(aTestID) {}
+
+ virtual void operator()(bool aSuccess, const uint8_t* aData,
+ uint32_t aDataSize) override {
+ if (std::string(reinterpret_cast<const char*>(aData), aDataSize) !=
+ mValue) {
+ FakeDecryptor::Message(
+ "FAIL VerifyAndOverwriteContinuation read data doesn't match "
+ "expected data");
+ }
+ auto cont = VerifyAndFinishContinuation(mOverwrite, mTestmanager, mTestID);
+ auto msg = "FAIL in VerifyAndOverwriteContinuation write.";
+ WriteRecord(FakeDecryptor::sInstance->mHost, mId, mOverwrite,
+ WriteRecordSuccessTask<VerifyAndFinishContinuation>(mId, cont),
+ WriteRecordFailureTask(msg, mTestmanager, mTestID));
+ }
+
+ private:
+ std::string mId;
+ std::string mValue;
+ std::string mOverwrite;
+ TestManager* const mTestmanager;
+ const std::string mTestID;
+};
+
+static const std::string OpenAgainRecordId = "open-again-record-id";
+
+class OpenedSecondTimeContinuation : public OpenContinuation {
+ public:
+ explicit OpenedSecondTimeContinuation(TestManager* aTestManager,
+ const std::string& aTestID)
+ : mTestmanager(aTestManager), mTestID(aTestID) {}
+
+ void operator()(bool aSuccess) override {
+ if (!aSuccess) {
+ FakeDecryptor::Message(
+ "FAIL OpenSecondTimeContinuation should not be able to re-open "
+ "record.");
+ }
+ // Succeeded, open should have failed.
+ mTestmanager->EndTest(mTestID);
+ }
+
+ private:
+ TestManager* const mTestmanager;
+ const std::string mTestID;
+};
+
+class OpenedFirstTimeContinuation : public OpenContinuation {
+ public:
+ OpenedFirstTimeContinuation(const std::string& aID, TestManager* aTestManager,
+ const std::string& aTestID)
+ : mID(aID), mTestmanager(aTestManager), mTestID(aTestID) {}
+
+ void operator()(bool aSuccess) override {
+ if (!aSuccess) {
+ FakeDecryptor::Message(
+ "FAIL OpenAgainContinuation to open record initially.");
+ mTestmanager->EndTest(mTestID);
+ return;
+ }
+
+ auto cont = OpenedSecondTimeContinuation(mTestmanager, mTestID);
+ OpenRecord(FakeDecryptor::sInstance->mHost, mID, cont);
+ }
+
+ private:
+ const std::string mID;
+ TestManager* const mTestmanager;
+ const std::string mTestID;
+};
+
+static void DoTestStorage(const std::string& aPrefix,
+ TestManager* aTestManager) {
+ MOZ_ASSERT(FakeDecryptor::sInstance->mHost,
+ "FakeDecryptor::sInstance->mHost should not be null");
+ // Basic I/O tests. We run three cases concurrently. The tests, like
+ // CDMStorage run asynchronously. When they've all passed, we send
+ // a message back to the parent process, or a failure message if not.
+
+ // Test 1: Basic I/O test, and test that writing 0 bytes in a record
+ // deletes record.
+ //
+ // Write data to truncate record, then
+ // read data, verify that we read what we wrote, then
+ // write 0 bytes to truncate record, then
+ // read data, verify that 0 bytes was read
+ const std::string id1 = aPrefix + TruncateRecordId;
+ const std::string testID1 = aPrefix + "write-test-1";
+ aTestManager->BeginTest(testID1);
+ auto cont1 = TruncateContinuation(id1, aTestManager, testID1);
+ auto msg1 = "FAIL in TestStorage writing TruncateRecord.";
+ WriteRecord(FakeDecryptor::sInstance->mHost, id1, TruncateRecordData,
+ WriteRecordSuccessTask<TruncateContinuation>(id1, cont1),
+ WriteRecordFailureTask(msg1, aTestManager, testID1));
+
+ // Test 2: Test that overwriting a record with a shorter record truncates
+ // the record to the shorter record.
+ //
+ // Write record, then
+ // read and verify record, then
+ // write a shorter record to same record.
+ // read and verify
+ std::string id2 = aPrefix + "record1";
+ std::string record1 = "This is the first write to a record.";
+ std::string overwrite = "A shorter record";
+ const std::string testID2 = aPrefix + "write-test-2";
+ aTestManager->BeginTest(testID2);
+ auto task2 = VerifyAndOverwriteContinuation(id2, record1, overwrite,
+ aTestManager, testID2);
+ auto msg2 = "FAIL in TestStorage writing record1.";
+ WriteRecord(
+ FakeDecryptor::sInstance->mHost, id2, record1,
+ WriteRecordSuccessTask<VerifyAndOverwriteContinuation>(id2, task2),
+ WriteRecordFailureTask(msg2, aTestManager, testID2));
+
+ // Test 3: Test that opening a record while it's already open fails.
+ //
+ // Open record1, then
+ // open record1, should fail.
+ // close record1
+ const std::string id3 = aPrefix + OpenAgainRecordId;
+ const std::string testID3 = aPrefix + "open-test-1";
+ aTestManager->BeginTest(testID3);
+ auto task3 = OpenedFirstTimeContinuation(id3, aTestManager, testID3);
+ OpenRecord(FakeDecryptor::sInstance->mHost, id3, task3);
+}
+
+void FakeDecryptor::TestStorage() {
+ auto* testManager = new TestManager();
+ // Main thread tests.
+ DoTestStorage("mt1-", testManager);
+ DoTestStorage("mt2-", testManager);
+
+ // Note: Once all tests finish, TestManager will dispatch "test-pass" message,
+ // which ends the test for the parent.
+}
+
+class ReportWritten {
+ public:
+ ReportWritten(const std::string& aRecordId, const std::string& aValue)
+ : mRecordId(aRecordId), mValue(aValue) {}
+ void operator()() {
+ FakeDecryptor::Message("stored " + mRecordId + " " + mValue);
+ }
+
+ const std::string mRecordId;
+ const std::string mValue;
+};
+
+class ReportReadStatusContinuation : public ReadContinuation {
+ public:
+ explicit ReportReadStatusContinuation(const std::string& aRecordId)
+ : mRecordId(aRecordId) {}
+ void operator()(bool aSuccess, const uint8_t* aData,
+ uint32_t aDataSize) override {
+ if (!aSuccess) {
+ FakeDecryptor::Message("retrieve " + mRecordId + " failed");
+ } else {
+ std::stringstream ss;
+ ss << aDataSize;
+ std::string len;
+ ss >> len;
+ FakeDecryptor::Message("retrieve " + mRecordId + " succeeded (length " +
+ len + " bytes)");
+ }
+ }
+ std::string mRecordId;
+};
+
+class ReportReadRecordContinuation : public ReadContinuation {
+ public:
+ explicit ReportReadRecordContinuation(const std::string& aRecordId)
+ : mRecordId(aRecordId) {}
+ void operator()(bool aSuccess, const uint8_t* aData,
+ uint32_t aDataSize) override {
+ if (!aSuccess) {
+ FakeDecryptor::Message("retrieved " + mRecordId + " failed");
+ } else {
+ FakeDecryptor::Message(
+ "retrieved " + mRecordId + " " +
+ std::string(reinterpret_cast<const char*>(aData), aDataSize));
+ }
+ }
+ std::string mRecordId;
+};
+
+enum ShutdownMode { ShutdownNormal, ShutdownTimeout, ShutdownStoreToken };
+
+static ShutdownMode sShutdownMode = ShutdownNormal;
+static std::string sShutdownToken;
+
+void FakeDecryptor::UpdateSession(uint32_t aPromiseId, const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const uint8_t* aResponse,
+ uint32_t aResponseSize) {
+ MOZ_ASSERT(FakeDecryptor::sInstance->mHost,
+ "FakeDecryptor::sInstance->mHost should not be null");
+ std::string response((const char*)aResponse,
+ (const char*)(aResponse) + aResponseSize);
+ std::vector<std::string> tokens = Tokenize(response);
+ const std::string& task = tokens[0];
+ if (task == "test-storage") {
+ TestStorage();
+ } else if (task == "store") {
+ // send "stored record" message on complete.
+ const std::string& id = tokens[1];
+ const std::string& value = tokens[2];
+ WriteRecord(FakeDecryptor::sInstance->mHost, id, value,
+ ReportWritten(id, value),
+ WriteRecordFailureTask("FAIL in writing record."));
+ } else if (task == "retrieve") {
+ const std::string& id = tokens[1];
+ ReadRecord(FakeDecryptor::sInstance->mHost, id,
+ ReportReadStatusContinuation(id));
+ } else if (task == "shutdown-mode") {
+ const std::string& mode = tokens[1];
+ if (mode == "timeout") {
+ sShutdownMode = ShutdownTimeout;
+ } else if (mode == "token") {
+ sShutdownMode = ShutdownStoreToken;
+ sShutdownToken = tokens[2];
+ Message("shutdown-token received " + sShutdownToken);
+ }
+ } else if (task == "retrieve-shutdown-token") {
+ ReadRecord(FakeDecryptor::sInstance->mHost, "shutdown-token",
+ ReportReadRecordContinuation("shutdown-token"));
+ } else if (task == "test-op-apis") {
+ mozilla::cdmtest::TestOuputProtectionAPIs();
+ }
+}
diff --git a/dom/media/fake-cdm/cdm-test-decryptor.h b/dom/media/fake-cdm/cdm-test-decryptor.h
new file mode 100644
index 0000000000..60f9f494ff
--- /dev/null
+++ b/dom/media/fake-cdm/cdm-test-decryptor.h
@@ -0,0 +1,106 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef FAKE_DECRYPTOR_H__
+#define FAKE_DECRYPTOR_H__
+
+#include "content_decryption_module.h"
+#include <string>
+
+class FakeDecryptor : public cdm::ContentDecryptionModule_10 {
+ public:
+ explicit FakeDecryptor(cdm::Host_10* aHost);
+
+ void Initialize(bool aAllowDistinctiveIdentifier, bool aAllowPersistentState,
+ bool aUseHardwareSecureCodecs) override {
+ mHost->OnInitialized(true);
+ }
+
+ void GetStatusForPolicy(uint32_t aPromiseId,
+ const cdm::Policy& aPolicy) override {}
+
+ void SetServerCertificate(uint32_t aPromiseId,
+ const uint8_t* aServerCertificateData,
+ uint32_t aServerCertificateDataSize) override {}
+
+ void CreateSessionAndGenerateRequest(uint32_t aPromiseId,
+ cdm::SessionType aSessionType,
+ cdm::InitDataType aInitDataType,
+ const uint8_t* aInitData,
+ uint32_t aInitDataSize) override {}
+
+ void LoadSession(uint32_t aPromiseId, cdm::SessionType aSessionType,
+ const char* aSessionId, uint32_t aSessionIdSize) override {}
+
+ void UpdateSession(uint32_t aPromiseId, const char* aSessionId,
+ uint32_t aSessionIdSize, const uint8_t* aResponse,
+ uint32_t aResponseSize) override;
+
+ void CloseSession(uint32_t aPromiseId, const char* aSessionId,
+ uint32_t aSessionIdSize) override {}
+
+ void RemoveSession(uint32_t aPromiseId, const char* aSessionId,
+ uint32_t aSessionIdSize) override {}
+
+ void TimerExpired(void* aContext) override {}
+
+ cdm::Status Decrypt(const cdm::InputBuffer_2& aEncryptedBuffer,
+ cdm::DecryptedBlock* aDecryptedBuffer) override {
+ return cdm::Status::kDecodeError;
+ }
+
+ cdm::Status InitializeAudioDecoder(
+ const cdm::AudioDecoderConfig_2& aAudioDecoderConfig) override {
+ return cdm::Status::kDecodeError;
+ }
+
+ cdm::Status InitializeVideoDecoder(
+ const cdm::VideoDecoderConfig_2& aVideoDecoderConfig) override {
+ return cdm::Status::kDecodeError;
+ }
+
+ void DeinitializeDecoder(cdm::StreamType aDecoderType) override {}
+
+ void ResetDecoder(cdm::StreamType aDecoderType) override {}
+
+ cdm::Status DecryptAndDecodeFrame(const cdm::InputBuffer_2& aEncryptedBuffer,
+ cdm::VideoFrame* aVideoFrame) override {
+ return cdm::Status::kDecodeError;
+ }
+
+ cdm::Status DecryptAndDecodeSamples(
+ const cdm::InputBuffer_2& aEncryptedBuffer,
+ cdm::AudioFrames* aAudioFrame) override {
+ return cdm::Status::kDecodeError;
+ }
+
+ void OnPlatformChallengeResponse(
+ const cdm::PlatformChallengeResponse& aResponse) override {}
+
+ void OnQueryOutputProtectionStatus(cdm::QueryResult aResult,
+ uint32_t aLinkMask,
+ uint32_t aOutputProtectionMask) override {}
+
+ void OnStorageId(uint32_t aVersion, const uint8_t* aStorageId,
+ uint32_t aStorageIdSize) override {}
+
+ void Destroy() override {
+ delete this;
+ sInstance = nullptr;
+ }
+
+ static void Message(const std::string& aMessage);
+
+ cdm::Host_10* mHost;
+
+ static FakeDecryptor* sInstance;
+
+ private:
+ virtual ~FakeDecryptor() = default;
+
+ void TestStorage();
+};
+
+#endif
diff --git a/dom/media/fake-cdm/cdm-test-output-protection.h b/dom/media/fake-cdm/cdm-test-output-protection.h
new file mode 100644
index 0000000000..bde7613ac2
--- /dev/null
+++ b/dom/media/fake-cdm/cdm-test-output-protection.h
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if defined(XP_WIN)
+# include <d3d9.h> // needed to prevent re-definition of enums
+# include <stdio.h>
+# include <string>
+# include <vector>
+# include <windows.h>
+
+# include "opmapi.h"
+#endif
+
+namespace mozilla::cdmtest {
+
+#if defined(XP_WIN)
+typedef HRESULT(STDAPICALLTYPE* OPMGetVideoOutputsFromHMONITORProc)(
+ HMONITOR, OPM_VIDEO_OUTPUT_SEMANTICS, ULONG*, IOPMVideoOutput***);
+
+static OPMGetVideoOutputsFromHMONITORProc sOPMGetVideoOutputsFromHMONITORProc =
+ nullptr;
+
+static BOOL CALLBACK EnumDisplayMonitorsCallback(HMONITOR hMonitor, HDC hdc,
+ LPRECT lprc, LPARAM pData) {
+ std::vector<std::string>* failureMsgs = (std::vector<std::string>*)pData;
+
+ MONITORINFOEXA miex;
+ ZeroMemory(&miex, sizeof(miex));
+ miex.cbSize = sizeof(miex);
+ if (!GetMonitorInfoA(hMonitor, &miex)) {
+ failureMsgs->push_back("FAIL GetMonitorInfoA call failed");
+ }
+
+ ULONG numVideoOutputs = 0;
+ IOPMVideoOutput** opmVideoOutputArray = nullptr;
+ HRESULT hr = sOPMGetVideoOutputsFromHMONITORProc(
+ hMonitor, OPM_VOS_OPM_SEMANTICS, &numVideoOutputs, &opmVideoOutputArray);
+ if (S_OK != hr) {
+ if ((HRESULT)0x8007001f != hr && (HRESULT)0x80070032 != hr &&
+ (HRESULT)0xc02625e5 != hr) {
+ char msg[100];
+ sprintf(
+ msg,
+ "FAIL OPMGetVideoOutputsFromHMONITOR call failed: HRESULT=0x%08lx",
+ hr);
+ failureMsgs->push_back(msg);
+ }
+ return true;
+ }
+
+ DISPLAY_DEVICEA dd;
+ ZeroMemory(&dd, sizeof(dd));
+ dd.cb = sizeof(dd);
+ if (!EnumDisplayDevicesA(miex.szDevice, 0, &dd, 1)) {
+ failureMsgs->push_back("FAIL EnumDisplayDevicesA call failed");
+ }
+
+ for (ULONG i = 0; i < numVideoOutputs; ++i) {
+ OPM_RANDOM_NUMBER opmRandomNumber;
+ BYTE* certificate = nullptr;
+ ULONG certificateLength = 0;
+ hr = opmVideoOutputArray[i]->StartInitialization(
+ &opmRandomNumber, &certificate, &certificateLength);
+ if (S_OK != hr) {
+ char msg[100];
+ sprintf(msg, "FAIL StartInitialization call failed: HRESULT=0x%08lx", hr);
+ failureMsgs->push_back(msg);
+ }
+
+ if (certificate) {
+ CoTaskMemFree(certificate);
+ }
+
+ opmVideoOutputArray[i]->Release();
+ }
+
+ if (opmVideoOutputArray) {
+ CoTaskMemFree(opmVideoOutputArray);
+ }
+
+ return true;
+}
+#endif
+
+static void RunOutputProtectionAPITests() {
+#if defined(XP_WIN)
+ // Get hold of OPMGetVideoOutputsFromHMONITOR function.
+ HMODULE hDvax2DLL = GetModuleHandleW(L"dxva2.dll");
+ if (!hDvax2DLL) {
+ FakeDecryptor::Message("FAIL GetModuleHandleW call failed for dxva2.dll");
+ return;
+ }
+
+ sOPMGetVideoOutputsFromHMONITORProc =
+ (OPMGetVideoOutputsFromHMONITORProc)GetProcAddress(
+ hDvax2DLL, "OPMGetVideoOutputsFromHMONITOR");
+ if (!sOPMGetVideoOutputsFromHMONITORProc) {
+ FakeDecryptor::Message(
+ "FAIL GetProcAddress call failed for OPMGetVideoOutputsFromHMONITOR");
+ return;
+ }
+
+ // Test EnumDisplayMonitors.
+ // Other APIs are tested in the callback function.
+ std::vector<std::string> failureMsgs;
+ if (!EnumDisplayMonitors(NULL, NULL, EnumDisplayMonitorsCallback,
+ (LPARAM)&failureMsgs)) {
+ FakeDecryptor::Message("FAIL EnumDisplayMonitors call failed");
+ }
+
+ // Report any failures in the callback function.
+ for (size_t i = 0; i < failureMsgs.size(); i++) {
+ FakeDecryptor::Message(failureMsgs[i]);
+ }
+#endif
+}
+
+static void TestOuputProtectionAPIs() {
+ RunOutputProtectionAPITests();
+ FakeDecryptor::Message("OP tests completed");
+}
+
+} // namespace mozilla::cdmtest
diff --git a/dom/media/fake-cdm/cdm-test-storage.cpp b/dom/media/fake-cdm/cdm-test-storage.cpp
new file mode 100644
index 0000000000..d0a343576f
--- /dev/null
+++ b/dom/media/fake-cdm/cdm-test-storage.cpp
@@ -0,0 +1,195 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "cdm-test-storage.h"
+#include <vector>
+
+using namespace cdm;
+
+class WriteRecordClient : public FileIOClient {
+ public:
+ WriteRecordClient(std::function<void()>&& aOnSuccess,
+ std::function<void()>&& aOnFailure, const uint8_t* aData,
+ uint32_t aDataSize)
+ : mOnSuccess(std::move(aOnSuccess)), mOnFailure(std::move(aOnFailure)) {
+ mData.insert(mData.end(), aData, aData + aDataSize);
+ }
+
+ void OnOpenComplete(Status aStatus) override {
+ // If we hit an error, fail.
+ if (aStatus != Status::kSuccess) {
+ Done(aStatus);
+ } else if (mFileIO) { // Otherwise, write our data to the file.
+ mFileIO->Write(mData.empty() ? nullptr : &mData.front(), mData.size());
+ }
+ }
+
+ void OnReadComplete(Status aStatus, const uint8_t* aData,
+ uint32_t aDataSize) override {}
+
+ void OnWriteComplete(Status aStatus) override { Done(aStatus); }
+
+ void Do(const std::string& aName, Host_10* aHost) {
+ // Initialize the FileIO.
+ mFileIO = aHost->CreateFileIO(this);
+ mFileIO->Open(aName.c_str(), aName.size());
+ }
+
+ private:
+ void Done(cdm::FileIOClient::Status aStatus) {
+ // Note: Call Close() before running continuation, in case the
+ // continuation tries to open the same record; if we call Close()
+ // after running the continuation, the Close() call will arrive
+ // just after the Open() call succeeds, immediately closing the
+ // record we just opened.
+ if (mFileIO) {
+ // will delete mFileIO inside Close.
+ mFileIO->Close();
+ }
+
+ if (IO_SUCCEEDED(aStatus)) {
+ mOnSuccess();
+ } else {
+ mOnFailure();
+ }
+
+ delete this;
+ }
+
+ FileIO* mFileIO = nullptr;
+ std::function<void()> mOnSuccess;
+ std::function<void()> mOnFailure;
+ std::vector<uint8_t> mData;
+};
+
+void WriteRecord(Host_10* aHost, const std::string& aRecordName,
+ const uint8_t* aData, uint32_t aNumBytes,
+ std::function<void()>&& aOnSuccess,
+ std::function<void()>&& aOnFailure) {
+ // client will be delete in WriteRecordClient::Done
+ WriteRecordClient* client = new WriteRecordClient(
+ std::move(aOnSuccess), std::move(aOnFailure), aData, aNumBytes);
+ client->Do(aRecordName, aHost);
+}
+
+void WriteRecord(Host_10* aHost, const std::string& aRecordName,
+ const std::string& aData, std::function<void()>&& aOnSuccess,
+ std::function<void()>&& aOnFailure) {
+ return WriteRecord(aHost, aRecordName, (const uint8_t*)aData.c_str(),
+ aData.size(), std::move(aOnSuccess),
+ std::move(aOnFailure));
+}
+
+class ReadRecordClient : public FileIOClient {
+ public:
+ explicit ReadRecordClient(
+ std::function<void(bool, const uint8_t*, uint32_t)>&& aOnReadComplete)
+ : mOnReadComplete(std::move(aOnReadComplete)) {}
+
+ void OnOpenComplete(Status aStatus) override {
+ auto err = aStatus;
+ if (aStatus != Status::kSuccess) {
+ Done(err, reinterpret_cast<const uint8_t*>(""), 0);
+ } else {
+ mFileIO->Read();
+ }
+ }
+
+ void OnReadComplete(Status aStatus, const uint8_t* aData,
+ uint32_t aDataSize) override {
+ Done(aStatus, aData, aDataSize);
+ }
+
+ void OnWriteComplete(Status aStatus) override {}
+
+ void Do(const std::string& aName, Host_10* aHost) {
+ mFileIO = aHost->CreateFileIO(this);
+ mFileIO->Open(aName.c_str(), aName.size());
+ }
+
+ private:
+ void Done(cdm::FileIOClient::Status aStatus, const uint8_t* aData,
+ uint32_t aDataSize) {
+ // Note: Call Close() before running continuation, in case the
+ // continuation tries to open the same record; if we call Close()
+ // after running the continuation, the Close() call will arrive
+ // just after the Open() call succeeds, immediately closing the
+ // record we just opened.
+ if (mFileIO) {
+ // will delete mFileIO inside Close.
+ mFileIO->Close();
+ }
+
+ if (IO_SUCCEEDED(aStatus)) {
+ mOnReadComplete(true, aData, aDataSize);
+ } else {
+ mOnReadComplete(false, reinterpret_cast<const uint8_t*>(""), 0);
+ }
+
+ delete this;
+ }
+
+ FileIO* mFileIO = nullptr;
+ std::function<void(bool, const uint8_t*, uint32_t)> mOnReadComplete;
+};
+
+void ReadRecord(
+ Host_10* aHost, const std::string& aRecordName,
+ std::function<void(bool, const uint8_t*, uint32_t)>&& aOnReadComplete) {
+ // client will be delete in ReadRecordClient::Done
+ ReadRecordClient* client = new ReadRecordClient(std::move(aOnReadComplete));
+ client->Do(aRecordName, aHost);
+}
+
+class OpenRecordClient : public FileIOClient {
+ public:
+ explicit OpenRecordClient(std::function<void(bool)>&& aOpenComplete)
+ : mOpenComplete(std::move(aOpenComplete)) {}
+
+ void OnOpenComplete(Status aStatus) override { Done(aStatus); }
+
+ void OnReadComplete(Status aStatus, const uint8_t* aData,
+ uint32_t aDataSize) override {}
+
+ void OnWriteComplete(Status aStatus) override {}
+
+ void Do(const std::string& aName, Host_10* aHost) {
+ // Initialize the FileIO.
+ mFileIO = aHost->CreateFileIO(this);
+ mFileIO->Open(aName.c_str(), aName.size());
+ }
+
+ private:
+ void Done(cdm::FileIOClient::Status aStatus) {
+ // Note: Call Close() before running continuation, in case the
+ // continuation tries to open the same record; if we call Close()
+ // after running the continuation, the Close() call will arrive
+ // just after the Open() call succeeds, immediately closing the
+ // record we just opened.
+ if (mFileIO) {
+ // will delete mFileIO inside Close.
+ mFileIO->Close();
+ }
+
+ if (IO_SUCCEEDED(aStatus)) {
+ mOpenComplete(true);
+ } else {
+ mOpenComplete(false);
+ }
+
+ delete this;
+ }
+
+ FileIO* mFileIO = nullptr;
+ std::function<void(bool)> mOpenComplete;
+ ;
+};
+
+void OpenRecord(Host_10* aHost, const std::string& aRecordName,
+ std::function<void(bool)>&& aOpenComplete) {
+ // client will be delete in OpenRecordClient::Done
+ OpenRecordClient* client = new OpenRecordClient(std::move(aOpenComplete));
+ client->Do(aRecordName, aHost);
+}
diff --git a/dom/media/fake-cdm/cdm-test-storage.h b/dom/media/fake-cdm/cdm-test-storage.h
new file mode 100644
index 0000000000..2c95cca6b5
--- /dev/null
+++ b/dom/media/fake-cdm/cdm-test-storage.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef TEST_CDM_STORAGE_H__
+#define TEST_CDM_STORAGE_H__
+
+#include <functional>
+#include <string>
+#include <cstdint>
+#include "content_decryption_module.h"
+
+#define IO_SUCCEEDED(x) ((x) == cdm::FileIOClient::Status::kSuccess)
+#define IO_FAILED(x) ((x) != cdm::FileIOClient::Status::kSuccess)
+
+class ReadContinuation {
+ public:
+ virtual ~ReadContinuation() = default;
+ virtual void operator()(bool aSuccess, const uint8_t* aData,
+ uint32_t aDataSize) = 0;
+};
+
+void WriteRecord(cdm::Host_10* aHost, const std::string& aRecordName,
+ const std::string& aData, std::function<void()>&& aOnSuccess,
+ std::function<void()>&& aOnFailure);
+
+void WriteRecord(cdm::Host_10* aHost, const std::string& aRecordName,
+ const uint8_t* aData, uint32_t aNumBytes,
+ std::function<void()>&& aOnSuccess,
+ std::function<void()>&& aOnFailure);
+
+void ReadRecord(
+ cdm::Host_10* aHost, const std::string& aRecordName,
+ std::function<void(bool, const uint8_t*, uint32_t)>&& aOnReadComplete);
+
+class OpenContinuation {
+ public:
+ virtual ~OpenContinuation() = default;
+ virtual void operator()(bool aSuccess) = 0;
+};
+
+void OpenRecord(cdm::Host_10* aHost, const std::string& aRecordName,
+ std::function<void(bool)>&& aOpenComplete);
+#endif // TEST_CDM_STORAGE_H__
diff --git a/dom/media/fake-cdm/manifest.json b/dom/media/fake-cdm/manifest.json
new file mode 100644
index 0000000000..96823bac09
--- /dev/null
+++ b/dom/media/fake-cdm/manifest.json
@@ -0,0 +1,9 @@
+{
+ "name": "fake",
+ "description": "Fake CDM Plugin",
+ "version": "1",
+ "x-cdm-module-versions": "4",
+ "x-cdm-interface-versions": "10",
+ "x-cdm-host-versions": "10",
+ "x-cdm-codecs": ""
+}
diff --git a/dom/media/fake-cdm/moz.build b/dom/media/fake-cdm/moz.build
new file mode 100644
index 0000000000..6c7aafd9b4
--- /dev/null
+++ b/dom/media/fake-cdm/moz.build
@@ -0,0 +1,33 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+FINAL_TARGET = "dist/bin/gmp-fake/1.0"
+
+FINAL_TARGET_FILES += [
+ "manifest.json",
+]
+
+SOURCES += [
+ "cdm-fake.cpp",
+ "cdm-test-decryptor.cpp",
+ "cdm-test-storage.cpp",
+]
+
+DEFINES["CDM_IMPLEMENTATION"] = True
+
+SharedLibrary("fake")
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ OS_LIBS += [
+ "ole32",
+ "user32",
+ ]
+
+USE_STATIC_LIBS = True
+NoVisibilityFlags()
+# Don't use STL wrappers; this isn't Gecko code
+DisableStlWrapping()
+NO_PGO = True