diff options
Diffstat (limited to 'dom/media/fake-cdm')
-rw-r--r-- | dom/media/fake-cdm/cdm-fake.cpp | 63 | ||||
-rw-r--r-- | dom/media/fake-cdm/cdm-test-decryptor.cpp | 433 | ||||
-rw-r--r-- | dom/media/fake-cdm/cdm-test-decryptor.h | 106 | ||||
-rw-r--r-- | dom/media/fake-cdm/cdm-test-output-protection.h | 126 | ||||
-rw-r--r-- | dom/media/fake-cdm/cdm-test-storage.cpp | 195 | ||||
-rw-r--r-- | dom/media/fake-cdm/cdm-test-storage.h | 45 | ||||
-rw-r--r-- | dom/media/fake-cdm/manifest.json | 9 | ||||
-rw-r--r-- | dom/media/fake-cdm/moz.build | 33 |
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 |