Bug 1100499 - Add GMP API to enumerate records stored on disk. r=jesup

This commit is contained in:
Chris Pearce 2014-11-21 12:25:12 +13:00
parent e3c9f4ba39
commit 725607e8e6
15 changed files with 393 additions and 6 deletions

View File

@ -285,6 +285,45 @@ public:
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,
@ -335,6 +374,8 @@ FakeDecryptor::UpdateSession(uint32_t aPromiseId,
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);
}
}

View File

@ -68,6 +68,9 @@ public:
static void Message(const std::string& aMessage);
void ProcessRecordNames(GMPRecordIterator* aRecordIterator,
GMPErr aStatus);
private:
virtual ~FakeDecryptor() {}

View File

@ -193,3 +193,10 @@ GMPOpenRecord(const std::string& aRecordName,
}
return client->Init(record, aContinuation);
}
GMPErr
GMPEnumRecordNames(RecvGMPRecordIteratorPtr aRecvIteratorFunc,
void* aUserArg)
{
return g_platform_api->getrecordenumerator(aRecvIteratorFunc, aUserArg);
}

View File

@ -52,6 +52,10 @@ public:
GMPErr
GMPOpenRecord(const std::string& aRecordName,
OpenContinuation* aContinuation);
OpenContinuation* aContinuation);
GMPErr
GMPEnumRecordNames(RecvGMPRecordIteratorPtr aRecvIteratorFunc,
void* aUserArg);
#endif // TEST_GMP_STORAGE_H__

View File

@ -158,8 +158,10 @@ CreateRecord(const char* aRecordName,
GMPRecord** aOutRecord,
GMPRecordClient* aClient)
{
MOZ_ASSERT(IsOnChildMainThread());
if (sMainLoop != MessageLoop::current()) {
NS_WARNING("GMP called CreateRecord() on non-main thread!");
MOZ_ASSERT(false, "GMP called CreateRecord() on non-main thread!");
return GMPGenericErr;
}
if (aRecordNameSize > GMP_MAX_RECORD_NAME_SIZE) {
@ -194,6 +196,25 @@ GetClock(GMPTimestamp* aOutTime)
return GMPNoErr;
}
GMPErr
CreateRecordIterator(RecvGMPRecordIteratorPtr aRecvIteratorFunc,
void* aUserArg)
{
if (sMainLoop != MessageLoop::current()) {
MOZ_ASSERT(false, "GMP called CreateRecord() on non-main thread!");
return GMPGenericErr;
}
if (!aRecvIteratorFunc) {
return GMPInvalidArgErr;
}
GMPStorageChild* storage = sChild->GetGMPStorage();
if (!storage) {
return GMPGenericErr;
}
MOZ_ASSERT(storage);
return storage->EnumerateRecords(aRecvIteratorFunc, aUserArg);
}
void
InitPlatformAPI(GMPPlatformAPI& aPlatformAPI, GMPChild* aChild)
{
@ -212,6 +233,7 @@ InitPlatformAPI(GMPPlatformAPI& aPlatformAPI, GMPChild* aChild)
aPlatformAPI.createrecord = &CreateRecord;
aPlatformAPI.settimer = &SetTimerOnMainThread;
aPlatformAPI.getcurrenttime = &GetClock;
aPlatformAPI.getrecordenumerator = &CreateRecordIterator;
}
GMPThreadImpl::GMPThreadImpl()

View File

@ -268,6 +268,82 @@ GMPStorageChild::RecvWriteComplete(const nsCString& aRecordName,
return true;
}
GMPErr
GMPStorageChild::EnumerateRecords(RecvGMPRecordIteratorPtr aRecvIteratorFunc,
void* aUserArg)
{
if (mPlugin->GMPMessageLoop() != MessageLoop::current()) {
MOZ_ASSERT(false, "GMP used GMPStorage on non-main thread.");
return GMPGenericErr;
}
if (mShutdown) {
NS_WARNING("GMPStorage used after it's been shutdown!");
return GMPClosedErr;
}
if (!SendGetRecordNames()) {
return GMPGenericErr;
}
MOZ_ASSERT(aRecvIteratorFunc);
mPendingRecordIterators.push(RecordIteratorContext(aRecvIteratorFunc, aUserArg));
return GMPNoErr;
}
class GMPRecordIteratorImpl : public GMPRecordIterator {
public:
GMPRecordIteratorImpl(const InfallibleTArray<nsCString>& aRecordNames)
: mRecordNames(aRecordNames)
, mIndex(0)
{
mRecordNames.Sort();
}
virtual GMPErr GetName(const char** aOutName, uint32_t* aOutNameLength) MOZ_OVERRIDE {
if (!aOutName || !aOutNameLength) {
return GMPInvalidArgErr;
}
if (mIndex == mRecordNames.Length()) {
return GMPEndOfEnumeration;
}
*aOutName = mRecordNames[mIndex].get();
*aOutNameLength = mRecordNames[mIndex].Length();
return GMPNoErr;
}
virtual GMPErr NextRecord() MOZ_OVERRIDE {
if (mIndex < mRecordNames.Length()) {
mIndex++;
}
return (mIndex < mRecordNames.Length()) ? GMPNoErr
: GMPEndOfEnumeration;
}
virtual void Close() MOZ_OVERRIDE {
delete this;
}
private:
nsTArray<nsCString> mRecordNames;
size_t mIndex;
};
bool
GMPStorageChild::RecvRecordNames(const InfallibleTArray<nsCString>& aRecordNames,
const GMPErr& aStatus)
{
if (mShutdown || mPendingRecordIterators.empty()) {
return true;
}
RecordIteratorContext ctx = mPendingRecordIterators.front();
mPendingRecordIterators.pop();
if (GMP_FAILED(aStatus)) {
ctx.mFunc(nullptr, ctx.mUserArg, aStatus);
} else {
ctx.mFunc(new GMPRecordIteratorImpl(aRecordNames), ctx.mUserArg, GMPNoErr);
}
return true;
}
bool
GMPStorageChild::RecvShutdown()
{
@ -275,6 +351,9 @@ GMPStorageChild::RecvShutdown()
// parent. We don't delete any objects here, as that may invalidate
// GMPRecord pointers held by the GMP.
mShutdown = true;
while (!mPendingRecordIterators.empty()) {
mPendingRecordIterators.pop();
}
return true;
}

View File

@ -10,6 +10,9 @@
#include "gmp-storage.h"
#include "nsTHashtable.h"
#include "nsRefPtrHashtable.h"
#include "gmp-platform.h"
#include <queue>
namespace mozilla {
namespace gmp {
@ -70,6 +73,9 @@ public:
GMPErr Close(GMPRecordImpl* aRecord);
GMPErr EnumerateRecords(RecvGMPRecordIteratorPtr aRecvIteratorFunc,
void* aUserArg);
protected:
~GMPStorageChild() {}
@ -81,11 +87,25 @@ protected:
const InfallibleTArray<uint8_t>& aBytes) MOZ_OVERRIDE;
virtual bool RecvWriteComplete(const nsCString& aRecordName,
const GMPErr& aStatus) MOZ_OVERRIDE;
virtual bool RecvRecordNames(const InfallibleTArray<nsCString>& aRecordNames,
const GMPErr& aStatus) MOZ_OVERRIDE;
virtual bool RecvShutdown() MOZ_OVERRIDE;
private:
nsRefPtrHashtable<nsCStringHashKey, GMPRecordImpl> mRecords;
GMPChild* mPlugin;
struct RecordIteratorContext {
explicit RecordIteratorContext(RecvGMPRecordIteratorPtr aFunc,
void* aUserArg)
: mFunc(aFunc)
, mUserArg(aUserArg)
{}
RecvGMPRecordIteratorPtr mFunc;
void* mUserArg;
};
std::queue<RecordIteratorContext> mPendingRecordIterators;
bool mShutdown;
};

View File

@ -18,6 +18,8 @@
#include "mozIGeckoMediaPluginService.h"
#include "nsContentCID.h"
#include "nsServiceManagerUtils.h"
#include "mozilla/Base64.h"
#include "nsISimpleEnumerator.h"
namespace mozilla {
@ -104,9 +106,17 @@ OpenStorageFile(const nsCString& aRecordName,
return rv;
}
nsAutoString recordNameHash;
recordNameHash.AppendInt(HashString(aRecordName.get()));
f->Append(recordNameHash);
nsAutoCString recordNameBase64;
rv = Base64Encode(aRecordName, recordNameBase64);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Base64 can encode to a '/' character, which will mess with file paths,
// so we need to replace that here with something that won't mess with paths.
recordNameBase64.ReplaceChar('/', '-');
f->AppendNative(recordNameBase64);
auto mode = PR_RDWR | PR_CREATE_FILE;
if (aMode == Truncate) {
@ -193,6 +203,54 @@ public:
return (bytesWritten == (int32_t)aBytes.Length()) ? GMPNoErr : GMPGenericErr;
}
virtual GMPErr GetRecordNames(nsTArray<nsCString>& aOutRecordNames) MOZ_OVERRIDE
{
nsCOMPtr<nsIFile> storageDir;
nsresult rv = GetGMPStorageDir(getter_AddRefs(storageDir), mNodeId);
if (NS_WARN_IF(NS_FAILED(rv))) {
return GMPGenericErr;
}
nsCOMPtr<nsISimpleEnumerator> iter;
rv = storageDir->GetDirectoryEntries(getter_AddRefs(iter));
if (NS_FAILED(rv)) {
return GMPGenericErr;
}
bool hasMore;
while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) {
nsCOMPtr<nsISupports> supports;
rv = iter->GetNext(getter_AddRefs(supports));
if (NS_FAILED(rv)) {
continue;
}
nsCOMPtr<nsIFile> dirEntry(do_QueryInterface(supports, &rv));
if (NS_FAILED(rv)) {
continue;
}
nsAutoCString leafName;
rv = dirEntry->GetNativeLeafName(leafName);
if (NS_FAILED(rv)) {
continue;
}
// The record's file name is the Base64 encode of the record name,
// with '/' characters replaced with '-' characters. Base64 decode
// to extract the file name.
leafName.ReplaceChar('-', '/');
nsAutoCString recordName;
rv = Base64Decode(leafName, recordName);
if (NS_WARN_IF(NS_FAILED(rv))) {
continue;
}
aOutRecordNames.AppendElement(recordName);
}
return GMPNoErr;
}
virtual void Close(const nsCString& aRecordName) MOZ_OVERRIDE
{
PRFileDesc* fd = mFiles.Get(aRecordName);
@ -255,6 +313,12 @@ public:
return GMPNoErr;
}
virtual GMPErr GetRecordNames(nsTArray<nsCString>& aOutRecordNames) MOZ_OVERRIDE
{
mRecords.EnumerateRead(EnumRecordNames, &aOutRecordNames);
return GMPNoErr;
}
virtual void Close(const nsCString& aRecordName) MOZ_OVERRIDE
{
Record* record = nullptr;
@ -277,6 +341,16 @@ private:
bool mIsOpen;
};
static PLDHashOperator
EnumRecordNames(const nsACString& aKey,
Record* aRecord,
void* aUserArg)
{
nsTArray<nsCString>* names = reinterpret_cast<nsTArray<nsCString>*>(aUserArg);
names->AppendElement(aKey);
return PL_DHASH_NEXT;
}
nsClassHashtable<nsCStringHashKey, Record> mRecords;
};
@ -389,6 +463,22 @@ GMPStorageParent::RecvWrite(const nsCString& aRecordName,
return true;
}
bool
GMPStorageParent::RecvGetRecordNames()
{
LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, this));
if (mShutdown) {
return true;
}
nsTArray<nsCString> recordNames;
GMPErr status = mStorage->GetRecordNames(recordNames);
unused << SendRecordNames(recordNames, status);
return true;
}
bool
GMPStorageParent::RecvClose(const nsCString& aRecordName)
{

View File

@ -25,6 +25,7 @@ public:
nsTArray<uint8_t>& aOutBytes) = 0;
virtual GMPErr Write(const nsCString& aRecordName,
const nsTArray<uint8_t>& aBytes) = 0;
virtual GMPErr GetRecordNames(nsTArray<nsCString>& aOutRecordNames) = 0;
virtual void Close(const nsCString& aRecordName) = 0;
};
@ -42,6 +43,7 @@ protected:
virtual bool RecvRead(const nsCString& aRecordName) MOZ_OVERRIDE;
virtual bool RecvWrite(const nsCString& aRecordName,
const InfallibleTArray<uint8_t>& aBytes) MOZ_OVERRIDE;
virtual bool RecvGetRecordNames() MOZ_OVERRIDE;
virtual bool RecvClose(const nsCString& aRecordName) MOZ_OVERRIDE;
virtual void ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;

View File

@ -19,6 +19,7 @@ child:
OpenComplete(nsCString aRecordName, GMPErr aStatus);
ReadComplete(nsCString aRecordName, GMPErr aStatus, uint8_t[] aBytes);
WriteComplete(nsCString aRecordName, GMPErr aStatus);
RecordNames(nsCString[] aRecordNames, GMPErr aStatus);
Shutdown();
parent:
@ -26,6 +27,7 @@ parent:
Read(nsCString aRecordName);
Write(nsCString aRecordName, uint8_t[] aBytes);
Close(nsCString aRecordName);
GetRecordNames();
__delete__();
};

View File

@ -37,7 +37,7 @@ struct GMPAudioCodec
// AAC AudioSpecificConfig.
// These are null/0 if not externally negotiated
const uint8_t* mExtraData;
size_t mExtraDataLen;
uint32_t mExtraDataLen;
};
#endif // GMP_AUDIO_CODEC_h_

View File

@ -45,6 +45,8 @@ typedef enum {
GMPEncodeErr = 8,
GMPNoKeyErr = 9,
GMPCryptoErr = 10,
GMPEndOfEnumeration = 11,
GMPInvalidArgErr = 12,
GMPLastErr // Placeholder, must be last. This enum's values must remain consecutive!
} GMPErr;

View File

@ -82,6 +82,22 @@ typedef GMPErr (*GMPCreateRecordPtr)(const char* aRecordName,
typedef GMPErr (*GMPSetTimerOnMainThreadPtr)(GMPTask* aTask, int64_t aTimeoutMS);
typedef GMPErr (*GMPGetCurrentTimePtr)(GMPTimestamp* aOutTime);
typedef void (*RecvGMPRecordIteratorPtr)(GMPRecordIterator* aRecordIterator,
void* aUserArg,
GMPErr aStatus);
// Creates a GMPCreateRecordIterator to enumerate the records in storage.
// When the iterator is ready, the function at aRecvIteratorFunc
// is called with the GMPRecordIterator as an argument. If the operation
// fails, RecvGMPRecordIteratorPtr is called with a failure aStatus code.
// The list that the iterator is covering is fixed when
// GMPCreateRecordIterator is called, it is *not* updated when changes are
// made to storage.
// Iterator begins pointing at first record.
// aUserArg is passed to the aRecvIteratorFunc upon completion.
typedef GMPErr (*GMPCreateRecordIteratorPtr)(RecvGMPRecordIteratorPtr aRecvIteratorFunc,
void* aUserArg);
struct GMPPlatformAPI {
// Increment the version when things change. Can only add to the struct,
// do not change what already exists. Pointers to functions may be NULL
@ -96,6 +112,7 @@ struct GMPPlatformAPI {
GMPCreateRecordPtr createrecord;
GMPSetTimerOnMainThreadPtr settimer;
GMPGetCurrentTimePtr getcurrenttime;
GMPCreateRecordIteratorPtr getrecordenumerator;
};
#endif // GMP_PLATFORM_h_

View File

@ -110,4 +110,31 @@ class GMPRecordClient {
virtual ~GMPRecordClient() {}
};
// Iterates over the records that are available. Note: this list maintains
// a snapshot of the records that were present when the iterator was created.
// Create by calling the GMPCreateRecordIteratorPtr function on the
// GMPPlatformAPI struct.
// Iteration is in alphabetical order.
class GMPRecordIterator {
public:
// Retrieve the name for the current record.
// Returns GMPNoErr if successful, or GMPEndOfEnumeration if iteration has
// reached the end.
virtual GMPErr GetName(const char ** aOutName, uint32_t * aOutNameLength) = 0;
// Advance iteration to the next record.
// Returns GMPNoErr if successful, or GMPEndOfEnumeration if iteration has
// reached the end.
virtual GMPErr NextRecord() = 0;
// Signals to the GMP host that the GMP is finished with the
// GMPRecordIterator. GMPs must call this to release memory held by
// the GMPRecordIterator. Do not access the GMPRecordIterator pointer
// after calling this!
// Memory retrieved by GetName is *not* valid after calling Close()!
virtual void Close() = 0;
virtual ~GMPRecordIterator() {}
};
#endif // GMP_STORAGE_h_

View File

@ -580,6 +580,67 @@ class GMPStorageTest : public GMPDecryptorProxyCallback
Update(NS_LITERAL_CSTRING("retrieve-plugin-voucher"));
}
void TestGetRecordNamesInMemoryStorage() {
TestGetRecordNames(true);
}
nsCString mRecordNames;
void AppendIntPadded(nsACString& aString, uint32_t aInt) {
if (aInt > 0 && aInt < 10) {
aString.AppendLiteral("0");
}
aString.AppendInt(aInt);
}
void TestGetRecordNames(bool aPrivateBrowsing) {
CreateDecryptor(NS_LITERAL_STRING("foo.com"),
NS_LITERAL_STRING("bar.com"),
aPrivateBrowsing);
// Create a number of records of different names.
const uint32_t num = 100;
for (uint32_t i = 0; i < num; i++) {
nsAutoCString response;
response.AppendLiteral("stored data");
AppendIntPadded(response, i);
response.AppendLiteral(" test-data");
AppendIntPadded(response, i);
if (i != 0) {
mRecordNames.AppendLiteral(",");
}
mRecordNames.AppendLiteral("data");
AppendIntPadded(mRecordNames, i);
nsAutoCString update;
update.AppendLiteral("store data");
AppendIntPadded(update, i);
update.AppendLiteral(" test-data");
AppendIntPadded(update, i);
nsIRunnable* continuation = nullptr;
if (i + 1 == num) {
continuation =
NS_NewRunnableMethod(this, &GMPStorageTest::TestGetRecordNames_QueryNames);
}
Expect(response, continuation);
Update(update);
}
}
void TestGetRecordNames_QueryNames() {
nsCString response("record-names ");
response.Append(mRecordNames);
Expect(response,
NS_NewRunnableMethod(this, &GMPStorageTest::SetFinished));
Update(NS_LITERAL_CSTRING("retrieve-record-names"));
}
void GetRecordNamesPersistentStorage() {
TestGetRecordNames(false);
}
void Expect(const nsCString& aMessage, nsIRunnable* aContinuation) {
mExpected.AppendElement(ExpectedMessage(aMessage, aContinuation));
}
@ -751,3 +812,13 @@ TEST(GeckoMediaPlugins, GMPOutputProtection) {
runner->DoTest(&GMPStorageTest::TestOutputProtection);
}
#endif
TEST(GeckoMediaPlugins, GMPStorageGetRecordNamesInMemoryStorage) {
nsRefPtr<GMPStorageTest> runner = new GMPStorageTest();
runner->DoTest(&GMPStorageTest::TestGetRecordNamesInMemoryStorage);
}
TEST(GeckoMediaPlugins, GMPStorageGetRecordNamesPersistentStorage) {
nsRefPtr<GMPStorageTest> runner = new GMPStorageTest();
runner->DoTest(&GMPStorageTest::GetRecordNamesPersistentStorage);
}