diff options
Diffstat (limited to 'dom/media/gmp-plugin')
-rw-r--r-- | dom/media/gmp-plugin/fake.info | 5 | ||||
-rw-r--r-- | dom/media/gmp-plugin/fake.voucher | 1 | ||||
-rw-r--r-- | dom/media/gmp-plugin/gmp-fake.cpp | 98 | ||||
-rw-r--r-- | dom/media/gmp-plugin/gmp-test-decryptor.cpp | 608 | ||||
-rw-r--r-- | dom/media/gmp-plugin/gmp-test-decryptor.h | 102 | ||||
-rw-r--r-- | dom/media/gmp-plugin/gmp-test-output-protection.h | 130 | ||||
-rw-r--r-- | dom/media/gmp-plugin/gmp-test-storage.cpp | 233 | ||||
-rw-r--r-- | dom/media/gmp-plugin/gmp-test-storage.h | 63 | ||||
-rw-r--r-- | dom/media/gmp-plugin/moz.build | 32 |
9 files changed, 1272 insertions, 0 deletions
diff --git a/dom/media/gmp-plugin/fake.info b/dom/media/gmp-plugin/fake.info new file mode 100644 index 000000000..b0592ff63 --- /dev/null +++ b/dom/media/gmp-plugin/fake.info @@ -0,0 +1,5 @@ +Name: fake +Description: Fake GMP Plugin, which deliberately uses GMP_API_DECRYPTOR_BACKWARDS_COMPAT for its decryptor. +Version: 1.0 +APIs: decode-video[h264:broken], eme-decrypt-v7[fake] +Libraries: dxva2.dll diff --git a/dom/media/gmp-plugin/fake.voucher b/dom/media/gmp-plugin/fake.voucher new file mode 100644 index 000000000..bb133701c --- /dev/null +++ b/dom/media/gmp-plugin/fake.voucher @@ -0,0 +1 @@ +gmp-fake placeholder voucher
\ No newline at end of file diff --git a/dom/media/gmp-plugin/gmp-fake.cpp b/dom/media/gmp-plugin/gmp-fake.cpp new file mode 100644 index 000000000..b67efe251 --- /dev/null +++ b/dom/media/gmp-plugin/gmp-fake.cpp @@ -0,0 +1,98 @@ +/*! + * \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 <stdint.h> +#include <cstdio> +#include <cstring> +#include <string> +#include <memory> + +#include "gmp-platform.h" +#include "gmp-video-decode.h" + +#if defined(GMP_FAKE_SUPPORT_DECRYPT) +#include "gmp-decryption.h" +#include "gmp-test-decryptor.h" +#include "gmp-test-storage.h" +#endif + +#if defined(_MSC_VER) +#define PUBLIC_FUNC __declspec(dllexport) +#else +#define PUBLIC_FUNC +#endif + +GMPPlatformAPI* g_platform_api = NULL; + +extern "C" { + + PUBLIC_FUNC GMPErr + GMPInit (GMPPlatformAPI* aPlatformAPI) { + g_platform_api = aPlatformAPI; + return GMPNoErr; + } + + PUBLIC_FUNC GMPErr + GMPGetAPI (const char* aApiName, void* aHostAPI, void** aPluginApi) { + if (!strcmp (aApiName, GMP_API_VIDEO_DECODER)) { + // Note: Deliberately advertise in our .info file that we support + // video-decode, but we fail the "get" call here to simulate what + // happens when decoder init fails. + return GMPGenericErr; +#if defined(GMP_FAKE_SUPPORT_DECRYPT) + } else if (!strcmp (aApiName, GMP_API_DECRYPTOR_BACKWARDS_COMPAT)) { + *aPluginApi = new FakeDecryptor(static_cast<GMPDecryptorHost*> (aHostAPI)); + return GMPNoErr; + } else if (!strcmp (aApiName, GMP_API_ASYNC_SHUTDOWN)) { + *aPluginApi = new TestAsyncShutdown(static_cast<GMPAsyncShutdownHost*> (aHostAPI)); + return GMPNoErr; +#endif + } + return GMPGenericErr; + } + + PUBLIC_FUNC void + GMPShutdown (void) { + g_platform_api = NULL; + } + +#if defined(GMP_FAKE_SUPPORT_DECRYPT) + PUBLIC_FUNC void + GMPSetNodeId(const char* aNodeId, uint32_t aLength) { + FakeDecryptor::SetNodeId(aNodeId, aLength); + } +#endif + +} // extern "C" diff --git a/dom/media/gmp-plugin/gmp-test-decryptor.cpp b/dom/media/gmp-plugin/gmp-test-decryptor.cpp new file mode 100644 index 000000000..afa809602 --- /dev/null +++ b/dom/media/gmp-plugin/gmp-test-decryptor.cpp @@ -0,0 +1,608 @@ +/* -*- 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 "gmp-test-decryptor.h" +#include "gmp-test-storage.h" +#include "gmp-test-output-protection.h" + +#include <string> +#include <vector> +#include <iostream> +#include <istream> +#include <iterator> +#include <sstream> +#include <set> + +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" + +using namespace std; + +std::string FakeDecryptor::sNodeId; + +FakeDecryptor* FakeDecryptor::sInstance = nullptr; +extern GMPPlatformAPI* g_platform_api; // Defined in gmp-fake.cpp + +class GMPMutexAutoLock +{ +public: + explicit GMPMutexAutoLock(GMPMutex* aMutex) : mMutex(aMutex) { + mMutex->Acquire(); + } + ~GMPMutexAutoLock() { + mMutex->Release(); + } +private: + GMPMutex* const mMutex; +}; + +class TestManager { +public: + TestManager() : mMutex(CreateMutex()) {} + + // Register a test with the test manager. + void BeginTest(const string& aTestID) { + GMPMutexAutoLock 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 string& aTestID) { + bool isEmpty = false; + { + GMPMutexAutoLock 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() { + mMutex->Destroy(); + } + + static void Error(const string& msg) { + FakeDecryptor::Message(msg); + } + + static void Finish() { + FakeDecryptor::Message("test-storage complete"); + } + + static GMPMutex* CreateMutex() { + GMPMutex* mutex = nullptr; + g_platform_api->createmutex(&mutex); + return mutex; + } + + GMPMutex* const mMutex; + set<string> mTestIDs; +}; + +FakeDecryptor::FakeDecryptor(GMPDecryptorHost* aHost) + : mCallback(nullptr) + , mHost(aHost) +{ + MOZ_ASSERT(!sInstance); + sInstance = this; +} + +void FakeDecryptor::DecryptingComplete() +{ + sInstance = nullptr; + delete this; +} + +void +FakeDecryptor::Message(const std::string& aMessage) +{ + MOZ_ASSERT(sInstance); + const static std::string sid("fake-session-id"); + sInstance->mCallback->SessionMessage(sid.c_str(), sid.size(), + kGMPLicenseRequest, + (const uint8_t*)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 string TruncateRecordId = "truncate-record-id"; +static const string TruncateRecordData = "I will soon be truncated"; + +class ReadThenTask : public GMPTask { +public: + ReadThenTask(string aId, ReadContinuation* aThen) + : mId(aId) + , mThen(aThen) + {} + void Run() override { + ReadRecord(mId, mThen); + } + void Destroy() override { + delete this; + } + string mId; + ReadContinuation* mThen; +}; + +class SendMessageTask : public GMPTask { +public: + explicit SendMessageTask(const string& aMessage, + TestManager* aTestManager = nullptr, + const string& aTestID = "") + : mMessage(aMessage), mTestmanager(aTestManager), mTestID(aTestID) {} + + void Run() override { + FakeDecryptor::Message(mMessage); + if (mTestmanager) { + mTestmanager->EndTest(mTestID); + } + } + + void Destroy() override { + delete this; + } + +private: + string mMessage; + TestManager* const mTestmanager; + const string mTestID; +}; + +class TestEmptyContinuation : public ReadContinuation { +public: + TestEmptyContinuation(TestManager* aTestManager, const string& aTestID) + : mTestmanager(aTestManager), mTestID(aTestID) {} + + void ReadComplete(GMPErr aErr, const std::string& aData) override { + if (aData != "") { + FakeDecryptor::Message("FAIL TestEmptyContinuation record was not truncated"); + } + mTestmanager->EndTest(mTestID); + delete this; + } + +private: + TestManager* const mTestmanager; + const string mTestID; +}; + +class TruncateContinuation : public ReadContinuation { +public: + TruncateContinuation(const string& aID, + TestManager* aTestManager, + const string& aTestID) + : mID(aID), mTestmanager(aTestManager), mTestID(aTestID) {} + + void ReadComplete(GMPErr aErr, const std::string& aData) override { + if (aData != TruncateRecordData) { + FakeDecryptor::Message("FAIL TruncateContinuation read data doesn't match written data"); + } + auto cont = new TestEmptyContinuation(mTestmanager, mTestID); + auto msg = "FAIL in TruncateContinuation write."; + auto failTask = new SendMessageTask(msg, mTestmanager, mTestID); + WriteRecord(mID, nullptr, 0, new ReadThenTask(mID, cont), failTask); + delete this; + } + +private: + const string mID; + TestManager* const mTestmanager; + const string mTestID; +}; + +class VerifyAndFinishContinuation : public ReadContinuation { +public: + explicit VerifyAndFinishContinuation(string aValue, + TestManager* aTestManager, + const string& aTestID) + : mValue(aValue), mTestmanager(aTestManager), mTestID(aTestID) {} + + void ReadComplete(GMPErr aErr, const std::string& aData) override { + if (aData != mValue) { + FakeDecryptor::Message("FAIL VerifyAndFinishContinuation read data doesn't match expected data"); + } + mTestmanager->EndTest(mTestID); + delete this; + } + +private: + string mValue; + TestManager* const mTestmanager; + const string mTestID; +}; + +class VerifyAndOverwriteContinuation : public ReadContinuation { +public: + VerifyAndOverwriteContinuation(string aId, string aValue, string aOverwrite, + TestManager* aTestManager, const string& aTestID) + : mId(aId) + , mValue(aValue) + , mOverwrite(aOverwrite) + , mTestmanager(aTestManager) + , mTestID(aTestID) + {} + + void ReadComplete(GMPErr aErr, const std::string& aData) override { + if (aData != mValue) { + FakeDecryptor::Message("FAIL VerifyAndOverwriteContinuation read data doesn't match expected data"); + } + auto cont = new VerifyAndFinishContinuation(mOverwrite, mTestmanager, mTestID); + auto msg = "FAIL in VerifyAndOverwriteContinuation write."; + auto failTask = new SendMessageTask(msg, mTestmanager, mTestID); + WriteRecord(mId, mOverwrite, new ReadThenTask(mId, cont), failTask); + delete this; + } + +private: + string mId; + string mValue; + string mOverwrite; + TestManager* const mTestmanager; + const string mTestID; +}; + +static const string OpenAgainRecordId = "open-again-record-id"; + +class OpenedSecondTimeContinuation : public OpenContinuation { +public: + explicit OpenedSecondTimeContinuation(GMPRecord* aRecord, + TestManager* aTestManager, + const string& aTestID) + : mRecord(aRecord), mTestmanager(aTestManager), mTestID(aTestID) { + MOZ_ASSERT(aRecord); + } + + virtual void OpenComplete(GMPErr aStatus, GMPRecord* aRecord) override { + if (GMP_SUCCEEDED(aStatus)) { + FakeDecryptor::Message("FAIL OpenSecondTimeContinuation should not be able to re-open record."); + } + if (aRecord) { + aRecord->Close(); + } + // Succeeded, open should have failed. + mTestmanager->EndTest(mTestID); + mRecord->Close(); + } + +private: + GMPRecord* mRecord; + TestManager* const mTestmanager; + const string mTestID; +}; + +class OpenedFirstTimeContinuation : public OpenContinuation { +public: + OpenedFirstTimeContinuation(const string& aID, + TestManager* aTestManager, + const string& aTestID) + : mID(aID), mTestmanager(aTestManager), mTestID(aTestID) {} + + virtual void OpenComplete(GMPErr aStatus, GMPRecord* aRecord) override { + if (GMP_FAILED(aStatus)) { + FakeDecryptor::Message("FAIL OpenAgainContinuation to open record initially."); + mTestmanager->EndTest(mTestID); + if (aRecord) { + aRecord->Close(); + } + return; + } + + auto cont = new OpenedSecondTimeContinuation(aRecord, mTestmanager, mTestID); + GMPOpenRecord(mID, cont); + } + +private: + const string mID; + TestManager* const mTestmanager; + const string mTestID; +}; + +static void +DoTestStorage(const string& aPrefix, TestManager* aTestManager) +{ + // Basic I/O tests. We run three cases concurrently. The tests, like + // GMPStorage 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 string id1 = aPrefix + TruncateRecordId; + const string testID1 = aPrefix + "write-test-1"; + aTestManager->BeginTest(testID1); + auto cont1 = new TruncateContinuation(id1, aTestManager, testID1); + auto msg1 = "FAIL in TestStorage writing TruncateRecord."; + auto failTask1 = new SendMessageTask(msg1, aTestManager, testID1); + WriteRecord(id1, TruncateRecordData, + new ReadThenTask(id1, cont1), failTask1); + + // 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 + string id2 = aPrefix + "record1"; + string record1 = "This is the first write to a record."; + string overwrite = "A shorter record"; + const string testID2 = aPrefix + "write-test-2"; + aTestManager->BeginTest(testID2); + auto task2 = new VerifyAndOverwriteContinuation(id2, record1, overwrite, + aTestManager, testID2); + auto msg2 = "FAIL in TestStorage writing record1."; + auto failTask2 = new SendMessageTask(msg2, aTestManager, testID2); + WriteRecord(id2, record1, new ReadThenTask(id2, task2), failTask2); + + // Test 3: Test that opening a record while it's already open fails. + // + // Open record1, then + // open record1, should fail. + // close record1 + const string id3 = aPrefix + OpenAgainRecordId; + const string testID3 = aPrefix + "open-test-1"; + aTestManager->BeginTest(testID3); + auto task3 = new OpenedFirstTimeContinuation(id3, aTestManager, testID3); + GMPOpenRecord(id3, task3); +} + +class TestStorageTask : public GMPTask { +public: + TestStorageTask(const string& aPrefix, TestManager* aTestManager) + : mPrefix(aPrefix), mTestManager(aTestManager) {} + virtual void Destroy() { delete this; } + virtual void Run() { + DoTestStorage(mPrefix, mTestManager); + } +private: + const string mPrefix; + TestManager* const mTestManager; +}; + +void +FakeDecryptor::TestStorage() +{ + TestManager* testManager = new TestManager(); + GMPThread* thread1 = nullptr; + GMPThread* thread2 = nullptr; + + // Main thread tests. + DoTestStorage("mt1-", testManager); + DoTestStorage("mt2-", testManager); + + // Off-main-thread tests. + if (GMP_SUCCEEDED(g_platform_api->createthread(&thread1))) { + thread1->Post(new TestStorageTask("thread1-", testManager)); + } else { + FakeDecryptor::Message("FAIL to create thread1 for storage tests"); + } + + if (GMP_SUCCEEDED(g_platform_api->createthread(&thread2))) { + thread2->Post(new TestStorageTask("thread2-", testManager)); + } else { + FakeDecryptor::Message("FAIL to create thread2 for storage tests"); + } + + if (thread1) { + thread1->Join(); + } + + if (thread2) { + thread2->Join(); + } + + // Note: Once all tests finish, TestManager will dispatch "test-pass" message, + // which ends the test for the parent. +} + +class ReportWritten : public GMPTask { +public: + ReportWritten(const string& aRecordId, const string& aValue) + : mRecordId(aRecordId) + , mValue(aValue) + {} + void Run() override { + FakeDecryptor::Message("stored " + mRecordId + " " + mValue); + } + void Destroy() override { + delete this; + } + const string mRecordId; + const string mValue; +}; + +class ReportReadStatusContinuation : public ReadContinuation { +public: + explicit ReportReadStatusContinuation(const string& aRecordId) + : mRecordId(aRecordId) + {} + void ReadComplete(GMPErr aErr, const std::string& aData) override { + if (GMP_FAILED(aErr)) { + FakeDecryptor::Message("retrieve " + mRecordId + " failed"); + } else { + stringstream ss; + ss << aData.size(); + string len; + ss >> len; + FakeDecryptor::Message("retrieve " + mRecordId + " succeeded (length " + + len + " bytes)"); + } + delete this; + } + string mRecordId; +}; + +class ReportReadRecordContinuation : public ReadContinuation { +public: + explicit ReportReadRecordContinuation(const string& aRecordId) + : mRecordId(aRecordId) + {} + void ReadComplete(GMPErr aErr, const std::string& aData) override { + if (GMP_FAILED(aErr)) { + FakeDecryptor::Message("retrieved " + mRecordId + " failed"); + } else { + FakeDecryptor::Message("retrieved " + mRecordId + " " + aData); + } + delete this; + } + string mRecordId; +}; + +static void +RecvGMPRecordIterator(GMPRecordIterator* aRecordIterator, + void* aUserArg, + GMPErr aStatus) +{ + FakeDecryptor* decryptor = reinterpret_cast<FakeDecryptor*>(aUserArg); + decryptor->ProcessRecordNames(aRecordIterator, aStatus); +} + +void +FakeDecryptor::ProcessRecordNames(GMPRecordIterator* aRecordIterator, + GMPErr aStatus) +{ + if (sInstance != this) { + FakeDecryptor::Message("Error aUserArg was not passed through GetRecordIterator"); + return; + } + if (GMP_FAILED(aStatus)) { + FakeDecryptor::Message("Error GetRecordIterator failed"); + return; + } + std::string response("record-names "); + bool first = true; + const char* name = nullptr; + uint32_t len = 0; + while (GMP_SUCCEEDED(aRecordIterator->GetName(&name, &len))) { + std::string s(name, name+len); + if (!first) { + response += ","; + } else { + first = false; + } + response += s; + aRecordIterator->NextRecord(); + } + aRecordIterator->Close(); + FakeDecryptor::Message(response); +} + +enum ShutdownMode { + ShutdownNormal, + ShutdownTimeout, + ShutdownStoreToken +}; + +static ShutdownMode sShutdownMode = ShutdownNormal; +static string sShutdownToken = ""; + +void +FakeDecryptor::UpdateSession(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdLength, + const uint8_t* aResponse, + uint32_t aResponseSize) +{ + std::string response((const char*)aResponse, (const char*)(aResponse)+aResponseSize); + std::vector<std::string> tokens = Tokenize(response); + const string& task = tokens[0]; + if (task == "test-storage") { + TestStorage(); + } else if (task == "store") { + // send "stored record" message on complete. + const string& id = tokens[1]; + const string& value = tokens[2]; + WriteRecord(id, + value, + new ReportWritten(id, value), + new SendMessageTask("FAIL in writing record.")); + } else if (task == "retrieve") { + const string& id = tokens[1]; + ReadRecord(id, new ReportReadStatusContinuation(id)); + } else if (task == "shutdown-mode") { + const 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("shutdown-token", new ReportReadRecordContinuation("shutdown-token")); + } else if (task == "test-op-apis") { + mozilla::gmptest::TestOuputProtectionAPIs(); + } else if (task == "retrieve-plugin-voucher") { + const uint8_t* rawVoucher = nullptr; + uint32_t length = 0; + mHost->GetPluginVoucher(&rawVoucher, &length); + std::string voucher((const char*)rawVoucher, (const char*)(rawVoucher + length)); + Message("retrieved plugin-voucher: " + voucher); + } else if (task == "retrieve-record-names") { + GMPEnumRecordNames(&RecvGMPRecordIterator, this); + } else if (task == "retrieve-node-id") { + Message("node-id " + sNodeId); + } +} + +class CompleteShutdownTask : public GMPTask { +public: + explicit CompleteShutdownTask(GMPAsyncShutdownHost* aHost) + : mHost(aHost) + { + } + virtual void Run() { + mHost->ShutdownComplete(); + } + virtual void Destroy() { delete this; } + GMPAsyncShutdownHost* mHost; +}; + +void +TestAsyncShutdown::BeginShutdown() { + switch (sShutdownMode) { + case ShutdownNormal: + mHost->ShutdownComplete(); + break; + case ShutdownTimeout: + // Don't do anything; wait for timeout, Gecko should kill + // the plugin and recover. + break; + case ShutdownStoreToken: + // Store message, then shutdown. + WriteRecord("shutdown-token", + sShutdownToken, + new CompleteShutdownTask(mHost), + new SendMessageTask("FAIL writing shutdown-token.")); + break; + } +} diff --git a/dom/media/gmp-plugin/gmp-test-decryptor.h b/dom/media/gmp-plugin/gmp-test-decryptor.h new file mode 100644 index 000000000..3b17e42c5 --- /dev/null +++ b/dom/media/gmp-plugin/gmp-test-decryptor.h @@ -0,0 +1,102 @@ +/* -*- 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 "gmp-decryption.h" +#include "gmp-async-shutdown.h" +#include <string> +#include "mozilla/Attributes.h" + +class FakeDecryptor : public GMPDecryptor7 { +public: + + explicit FakeDecryptor(GMPDecryptorHost* aHost); + + void Init(GMPDecryptorCallback* aCallback) override { + mCallback = aCallback; + } + + void CreateSession(uint32_t aCreateSessionToken, + uint32_t aPromiseId, + const char* aInitDataType, + uint32_t aInitDataTypeSize, + const uint8_t* aInitData, + uint32_t aInitDataSize, + GMPSessionType aSessionType) override + { + } + + void LoadSession(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdLength) override + { + } + + void UpdateSession(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdLength, + const uint8_t* aResponse, + uint32_t aResponseSize) override; + + void CloseSession(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdLength) override + { + } + + void RemoveSession(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdLength) override + { + } + + void SetServerCertificate(uint32_t aPromiseId, + const uint8_t* aServerCert, + uint32_t aServerCertSize) override + { + } + + void Decrypt(GMPBuffer* aBuffer, + GMPEncryptedBufferMetadata* aMetadata) override + { + } + + void DecryptingComplete() override; + + static void Message(const std::string& aMessage); + + void ProcessRecordNames(GMPRecordIterator* aRecordIterator, + GMPErr aStatus); + + static void SetNodeId(const char* aNodeId, uint32_t aLength) { + sNodeId = std::string(aNodeId, aNodeId + aLength); + } + +private: + + virtual ~FakeDecryptor() {} + static FakeDecryptor* sInstance; + static std::string sNodeId; + + void TestStorage(); + + GMPDecryptorCallback* mCallback; + GMPDecryptorHost* mHost; +}; + +class TestAsyncShutdown : public GMPAsyncShutdown { +public: + explicit TestAsyncShutdown(GMPAsyncShutdownHost* aHost) + : mHost(aHost) + { + } + void BeginShutdown() override; +private: + GMPAsyncShutdownHost* mHost; +}; + +#endif diff --git a/dom/media/gmp-plugin/gmp-test-output-protection.h b/dom/media/gmp-plugin/gmp-test-output-protection.h new file mode 100644 index 000000000..57855ca71 --- /dev/null +++ b/dom/media/gmp-plugin/gmp-test-output-protection.h @@ -0,0 +1,130 @@ +/* -*- 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 { +namespace gmptest { + +#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 (0x8007001f != hr && 0x80070032 != hr && 0xc02625e5 != hr) { + char msg[100]; + sprintf(msg, "FAIL OPMGetVideoOutputsFromHMONITOR call failed: HRESULT=0x%08x", 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%08x", 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"); + return; +} + +} // namespace gmptest +} // namespace mozilla diff --git a/dom/media/gmp-plugin/gmp-test-storage.cpp b/dom/media/gmp-plugin/gmp-test-storage.cpp new file mode 100644 index 000000000..e75b49f42 --- /dev/null +++ b/dom/media/gmp-plugin/gmp-test-storage.cpp @@ -0,0 +1,233 @@ +/* -*- 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 "gmp-test-storage.h" +#include <vector> + +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" + +class WriteRecordClient : public GMPRecordClient { +public: + GMPErr Init(GMPRecord* aRecord, + GMPTask* aOnSuccess, + GMPTask* aOnFailure, + const uint8_t* aData, + uint32_t aDataSize) { + mRecord = aRecord; + mOnSuccess = aOnSuccess; + mOnFailure = aOnFailure; + mData.insert(mData.end(), aData, aData + aDataSize); + return mRecord->Open(); + } + + void OpenComplete(GMPErr aStatus) override { + if (GMP_SUCCEEDED(aStatus)) { + mRecord->Write(mData.size() ? &mData.front() : nullptr, mData.size()); + } else { + GMPRunOnMainThread(mOnFailure); + mOnSuccess->Destroy(); + } + } + + void ReadComplete(GMPErr aStatus, + const uint8_t* aData, + uint32_t aDataSize) override {} + + void WriteComplete(GMPErr aStatus) override { + // 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. + mRecord->Close(); + if (GMP_SUCCEEDED(aStatus)) { + GMPRunOnMainThread(mOnSuccess); + mOnFailure->Destroy(); + } else { + GMPRunOnMainThread(mOnFailure); + mOnSuccess->Destroy(); + } + delete this; + } + +private: + GMPRecord* mRecord; + GMPTask* mOnSuccess; + GMPTask* mOnFailure; + std::vector<uint8_t> mData; +}; + +GMPErr +WriteRecord(const std::string& aRecordName, + const uint8_t* aData, + uint32_t aNumBytes, + GMPTask* aOnSuccess, + GMPTask* aOnFailure) +{ + GMPRecord* record; + WriteRecordClient* client = new WriteRecordClient(); + auto err = GMPOpenRecord(aRecordName.c_str(), + aRecordName.size(), + &record, + client); + if (GMP_FAILED(err)) { + GMPRunOnMainThread(aOnFailure); + aOnSuccess->Destroy(); + return err; + } + return client->Init(record, aOnSuccess, aOnFailure, aData, aNumBytes); +} + +GMPErr +WriteRecord(const std::string& aRecordName, + const std::string& aData, + GMPTask* aOnSuccess, + GMPTask* aOnFailure) +{ + return WriteRecord(aRecordName, + (const uint8_t*)aData.c_str(), + aData.size(), + aOnSuccess, + aOnFailure); +} + +class ReadRecordClient : public GMPRecordClient { +public: + GMPErr Init(GMPRecord* aRecord, + ReadContinuation* aContinuation) { + mRecord = aRecord; + mContinuation = aContinuation; + return mRecord->Open(); + } + + void OpenComplete(GMPErr aStatus) override { + auto err = mRecord->Read(); + if (GMP_FAILED(err)) { + mContinuation->ReadComplete(err, ""); + delete this; + } + } + + void ReadComplete(GMPErr aStatus, + const uint8_t* aData, + uint32_t aDataSize) override { + // 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. + mRecord->Close(); + std::string data((const char*)aData, aDataSize); + mContinuation->ReadComplete(GMPNoErr, data); + delete this; + } + + void WriteComplete(GMPErr aStatus) override { + } + +private: + GMPRecord* mRecord; + ReadContinuation* mContinuation; +}; + +GMPErr +ReadRecord(const std::string& aRecordName, + ReadContinuation* aContinuation) +{ + MOZ_ASSERT(aContinuation); + GMPRecord* record; + ReadRecordClient* client = new ReadRecordClient(); + auto err = GMPOpenRecord(aRecordName.c_str(), + aRecordName.size(), + &record, + client); + if (GMP_FAILED(err)) { + return err; + } + return client->Init(record, aContinuation); +} + +extern GMPPlatformAPI* g_platform_api; // Defined in gmp-fake.cpp + +GMPErr +GMPOpenRecord(const char* aName, + uint32_t aNameLength, + GMPRecord** aOutRecord, + GMPRecordClient* aClient) +{ + MOZ_ASSERT(g_platform_api); + return g_platform_api->createrecord(aName, aNameLength, aOutRecord, aClient); +} + +GMPErr +GMPRunOnMainThread(GMPTask* aTask) +{ + MOZ_ASSERT(g_platform_api); + return g_platform_api->runonmainthread(aTask); +} + +class OpenRecordClient : public GMPRecordClient { +public: + /* + * This function will take the memory ownership of the parameters and + * delete them when done. + */ + static void Open(const std::string& aRecordName, + OpenContinuation* aContinuation) { + MOZ_ASSERT(aContinuation); + (new OpenRecordClient(aContinuation))->Do(aRecordName); + } + + void OpenComplete(GMPErr aStatus) override { + Done(aStatus); + } + + void ReadComplete(GMPErr aStatus, + const uint8_t* aData, + uint32_t aDataSize) override { + MOZ_CRASH("Should not reach here."); + } + + void WriteComplete(GMPErr aStatus) override { + MOZ_CRASH("Should not reach here."); + } + +private: + explicit OpenRecordClient(OpenContinuation* aContinuation) + : mRecord(nullptr), mContinuation(aContinuation) {} + + void Do(const std::string& aName) { + auto err = GMPOpenRecord(aName.c_str(), aName.size(), &mRecord, this); + if (GMP_FAILED(err) || + GMP_FAILED(err = mRecord->Open())) { + Done(err); + } + } + + void Done(GMPErr err) { + // mContinuation is responsible for closing mRecord. + mContinuation->OpenComplete(err, mRecord); + delete mContinuation; + delete this; + } + + GMPRecord* mRecord; + OpenContinuation* mContinuation; +}; + +void +GMPOpenRecord(const std::string& aRecordName, + OpenContinuation* aContinuation) +{ + OpenRecordClient::Open(aRecordName, aContinuation); +} + +GMPErr +GMPEnumRecordNames(RecvGMPRecordIteratorPtr aRecvIteratorFunc, + void* aUserArg) +{ + return g_platform_api->getrecordenumerator(aRecvIteratorFunc, aUserArg); +} diff --git a/dom/media/gmp-plugin/gmp-test-storage.h b/dom/media/gmp-plugin/gmp-test-storage.h new file mode 100644 index 000000000..d77f8ebb3 --- /dev/null +++ b/dom/media/gmp-plugin/gmp-test-storage.h @@ -0,0 +1,63 @@ +/* -*- 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_GMP_STORAGE_H__ +#define TEST_GMP_STORAGE_H__ + +#include "gmp-errors.h" +#include "gmp-platform.h" +#include <string> + +class ReadContinuation { +public: + virtual ~ReadContinuation() {} + virtual void ReadComplete(GMPErr aErr, const std::string& aData) = 0; +}; + +// Reads a record to storage using GMPRecord. +// Calls ReadContinuation with read data. +GMPErr +ReadRecord(const std::string& aRecordName, + ReadContinuation* aContinuation); + +// Writes a record to storage using GMPRecord. +// Runs continuation when data is written. +GMPErr +WriteRecord(const std::string& aRecordName, + const std::string& aData, + GMPTask* aOnSuccess, + GMPTask* aOnFailure); + +GMPErr +WriteRecord(const std::string& aRecordName, + const uint8_t* aData, + uint32_t aNumBytes, + GMPTask* aOnSuccess, + GMPTask* aOnFailure); + +GMPErr +GMPOpenRecord(const char* aName, + uint32_t aNameLength, + GMPRecord** aOutRecord, + GMPRecordClient* aClient); + +GMPErr +GMPRunOnMainThread(GMPTask* aTask); + +class OpenContinuation { +public: + virtual ~OpenContinuation() {} + virtual void OpenComplete(GMPErr aStatus, GMPRecord* aRecord) = 0; +}; + +void +GMPOpenRecord(const std::string& aRecordName, + OpenContinuation* aContinuation); + +GMPErr +GMPEnumRecordNames(RecvGMPRecordIteratorPtr aRecvIteratorFunc, + void* aUserArg); + +#endif // TEST_GMP_STORAGE_H__ diff --git a/dom/media/gmp-plugin/moz.build b/dom/media/gmp-plugin/moz.build new file mode 100644 index 000000000..432d842fc --- /dev/null +++ b/dom/media/gmp-plugin/moz.build @@ -0,0 +1,32 @@ +# -*- 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 += [ + 'fake.info', + 'fake.voucher', +] + +SOURCES += [ + 'gmp-fake.cpp', + 'gmp-test-decryptor.cpp', + 'gmp-test-storage.cpp', +] + +DEFINES['GMP_FAKE_SUPPORT_DECRYPT'] = True + +SharedLibrary("fake") + +if CONFIG['OS_ARCH'] == 'WINNT': + OS_LIBS += [ + 'ole32', + ] + +USE_STATIC_LIBS = True +NO_VISIBILITY_FLAGS = True +# Don't use STL wrappers; this isn't Gecko code +DISABLE_STL_WRAPPING = True |