summaryrefslogtreecommitdiffstats
path: root/dom/media/gmp-plugin/gmp-test-decryptor.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/gmp-plugin/gmp-test-decryptor.cpp')
-rw-r--r--dom/media/gmp-plugin/gmp-test-decryptor.cpp608
1 files changed, 608 insertions, 0 deletions
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;
+ }
+}