mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-26 14:22:01 +00:00
Bug 1109457 - Add persistent session to our ClearKey CDM. r=edwin
This commit is contained in:
parent
682a5b2f5c
commit
a23d2bdf73
@ -118,6 +118,7 @@ class GMPRecordClient {
|
||||
class GMPRecordIterator {
|
||||
public:
|
||||
// Retrieve the name for the current record.
|
||||
// aOutName is null terminated at character at index (*aOutNameLength).
|
||||
// Returns GMPNoErr if successful, or GMPEndOfEnumeration if iteration has
|
||||
// reached the end.
|
||||
virtual GMPErr GetName(const char ** aOutName, uint32_t * aOutNameLength) = 0;
|
||||
|
@ -3,9 +3,12 @@ const KEYSYSTEM_TYPE = "org.w3.clearkey";
|
||||
function bail(message)
|
||||
{
|
||||
return function(err) {
|
||||
if (err) {
|
||||
message += "; " + String(err)
|
||||
}
|
||||
ok(false, message);
|
||||
if (err) {
|
||||
info(err);
|
||||
info(String(err));
|
||||
}
|
||||
SimpleTest.finish();
|
||||
}
|
||||
@ -70,13 +73,13 @@ function Log(token, msg) {
|
||||
info(TimeStamp(token) + " " + msg);
|
||||
}
|
||||
|
||||
function UpdateSessionFunc(test, token) {
|
||||
function UpdateSessionFunc(test, token, sessionType) {
|
||||
return function(ev) {
|
||||
var msgStr = ArrayBufferToString(ev.message);
|
||||
var msg = JSON.parse(msgStr);
|
||||
|
||||
Log(token, "got message from CDM: " + msgStr);
|
||||
is(msg.type, test.sessionType, TimeStamp(token) + " key session type should match");
|
||||
is(msg.type, sessionType, TimeStamp(token) + " key session type should match");
|
||||
ok(msg.kids, TimeStamp(token) + " message event should contain key ID array");
|
||||
|
||||
var outKeys = [];
|
||||
@ -211,24 +214,24 @@ function SetupEME(test, token, params)
|
||||
.then(function(keySystemAccess) {
|
||||
return keySystemAccess.createMediaKeys();
|
||||
}, bail(token + " Failed to request key system access."))
|
||||
|
||||
|
||||
.then(function(mediaKeys) {
|
||||
Log(token, "created MediaKeys object ok");
|
||||
mediaKeys.sessions = [];
|
||||
return v.setMediaKeys(mediaKeys);
|
||||
}, bail("failed to create MediaKeys object"))
|
||||
|
||||
|
||||
.then(function() {
|
||||
Log(token, "set MediaKeys on <video> element ok");
|
||||
|
||||
var session = v.mediaKeys.createSession(test.sessionType);
|
||||
var sessionType = (params && params.sessionType) ? params.sessionType : "temporary";
|
||||
var session = v.mediaKeys.createSession(sessionType);
|
||||
if (params && params.onsessioncreated) {
|
||||
params.onsessioncreated(session);
|
||||
}
|
||||
session.addEventListener("message", UpdateSessionFunc(test, token));
|
||||
session.addEventListener("message", UpdateSessionFunc(test, token, sessionType));
|
||||
return session.generateRequest(ev.initDataType, ev.initData);
|
||||
}, onSetKeysFail)
|
||||
|
||||
|
||||
.then(function() {
|
||||
Log(token, "generated request");
|
||||
}, bail(token + " Failed to request key system access2."));
|
||||
|
@ -364,6 +364,8 @@ skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
|
||||
skip-if = buildapp == 'b2g' && toolkit != 'gonk' # bug 1082984
|
||||
[test_eme_canvas_blocked.html]
|
||||
skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s # bug 1043403, bug 1057908
|
||||
[test_eme_persistent_sessions.html]
|
||||
skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s # bug 1043403, bug 1057908
|
||||
[test_eme_playback.html]
|
||||
skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s # bug 1043403, bug 1057908
|
||||
[test_eme_requestKeySystemAccess.html]
|
||||
|
156
dom/media/test/test_eme_persistent_sessions.html
Normal file
156
dom/media/test/test_eme_persistent_sessions.html
Normal file
@ -0,0 +1,156 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test Encrypted Media Extensions</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
<script type="text/javascript" src="manifest.js"></script>
|
||||
<script type="text/javascript" src="eme.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
var manager = new MediaTestManager;
|
||||
|
||||
function UsableKeyIdsMatch(usableKeyIds, expectedKeyIds) {
|
||||
var hexKeyIds = usableKeyIds.map(function(keyId) {
|
||||
return Base64ToHex(window.btoa(ArrayBufferToString(keyId)));
|
||||
}).sort();
|
||||
var expected = Object.keys(expectedKeyIds).sort();
|
||||
if (expected.length != hexKeyIds.length) {
|
||||
return false;
|
||||
}
|
||||
for (var i = 0; i < hexKeyIds.length; i++) {
|
||||
if (hexKeyIds[i] != expected[i]){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function AwaitAllKeysUsable(session, keys, token) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
function listener(event) {
|
||||
session.getUsableKeyIds().then(function(usableKeyIds) {
|
||||
var u = UsableKeyIdsMatch(usableKeyIds, keys);
|
||||
if (UsableKeyIdsMatch(usableKeyIds, keys)) {
|
||||
Log(token, "resolving AwaitAllKeysUsable promise");
|
||||
session.removeEventListener("keyschange", listener);
|
||||
resolve();
|
||||
}
|
||||
}, bail(token + " failed to get usableKeyIds"));
|
||||
}
|
||||
session.addEventListener("keyschange", listener);
|
||||
});
|
||||
}
|
||||
|
||||
function AwaitAllKeysNotUsable(session, token) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
function listener(event) {
|
||||
session.getUsableKeyIds().then(function(usableKeyIds) {
|
||||
if (usableKeyIds.length == 0) {
|
||||
session.removeEventListener("keyschange", listener);
|
||||
resolve();
|
||||
}
|
||||
}, bail(token + " failed to get usableKeyIds"));
|
||||
}
|
||||
session.addEventListener("keyschange", listener);
|
||||
});
|
||||
}
|
||||
|
||||
function startTest(test, token)
|
||||
{
|
||||
manager.started(token);
|
||||
|
||||
var recreatedSession; // will have remove() called on it.
|
||||
|
||||
var keySystemAccess;
|
||||
|
||||
var v = SetupEME(test, token,
|
||||
{
|
||||
onsessioncreated: function(session) {
|
||||
Log(token, "Session created");
|
||||
var sessionId;
|
||||
initialSession = session;
|
||||
|
||||
// Once the session has loaded and has all its keys usable, close
|
||||
// all sessions without calling remove() on them.
|
||||
AwaitAllKeysUsable(session, test.keys, token).then(
|
||||
function() {
|
||||
sessionId = session.sessionId;
|
||||
Log(token, "Closing session with id=" + sessionId);
|
||||
session.close();
|
||||
}
|
||||
);
|
||||
|
||||
// Once the session is closed, reload the MediaKeys and reload the session
|
||||
session.closed.then(function() {
|
||||
return navigator.requestMediaKeySystemAccess(KEYSYSTEM_TYPE)
|
||||
}, bail("close promise rejected"))
|
||||
|
||||
.then(function(requestedKeySystemAccess) {
|
||||
keySystemAccess = requestedKeySystemAccess;
|
||||
return keySystemAccess.createMediaKeys();
|
||||
}, bail(token + " Failed to request key system access."))
|
||||
|
||||
.then(function(mediaKeys) {
|
||||
Log(token, "re-created MediaKeys object ok");
|
||||
recreatedSession = mediaKeys.createSession("persistent");
|
||||
Log(token, "Created recreatedSession, loading sessionId=" + sessionId);
|
||||
return Promise.all([AwaitAllKeysUsable(recreatedSession, test.keys, token), recreatedSession.load(sessionId)]);
|
||||
}, bail(token + " failed to create mediaKeys"))
|
||||
|
||||
.then(function() {
|
||||
Log(token, "re-loaded persistent session, all keys still usable");
|
||||
return Promise.all([AwaitAllKeysNotUsable(recreatedSession, token), recreatedSession.remove()]);
|
||||
}, bail(token + " failed to get reload session or keys"))
|
||||
|
||||
.then(function() {
|
||||
Log(token, "removed session, all keys unusable.");
|
||||
// Attempt to recreate the session, the attempt should fail.
|
||||
return keySystemAccess.createMediaKeys();
|
||||
}, bail(token + " failed to remove session"))
|
||||
|
||||
.then(function(mediaKeys) {
|
||||
Log(token, "re-re-created MediaKeys object ok");
|
||||
// Trying to load the removed persistent session should fail.
|
||||
return mediaKeys.createSession("persistent").load(sessionId);
|
||||
}, bail(token + " failed to create mediaKeys"))
|
||||
|
||||
.then(function(suceeded) {
|
||||
is(suceeded, false, token + " we expect the third session creation to fail, as the session should have been removed.");
|
||||
manager.finished(token);
|
||||
}, bail(token + " failure to load session."));
|
||||
|
||||
},
|
||||
sessionType: "persistent",
|
||||
}
|
||||
);
|
||||
|
||||
v.addEventListener("error", bail(token + " got error event"));
|
||||
|
||||
LoadTest(test, v, token);
|
||||
}
|
||||
|
||||
function beginTest() {
|
||||
manager.runTests(gEMETests, startTest);
|
||||
}
|
||||
|
||||
var prefs = [
|
||||
[ "media.mediasource.enabled", true ],
|
||||
[ "media.mediasource.mp4.enabled", true ],
|
||||
];
|
||||
|
||||
if (/Linux/.test(navigator.userAgent) ||
|
||||
!document.createElement('video').canPlayType("video/mp4")) {
|
||||
// XXX remove once we have mp4 PlatformDecoderModules on all platforms.
|
||||
prefs.push([ "media.fragmented-mp4.exposed", true ]);
|
||||
prefs.push([ "media.fragmented-mp4.use-blank-decoder", true ]);
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({ "set" : prefs }, beginTest);
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -2,12 +2,14 @@
|
||||
* 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 <sstream>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "ClearKeyDecryptionManager.h"
|
||||
#include "ClearKeyUtils.h"
|
||||
#include "ClearKeyStorage.h"
|
||||
#include "ClearKeyPersistence.h"
|
||||
#include "gmp-task-utils.h"
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/NullPtr.h"
|
||||
@ -15,7 +17,6 @@
|
||||
using namespace mozilla;
|
||||
using namespace std;
|
||||
|
||||
|
||||
class ClearKeyDecryptor
|
||||
{
|
||||
public:
|
||||
@ -29,6 +30,8 @@ public:
|
||||
uint32_t AddRef();
|
||||
uint32_t Release();
|
||||
|
||||
const Key& DecryptionKey() const { return mKey; }
|
||||
|
||||
private:
|
||||
struct DecryptTask : public GMPTask
|
||||
{
|
||||
@ -79,15 +82,22 @@ private:
|
||||
Key mKey;
|
||||
};
|
||||
|
||||
|
||||
ClearKeyDecryptionManager::ClearKeyDecryptionManager()
|
||||
{
|
||||
CK_LOGD("ClearKeyDecryptionManager ctor");
|
||||
|
||||
// The ClearKeyDecryptionManager maintains a self reference which is
|
||||
// removed when the host is finished with the interface and calls
|
||||
// DecryptingComplete(). We make ClearKeyDecryptionManager refcounted so
|
||||
// that the tasks to that we dispatch to call functions on it won't end up
|
||||
// derefing a null reference after DecryptingComplete() is called.
|
||||
AddRef();
|
||||
}
|
||||
|
||||
ClearKeyDecryptionManager::~ClearKeyDecryptionManager()
|
||||
{
|
||||
CK_LOGD("ClearKeyDecryptionManager dtor");
|
||||
MOZ_ASSERT(mRefCount == 1);
|
||||
}
|
||||
|
||||
void
|
||||
@ -97,19 +107,7 @@ ClearKeyDecryptionManager::Init(GMPDecryptorCallback* aCallback)
|
||||
mCallback = aCallback;
|
||||
mCallback->SetCapabilities(GMP_EME_CAP_DECRYPT_AUDIO |
|
||||
GMP_EME_CAP_DECRYPT_VIDEO);
|
||||
}
|
||||
|
||||
static string
|
||||
GetNewSessionId()
|
||||
{
|
||||
static uint32_t sNextSessionId = 0;
|
||||
|
||||
string sessionId;
|
||||
stringstream ss;
|
||||
ss << ++sNextSessionId;
|
||||
ss >> sessionId;
|
||||
|
||||
return sessionId;
|
||||
ClearKeyPersistence::EnsureInitialized();
|
||||
}
|
||||
|
||||
void
|
||||
@ -129,10 +127,18 @@ ClearKeyDecryptionManager::CreateSession(uint32_t aPromiseId,
|
||||
return;
|
||||
}
|
||||
|
||||
string sessionId = GetNewSessionId();
|
||||
if (ClearKeyPersistence::DeferCreateSessionIfNotReady(this,
|
||||
aPromiseId,
|
||||
aInitData,
|
||||
aInitDataSize,
|
||||
aSessionType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
string sessionId = ClearKeyPersistence::GetNewSessionId(aSessionType);
|
||||
MOZ_ASSERT(mSessions.find(sessionId) == mSessions.end());
|
||||
|
||||
ClearKeySession* session = new ClearKeySession(sessionId, mCallback);
|
||||
ClearKeySession* session = new ClearKeySession(sessionId, mCallback, aSessionType);
|
||||
session->Init(aPromiseId, aInitData, aInitDataSize);
|
||||
mSessions[sessionId] = session;
|
||||
|
||||
@ -157,7 +163,7 @@ ClearKeyDecryptionManager::CreateSession(uint32_t aPromiseId,
|
||||
|
||||
// Send a request for needed key data.
|
||||
string request;
|
||||
ClearKeyUtils::MakeKeyRequest(neededKeys, request);
|
||||
ClearKeyUtils::MakeKeyRequest(neededKeys, request, aSessionType);
|
||||
mCallback->SessionMessage(&sessionId[0], sessionId.length(),
|
||||
(uint8_t*)&request[0], request.length(),
|
||||
"" /* destination url */, 0);
|
||||
@ -168,10 +174,73 @@ ClearKeyDecryptionManager::LoadSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdLength)
|
||||
{
|
||||
// TODO implement "persistent" sessions.
|
||||
mCallback->ResolveLoadSessionPromise(aPromiseId, false);
|
||||
|
||||
CK_LOGD("ClearKeyDecryptionManager::LoadSession");
|
||||
|
||||
if (!ClearKeyUtils::IsValidSessionId(aSessionId, aSessionIdLength)) {
|
||||
mCallback->ResolveLoadSessionPromise(aPromiseId, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ClearKeyPersistence::DeferLoadSessionIfNotReady(this,
|
||||
aPromiseId,
|
||||
aSessionId,
|
||||
aSessionIdLength)) {
|
||||
return;
|
||||
}
|
||||
|
||||
string sid(aSessionId, aSessionId + aSessionIdLength);
|
||||
if (!ClearKeyPersistence::IsPersistentSessionId(sid)) {
|
||||
mCallback->ResolveLoadSessionPromise(aPromiseId, false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Callsback PersistentSessionDataLoaded with results...
|
||||
ClearKeyPersistence::LoadSessionData(this, sid, aPromiseId);
|
||||
}
|
||||
|
||||
void
|
||||
ClearKeyDecryptionManager::PersistentSessionDataLoaded(GMPErr aStatus,
|
||||
uint32_t aPromiseId,
|
||||
const string& aSessionId,
|
||||
const uint8_t* aKeyData,
|
||||
uint32_t aKeyDataSize)
|
||||
{
|
||||
if (GMP_FAILED(aStatus) ||
|
||||
Contains(mSessions, aSessionId) ||
|
||||
(aKeyDataSize % (2 * CLEARKEY_KEY_LEN)) != 0) {
|
||||
mCallback->ResolveLoadSessionPromise(aPromiseId, false);
|
||||
return;
|
||||
}
|
||||
|
||||
ClearKeySession* session = new ClearKeySession(aSessionId,
|
||||
mCallback,
|
||||
kGMPPersistentSession);
|
||||
mSessions[aSessionId] = session;
|
||||
|
||||
// TODO: currently we have to resolve the load-session promise before we
|
||||
// can mark the keys as usable. We should really do this before marking
|
||||
// the keys usable, but we need to fix Gecko first.
|
||||
mCallback->ResolveLoadSessionPromise(aPromiseId, true);
|
||||
|
||||
uint32_t numKeys = aKeyDataSize / (2 * CLEARKEY_KEY_LEN);
|
||||
for (uint32_t i = 0; i < numKeys; i ++) {
|
||||
const uint8_t* base = aKeyData + 2 * CLEARKEY_KEY_LEN * i;
|
||||
|
||||
KeyId keyId(base, base + CLEARKEY_KEY_LEN);
|
||||
MOZ_ASSERT(keyId.size() == CLEARKEY_KEY_LEN);
|
||||
|
||||
Key key(base + CLEARKEY_KEY_LEN, base + 2 * CLEARKEY_KEY_LEN);
|
||||
MOZ_ASSERT(key.size() == CLEARKEY_KEY_LEN);
|
||||
|
||||
session->AddKeyId(keyId);
|
||||
|
||||
if (!Contains(mDecryptors, keyId)) {
|
||||
mDecryptors[keyId] = new ClearKeyDecryptor(mCallback, key);
|
||||
}
|
||||
mDecryptors[keyId]->AddRef();
|
||||
mCallback->KeyIdUsable(aSessionId.c_str(), aSessionId.size(),
|
||||
&keyId[0], keyId.size());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@ -184,20 +253,21 @@ ClearKeyDecryptionManager::UpdateSession(uint32_t aPromiseId,
|
||||
CK_LOGD("ClearKeyDecryptionManager::UpdateSession");
|
||||
string sessionId(aSessionId, aSessionId + aSessionIdLength);
|
||||
|
||||
if (mSessions.find(sessionId) == mSessions.end() || !mSessions[sessionId]) {
|
||||
auto itr = mSessions.find(sessionId);
|
||||
if (itr == mSessions.end() || !(itr->second)) {
|
||||
CK_LOGW("ClearKey CDM couldn't resolve session ID in UpdateSession.");
|
||||
mCallback->RejectPromise(aPromiseId, kGMPNotFoundError, nullptr, 0);
|
||||
return;
|
||||
}
|
||||
ClearKeySession* session = itr->second;
|
||||
|
||||
// Parse the response for any (key ID, key) pairs.
|
||||
vector<KeyIdPair> keyPairs;
|
||||
if (!ClearKeyUtils::ParseJWK(aResponse, aResponseSize, keyPairs)) {
|
||||
if (!ClearKeyUtils::ParseJWK(aResponse, aResponseSize, keyPairs, session->Type())) {
|
||||
CK_LOGW("ClearKey CDM failed to parse JSON Web Key.");
|
||||
mCallback->RejectPromise(aPromiseId, kGMPInvalidAccessError, nullptr, 0);
|
||||
return;
|
||||
}
|
||||
mCallback->ResolvePromise(aPromiseId);
|
||||
|
||||
for (auto it = keyPairs.begin(); it != keyPairs.end(); it++) {
|
||||
KeyId& keyId = it->mKeyId;
|
||||
@ -210,6 +280,43 @@ ClearKeyDecryptionManager::UpdateSession(uint32_t aPromiseId,
|
||||
|
||||
mDecryptors[keyId]->AddRef();
|
||||
}
|
||||
|
||||
if (session->Type() != kGMPPersistentSession) {
|
||||
mCallback->ResolvePromise(aPromiseId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the keys on disk. We store a record whose name is the sessionId,
|
||||
// and simply append each keyId followed by its key.
|
||||
vector<uint8_t> keydata;
|
||||
Serialize(session, keydata);
|
||||
GMPTask* resolve = WrapTask(mCallback, &GMPDecryptorCallback::ResolvePromise, aPromiseId);
|
||||
static const char* message = "Couldn't store cenc key init data";
|
||||
GMPTask* reject = WrapTask(mCallback,
|
||||
&GMPDecryptorCallback::RejectPromise,
|
||||
aPromiseId,
|
||||
kGMPInvalidStateError,
|
||||
message,
|
||||
strlen(message));
|
||||
StoreData(sessionId, keydata, resolve, reject);
|
||||
}
|
||||
|
||||
void
|
||||
ClearKeyDecryptionManager::Serialize(const ClearKeySession* aSession,
|
||||
std::vector<uint8_t>& aOutKeyData)
|
||||
{
|
||||
const std::vector<KeyId>& keyIds = aSession->GetKeyIds();
|
||||
for (size_t i = 0; i < keyIds.size(); i++) {
|
||||
const KeyId& keyId = keyIds[i];
|
||||
if (!Contains(mDecryptors, keyId)) {
|
||||
continue;
|
||||
}
|
||||
MOZ_ASSERT(keyId.size() == CLEARKEY_KEY_LEN);
|
||||
aOutKeyData.insert(aOutKeyData.end(), keyId.begin(), keyId.end());
|
||||
const Key& key = mDecryptors[keyId]->DecryptionKey();
|
||||
MOZ_ASSERT(key.size() == CLEARKEY_KEY_LEN);
|
||||
aOutKeyData.insert(aOutKeyData.end(), key.begin(), key.end());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@ -220,25 +327,39 @@ ClearKeyDecryptionManager::CloseSession(uint32_t aPromiseId,
|
||||
CK_LOGD("ClearKeyDecryptionManager::CloseSession");
|
||||
|
||||
string sessionId(aSessionId, aSessionId + aSessionIdLength);
|
||||
ClearKeySession* session = mSessions[sessionId];
|
||||
auto itr = mSessions.find(sessionId);
|
||||
if (itr == mSessions.end()) {
|
||||
CK_LOGW("ClearKey CDM couldn't close non-existent session.");
|
||||
mCallback->RejectPromise(aPromiseId, kGMPNotFoundError, nullptr, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
ClearKeySession* session = itr->second;
|
||||
MOZ_ASSERT(session);
|
||||
|
||||
const vector<KeyId>& keyIds = session->GetKeyIds();
|
||||
ClearInMemorySessionData(session);
|
||||
mCallback->ResolvePromise(aPromiseId);
|
||||
mCallback->SessionClosed(aSessionId, aSessionIdLength);
|
||||
}
|
||||
|
||||
void
|
||||
ClearKeyDecryptionManager::ClearInMemorySessionData(ClearKeySession* aSession)
|
||||
{
|
||||
MOZ_ASSERT(aSession);
|
||||
|
||||
const vector<KeyId>& keyIds = aSession->GetKeyIds();
|
||||
for (auto it = keyIds.begin(); it != keyIds.end(); it++) {
|
||||
MOZ_ASSERT(mDecryptors.find(*it) != mDecryptors.end());
|
||||
|
||||
if (!mDecryptors[*it]->Release()) {
|
||||
mDecryptors.erase(*it);
|
||||
mCallback->KeyIdNotUsable(aSessionId, aSessionIdLength,
|
||||
mCallback->KeyIdNotUsable(aSession->Id().c_str(), aSession->Id().size(),
|
||||
&(*it)[0], it->size());
|
||||
}
|
||||
}
|
||||
|
||||
mSessions.erase(sessionId);
|
||||
delete session;
|
||||
|
||||
mCallback->ResolvePromise(aPromiseId);
|
||||
mSessions.erase(aSession->Id());
|
||||
delete aSession;
|
||||
}
|
||||
|
||||
void
|
||||
@ -246,10 +367,39 @@ ClearKeyDecryptionManager::RemoveSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdLength)
|
||||
{
|
||||
// TODO implement "persistent" sessions.
|
||||
CK_LOGD("ClearKeyDecryptionManager::RemoveSession");
|
||||
mCallback->RejectPromise(aPromiseId, kGMPInvalidAccessError,
|
||||
nullptr /* message */, 0 /* messageLen */);
|
||||
string sessionId(aSessionId, aSessionId + aSessionIdLength);
|
||||
auto itr = mSessions.find(sessionId);
|
||||
if (itr == mSessions.end()) {
|
||||
CK_LOGW("ClearKey CDM couldn't remove non-existent session.");
|
||||
mCallback->RejectPromise(aPromiseId, kGMPNotFoundError, nullptr, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
ClearKeySession* session = itr->second;
|
||||
MOZ_ASSERT(session);
|
||||
string sid = session->Id();
|
||||
bool isPersistent = session->Type() == kGMPPersistentSession;
|
||||
ClearInMemorySessionData(session);
|
||||
|
||||
if (!isPersistent) {
|
||||
mCallback->ResolvePromise(aPromiseId);
|
||||
return;
|
||||
}
|
||||
|
||||
ClearKeyPersistence::PersistentSessionRemoved(sid);
|
||||
|
||||
// Overwrite the record storing the sessionId's key data with a zero
|
||||
// length record to delete it.
|
||||
vector<uint8_t> emptyKeydata;
|
||||
GMPTask* resolve = WrapTask(mCallback, &GMPDecryptorCallback::ResolvePromise, aPromiseId);
|
||||
static const char* message = "Could not remove session";
|
||||
GMPTask* reject = WrapTask(mCallback,
|
||||
&GMPDecryptorCallback::RejectPromise,
|
||||
aPromiseId,
|
||||
kGMPInvalidAccessError,
|
||||
message,
|
||||
strlen(message));
|
||||
StoreData(sessionId, emptyKeydata, resolve, reject);
|
||||
}
|
||||
|
||||
void
|
||||
@ -270,8 +420,11 @@ ClearKeyDecryptionManager::Decrypt(GMPBuffer* aBuffer,
|
||||
CK_LOGD("ClearKeyDecryptionManager::Decrypt");
|
||||
KeyId keyId(aMetadata->KeyId(), aMetadata->KeyId() + aMetadata->KeyIdSize());
|
||||
|
||||
if (mDecryptors.find(keyId) == mDecryptors.end() || !mDecryptors[keyId]) {
|
||||
auto itr = mDecryptors.find(keyId);
|
||||
if (itr == mDecryptors.end() || !(itr->second)) {
|
||||
CK_LOGD("ClearKeyDecryptionManager::Decrypt GMPNoKeyErr");
|
||||
mCallback->Decrypted(aBuffer, GMPNoKeyErr);
|
||||
return;
|
||||
}
|
||||
|
||||
mDecryptors[keyId]->QueueDecrypt(aBuffer, aMetadata);
|
||||
@ -290,7 +443,7 @@ ClearKeyDecryptionManager::DecryptingComplete()
|
||||
delete it->second;
|
||||
}
|
||||
|
||||
delete this;
|
||||
Release();
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -13,14 +13,14 @@
|
||||
#include "ClearKeyUtils.h"
|
||||
#include "gmp-api/gmp-decryption.h"
|
||||
#include "ScopedNSSTypes.h"
|
||||
#include "RefCounted.h"
|
||||
|
||||
class ClearKeyDecryptor;
|
||||
|
||||
class ClearKeyDecryptionManager MOZ_FINAL : public GMPDecryptor
|
||||
, public RefCounted
|
||||
{
|
||||
public:
|
||||
ClearKeyDecryptionManager();
|
||||
~ClearKeyDecryptionManager();
|
||||
|
||||
virtual void Init(GMPDecryptorCallback* aCallback) MOZ_OVERRIDE;
|
||||
|
||||
@ -58,7 +58,18 @@ public:
|
||||
|
||||
virtual void DecryptingComplete() MOZ_OVERRIDE;
|
||||
|
||||
void PersistentSessionDataLoaded(GMPErr aStatus,
|
||||
uint32_t aPromiseId,
|
||||
const std::string& aSessionId,
|
||||
const uint8_t* aKeyData,
|
||||
uint32_t aKeyDataSize);
|
||||
|
||||
private:
|
||||
~ClearKeyDecryptionManager();
|
||||
|
||||
void ClearInMemorySessionData(ClearKeySession* aSession);
|
||||
void Serialize(const ClearKeySession* aSession, std::vector<uint8_t>& aOutKeyData);
|
||||
|
||||
GMPDecryptorCallback* mCallback;
|
||||
|
||||
std::map<KeyId, ClearKeyDecryptor*> mDecryptors;
|
||||
|
236
media/gmp-clearkey/0.1/ClearKeyPersistence.cpp
Normal file
236
media/gmp-clearkey/0.1/ClearKeyPersistence.cpp
Normal file
@ -0,0 +1,236 @@
|
||||
/* 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 "ClearKeyPersistence.h"
|
||||
#include "ClearKeyUtils.h"
|
||||
#include "ClearKeyStorage.h"
|
||||
#include "ClearKeyDecryptionManager.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace std;
|
||||
|
||||
// Whether we've loaded the persistent session ids from GMPStorage yet.
|
||||
enum PersistentKeyState {
|
||||
UNINITIALIZED,
|
||||
LOADING,
|
||||
LOADED
|
||||
};
|
||||
static PersistentKeyState sPersistentKeyState = UNINITIALIZED;
|
||||
|
||||
// Set of session Ids of the persistent sessions created or residing in
|
||||
// storage.
|
||||
static set<uint32_t> sPersistentSessionIds;
|
||||
|
||||
static vector<GMPTask*> sTasksBlockedOnSessionIdLoad;
|
||||
|
||||
static void
|
||||
ReadAllRecordsFromIterator(GMPRecordIterator* aRecordIterator,
|
||||
void* aUserArg,
|
||||
GMPErr aStatus)
|
||||
{
|
||||
MOZ_ASSERT(sPersistentKeyState == LOADING);
|
||||
if (GMP_SUCCEEDED(aStatus)) {
|
||||
// Extract the record names which are valid uint32_t's; they're
|
||||
// the persistent session ids.
|
||||
const char* name = nullptr;
|
||||
uint32_t len = 0;
|
||||
while (GMP_SUCCEEDED(aRecordIterator->GetName(&name, &len))) {
|
||||
if (ClearKeyUtils::IsValidSessionId(name, len)) {
|
||||
MOZ_ASSERT(name[len] == 0);
|
||||
sPersistentSessionIds.insert(atoi(name));
|
||||
}
|
||||
aRecordIterator->NextRecord();
|
||||
}
|
||||
}
|
||||
sPersistentKeyState = LOADED;
|
||||
aRecordIterator->Close();
|
||||
|
||||
for (size_t i = 0; i < sTasksBlockedOnSessionIdLoad.size(); i++) {
|
||||
sTasksBlockedOnSessionIdLoad[i]->Run();
|
||||
sTasksBlockedOnSessionIdLoad[i]->Destroy();
|
||||
}
|
||||
sTasksBlockedOnSessionIdLoad.clear();
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
ClearKeyPersistence::EnsureInitialized()
|
||||
{
|
||||
if (sPersistentKeyState == UNINITIALIZED) {
|
||||
sPersistentKeyState = LOADING;
|
||||
if (GMP_FAILED(EnumRecordNames(&ReadAllRecordsFromIterator))) {
|
||||
sPersistentKeyState = LOADED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ string
|
||||
ClearKeyPersistence::GetNewSessionId(GMPSessionType aSessionType)
|
||||
{
|
||||
static uint32_t sNextSessionId = 1;
|
||||
|
||||
// Ensure we don't re-use a session id that was persisted.
|
||||
while (Contains(sPersistentSessionIds, sNextSessionId)) {
|
||||
sNextSessionId++;
|
||||
}
|
||||
|
||||
string sessionId;
|
||||
stringstream ss;
|
||||
ss << sNextSessionId;
|
||||
ss >> sessionId;
|
||||
|
||||
if (aSessionType == kGMPPersistentSession) {
|
||||
sPersistentSessionIds.insert(sNextSessionId);
|
||||
}
|
||||
|
||||
sNextSessionId++;
|
||||
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
|
||||
class CreateSessionTask : public GMPTask {
|
||||
public:
|
||||
CreateSessionTask(ClearKeyDecryptionManager* aTarget,
|
||||
uint32_t aPromiseId,
|
||||
const uint8_t* aInitData,
|
||||
uint32_t aInitDataSize,
|
||||
GMPSessionType aSessionType)
|
||||
: mTarget(aTarget)
|
||||
, mPromiseId(aPromiseId)
|
||||
, mSessionType(aSessionType)
|
||||
{
|
||||
mInitData.insert(mInitData.end(),
|
||||
aInitData,
|
||||
aInitData + aInitDataSize);
|
||||
}
|
||||
virtual void Run() MOZ_OVERRIDE {
|
||||
mTarget->CreateSession(mPromiseId,
|
||||
"cenc",
|
||||
strlen("cenc"),
|
||||
&mInitData.front(),
|
||||
mInitData.size(),
|
||||
mSessionType);
|
||||
}
|
||||
virtual void Destroy() MOZ_OVERRIDE {
|
||||
delete this;
|
||||
}
|
||||
private:
|
||||
RefPtr<ClearKeyDecryptionManager> mTarget;
|
||||
uint32_t mPromiseId;
|
||||
vector<uint8_t> mInitData;
|
||||
GMPSessionType mSessionType;
|
||||
};
|
||||
|
||||
|
||||
/* static */ bool
|
||||
ClearKeyPersistence::DeferCreateSessionIfNotReady(ClearKeyDecryptionManager* aInstance,
|
||||
uint32_t aPromiseId,
|
||||
const uint8_t* aInitData,
|
||||
uint32_t aInitDataSize,
|
||||
GMPSessionType aSessionType)
|
||||
{
|
||||
if (sPersistentKeyState >= LOADED) {
|
||||
return false;
|
||||
}
|
||||
GMPTask* t = new CreateSessionTask(aInstance,
|
||||
aPromiseId,
|
||||
aInitData,
|
||||
aInitDataSize,
|
||||
aSessionType);
|
||||
sTasksBlockedOnSessionIdLoad.push_back(t);
|
||||
return true;
|
||||
}
|
||||
|
||||
class LoadSessionTask : public GMPTask {
|
||||
public:
|
||||
LoadSessionTask(ClearKeyDecryptionManager* aTarget,
|
||||
uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdLength)
|
||||
: mTarget(aTarget)
|
||||
, mPromiseId(aPromiseId)
|
||||
, mSessionId(aSessionId, aSessionId + aSessionIdLength)
|
||||
{
|
||||
}
|
||||
virtual void Run() MOZ_OVERRIDE {
|
||||
mTarget->LoadSession(mPromiseId,
|
||||
mSessionId.c_str(),
|
||||
mSessionId.size());
|
||||
}
|
||||
virtual void Destroy() MOZ_OVERRIDE {
|
||||
delete this;
|
||||
}
|
||||
private:
|
||||
RefPtr<ClearKeyDecryptionManager> mTarget;
|
||||
uint32_t mPromiseId;
|
||||
string mSessionId;
|
||||
};
|
||||
|
||||
/* static */ bool
|
||||
ClearKeyPersistence::DeferLoadSessionIfNotReady(ClearKeyDecryptionManager* aInstance,
|
||||
uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdLength)
|
||||
{
|
||||
if (sPersistentKeyState >= LOADED) {
|
||||
return false;
|
||||
}
|
||||
GMPTask* t = new LoadSessionTask(aInstance,
|
||||
aPromiseId,
|
||||
aSessionId,
|
||||
aSessionIdLength);
|
||||
sTasksBlockedOnSessionIdLoad.push_back(t);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
ClearKeyPersistence::IsPersistentSessionId(const string& aSessionId)
|
||||
{
|
||||
return Contains(sPersistentSessionIds, atoi(aSessionId.c_str()));
|
||||
}
|
||||
|
||||
class LoadSessionFromKeysTask : public ReadContinuation {
|
||||
public:
|
||||
LoadSessionFromKeysTask(ClearKeyDecryptionManager* aTarget,
|
||||
const string& aSessionId,
|
||||
uint32_t aPromiseId)
|
||||
: mTarget(aTarget)
|
||||
, mSessionId(aSessionId)
|
||||
, mPromiseId(aPromiseId)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void ReadComplete(GMPErr aStatus,
|
||||
const uint8_t* aData,
|
||||
uint32_t aLength) MOZ_OVERRIDE
|
||||
{
|
||||
mTarget->PersistentSessionDataLoaded(aStatus, mPromiseId, mSessionId, aData, aLength);
|
||||
}
|
||||
private:
|
||||
RefPtr<ClearKeyDecryptionManager> mTarget;
|
||||
string mSessionId;
|
||||
uint32_t mPromiseId;
|
||||
};
|
||||
|
||||
/* static */ void
|
||||
ClearKeyPersistence::LoadSessionData(ClearKeyDecryptionManager* aInstance,
|
||||
const string& aSid,
|
||||
uint32_t aPromiseId)
|
||||
{
|
||||
LoadSessionFromKeysTask* loadTask =
|
||||
new LoadSessionFromKeysTask(aInstance, aSid, aPromiseId);
|
||||
ReadData(aSid, loadTask);
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
ClearKeyPersistence::PersistentSessionRemoved(const string& aSessionId)
|
||||
{
|
||||
sPersistentSessionIds.erase(atoi(aSessionId.c_str()));
|
||||
}
|
39
media/gmp-clearkey/0.1/ClearKeyPersistence.h
Normal file
39
media/gmp-clearkey/0.1/ClearKeyPersistence.h
Normal file
@ -0,0 +1,39 @@
|
||||
/* 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 __ClearKeyPersistence_h__
|
||||
#define __ClearKeyPersistence_h__
|
||||
|
||||
#include <string>
|
||||
#include "gmp-decryption.h"
|
||||
|
||||
class ClearKeyDecryptionManager;
|
||||
|
||||
class ClearKeyPersistence {
|
||||
public:
|
||||
static void EnsureInitialized();
|
||||
|
||||
static std::string GetNewSessionId(GMPSessionType aSessionType);
|
||||
|
||||
static bool DeferCreateSessionIfNotReady(ClearKeyDecryptionManager* aInstance,
|
||||
uint32_t aPromiseId,
|
||||
const uint8_t* aInitData,
|
||||
uint32_t aInitDataSize,
|
||||
GMPSessionType aSessionType);
|
||||
|
||||
static bool DeferLoadSessionIfNotReady(ClearKeyDecryptionManager* aInstance,
|
||||
uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdLength);
|
||||
|
||||
static bool IsPersistentSessionId(const std::string& aSid);
|
||||
|
||||
static void LoadSessionData(ClearKeyDecryptionManager* aInstance,
|
||||
const std::string& aSid,
|
||||
uint32_t aPromiseId);
|
||||
|
||||
static void PersistentSessionRemoved(const std::string& aSid);
|
||||
};
|
||||
|
||||
#endif // __ClearKeyPersistence_h__
|
@ -4,6 +4,8 @@
|
||||
|
||||
#include "ClearKeySession.h"
|
||||
#include "ClearKeyUtils.h"
|
||||
#include "ClearKeyStorage.h"
|
||||
#include "gmp-task-utils.h"
|
||||
|
||||
#include "gmp-api/gmp-decryption.h"
|
||||
#include "mozilla/Endian.h"
|
||||
@ -12,9 +14,11 @@
|
||||
using namespace mozilla;
|
||||
|
||||
ClearKeySession::ClearKeySession(const std::string& aSessionId,
|
||||
GMPDecryptorCallback* aCallback)
|
||||
GMPDecryptorCallback* aCallback,
|
||||
GMPSessionType aSessionType)
|
||||
: mSessionId(aSessionId)
|
||||
, mCallback(aCallback)
|
||||
, mSessionType(aSessionType)
|
||||
{
|
||||
CK_LOGD("ClearKeySession ctor %p", this);
|
||||
}
|
||||
@ -36,7 +40,18 @@ ClearKeySession::Init(uint32_t aPromiseId,
|
||||
mCallback->RejectPromise(aPromiseId, kGMPAbortError, message, strlen(message));
|
||||
return;
|
||||
}
|
||||
|
||||
mCallback->ResolveNewSessionPromise(aPromiseId,
|
||||
mSessionId.data(), mSessionId.length());
|
||||
}
|
||||
|
||||
GMPSessionType
|
||||
ClearKeySession::Type() const
|
||||
{
|
||||
return mSessionType;
|
||||
}
|
||||
|
||||
void
|
||||
ClearKeySession::AddKeyId(const KeyId& aKeyId)
|
||||
{
|
||||
mKeyIds.push_back(aKeyId);
|
||||
}
|
||||
|
@ -6,34 +6,39 @@
|
||||
#define __ClearKeySession_h__
|
||||
|
||||
#include "ClearKeyUtils.h"
|
||||
#include "gmp-decryption.h"
|
||||
|
||||
class GMPBuffer;
|
||||
class GMPDecryptorCallback;
|
||||
class GMPDecryptorHost;
|
||||
class GMPEncryptedBufferMetadata;
|
||||
|
||||
/**
|
||||
* Currently useless; will be fleshed out later with support for persistent
|
||||
* key sessions.
|
||||
*/
|
||||
|
||||
class ClearKeySession
|
||||
{
|
||||
public:
|
||||
ClearKeySession(const std::string& aSessionId,
|
||||
GMPDecryptorCallback* aCallback);
|
||||
explicit ClearKeySession(const std::string& aSessionId,
|
||||
GMPDecryptorCallback* aCallback,
|
||||
GMPSessionType aSessionType);
|
||||
|
||||
~ClearKeySession();
|
||||
|
||||
const std::vector<KeyId>& GetKeyIds() { return mKeyIds; }
|
||||
const std::vector<KeyId>& GetKeyIds() const { return mKeyIds; }
|
||||
|
||||
void Init(uint32_t aPromiseId,
|
||||
const uint8_t* aInitData, uint32_t aInitDataSize);
|
||||
|
||||
GMPSessionType Type() const;
|
||||
|
||||
void AddKeyId(const KeyId& aKeyId);
|
||||
|
||||
const std::string Id() const { return mSessionId; }
|
||||
|
||||
private:
|
||||
std::string mSessionId;
|
||||
const std::string mSessionId;
|
||||
std::vector<KeyId> mKeyIds;
|
||||
|
||||
GMPDecryptorCallback* mCallback;
|
||||
const GMPSessionType mSessionType;
|
||||
};
|
||||
|
||||
#endif // __ClearKeySession_h__
|
||||
|
174
media/gmp-clearkey/0.1/ClearKeyStorage.cpp
Normal file
174
media/gmp-clearkey/0.1/ClearKeyStorage.cpp
Normal file
@ -0,0 +1,174 @@
|
||||
/* 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 "ClearKeyStorage.h"
|
||||
#include "ClearKeyUtils.h"
|
||||
|
||||
#include "gmp-task-utils.h"
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/NullPtr.h"
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
static GMPErr
|
||||
RunOnMainThread(GMPTask* aTask)
|
||||
{
|
||||
return GetPlatform()->runonmainthread(aTask);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
virtual void OpenComplete(GMPErr aStatus) MOZ_OVERRIDE {
|
||||
if (GMP_FAILED(aStatus) ||
|
||||
GMP_FAILED(mRecord->Write(&mData.front(), mData.size()))) {
|
||||
RunOnMainThread(mOnFailure);
|
||||
mOnSuccess->Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
virtual void ReadComplete(GMPErr aStatus,
|
||||
const uint8_t* aData,
|
||||
uint32_t aDataSize) MOZ_OVERRIDE {
|
||||
MOZ_ASSERT(false, "Should not reach here.");
|
||||
}
|
||||
|
||||
virtual void WriteComplete(GMPErr aStatus) MOZ_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)) {
|
||||
RunOnMainThread(mOnSuccess);
|
||||
mOnFailure->Destroy();
|
||||
} else {
|
||||
RunOnMainThread(mOnFailure);
|
||||
mOnSuccess->Destroy();
|
||||
}
|
||||
delete this;
|
||||
}
|
||||
|
||||
private:
|
||||
GMPRecord* mRecord;
|
||||
GMPTask* mOnSuccess;
|
||||
GMPTask* mOnFailure;
|
||||
std::vector<uint8_t> mData;
|
||||
};
|
||||
|
||||
GMPErr
|
||||
OpenRecord(const char* aName,
|
||||
uint32_t aNameLength,
|
||||
GMPRecord** aOutRecord,
|
||||
GMPRecordClient* aClient)
|
||||
{
|
||||
return GetPlatform()->createrecord(aName, aNameLength, aOutRecord, aClient);
|
||||
}
|
||||
|
||||
void
|
||||
StoreData(const std::string& aRecordName,
|
||||
const std::vector<uint8_t>& aData,
|
||||
GMPTask* aOnSuccess,
|
||||
GMPTask* aOnFailure)
|
||||
{
|
||||
GMPRecord* record;
|
||||
WriteRecordClient* client = new WriteRecordClient();
|
||||
if (GMP_FAILED(OpenRecord(aRecordName.c_str(),
|
||||
aRecordName.size(),
|
||||
&record,
|
||||
client)) ||
|
||||
GMP_FAILED(client->Init(record,
|
||||
aOnSuccess,
|
||||
aOnFailure,
|
||||
&aData.front(),
|
||||
aData.size()))) {
|
||||
RunOnMainThread(aOnFailure);
|
||||
aOnSuccess->Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
class ReadRecordClient : public GMPRecordClient {
|
||||
public:
|
||||
ReadRecordClient()
|
||||
: mRecord(nullptr)
|
||||
, mContinuation(nullptr)
|
||||
{}
|
||||
~ReadRecordClient() {
|
||||
delete mContinuation;
|
||||
}
|
||||
|
||||
GMPErr Init(GMPRecord* aRecord,
|
||||
ReadContinuation* aContinuation) {
|
||||
mRecord = aRecord;
|
||||
mContinuation = aContinuation;
|
||||
return mRecord->Open();
|
||||
}
|
||||
|
||||
virtual void OpenComplete(GMPErr aStatus) MOZ_OVERRIDE {
|
||||
auto err = mRecord->Read();
|
||||
if (GMP_FAILED(err)) {
|
||||
mContinuation->ReadComplete(err, nullptr, 0);
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
||||
virtual void ReadComplete(GMPErr aStatus,
|
||||
const uint8_t* aData,
|
||||
uint32_t aDataSize) MOZ_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();
|
||||
mContinuation->ReadComplete(GMPNoErr, aData, aDataSize);
|
||||
delete this;
|
||||
}
|
||||
|
||||
virtual void WriteComplete(GMPErr aStatus) MOZ_OVERRIDE {
|
||||
MOZ_ASSERT(false, "Should not reach here.");
|
||||
}
|
||||
|
||||
private:
|
||||
GMPRecord* mRecord;
|
||||
ReadContinuation* mContinuation;
|
||||
};
|
||||
|
||||
void
|
||||
ReadData(const std::string& aRecordName,
|
||||
ReadContinuation* aContinuation)
|
||||
{
|
||||
MOZ_ASSERT(aContinuation);
|
||||
GMPRecord* record;
|
||||
ReadRecordClient* client = new ReadRecordClient();
|
||||
auto err = OpenRecord(aRecordName.c_str(),
|
||||
aRecordName.size(),
|
||||
&record,
|
||||
client);
|
||||
if (GMP_FAILED(err) ||
|
||||
GMP_FAILED(client->Init(record, aContinuation))) {
|
||||
aContinuation->ReadComplete(err, nullptr, 0);
|
||||
delete aContinuation;
|
||||
}
|
||||
}
|
||||
|
||||
GMPErr
|
||||
EnumRecordNames(RecvGMPRecordIteratorPtr aRecvIteratorFunc)
|
||||
{
|
||||
return GetPlatform()->getrecordenumerator(aRecvIteratorFunc, nullptr);
|
||||
}
|
36
media/gmp-clearkey/0.1/ClearKeyStorage.h
Normal file
36
media/gmp-clearkey/0.1/ClearKeyStorage.h
Normal file
@ -0,0 +1,36 @@
|
||||
/* 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 __ClearKeyStorage_h__
|
||||
#define __ClearKeyStorage_h__
|
||||
|
||||
#include "gmp-errors.h"
|
||||
#include "gmp-platform.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <stdint.h>
|
||||
|
||||
class GMPTask;
|
||||
|
||||
// Responsible for ensuring that both aOnSuccess and aOnFailure are destroyed.
|
||||
void StoreData(const std::string& aRecordName,
|
||||
const std::vector<uint8_t>& aData,
|
||||
GMPTask* aOnSuccess,
|
||||
GMPTask* aOnFailure);
|
||||
|
||||
class ReadContinuation {
|
||||
public:
|
||||
virtual void ReadComplete(GMPErr aStatus,
|
||||
const uint8_t* aData,
|
||||
uint32_t aLength) = 0;
|
||||
virtual ~ReadContinuation() {}
|
||||
};
|
||||
|
||||
// Deletes aContinuation after running it to report the result.
|
||||
void ReadData(const std::string& aSessionId,
|
||||
ReadContinuation* aContinuation);
|
||||
|
||||
GMPErr EnumRecordNames(RecvGMPRecordIteratorPtr aRecvIteratorFunc);
|
||||
|
||||
#endif // __ClearKeyStorage_h__
|
@ -177,7 +177,8 @@ ClearKeyUtils::ParseInitData(const uint8_t* aInitData, uint32_t aInitDataSize,
|
||||
|
||||
/* static */ void
|
||||
ClearKeyUtils::MakeKeyRequest(const vector<KeyId>& aKeyIDs,
|
||||
string& aOutRequest)
|
||||
string& aOutRequest,
|
||||
GMPSessionType aSessionType)
|
||||
{
|
||||
MOZ_ASSERT(aKeyIDs.size() && aOutRequest.empty());
|
||||
|
||||
@ -195,9 +196,10 @@ ClearKeyUtils::MakeKeyRequest(const vector<KeyId>& aKeyIDs,
|
||||
aOutRequest.append("\"");
|
||||
}
|
||||
aOutRequest.append("], \"type\":");
|
||||
// TODO implement "persistent" session type
|
||||
aOutRequest.append("\"temporary\"");
|
||||
aOutRequest.append("}");
|
||||
|
||||
aOutRequest.append("\"");
|
||||
aOutRequest.append(SessionTypeToString(aSessionType));
|
||||
aOutRequest.append("\"}");
|
||||
}
|
||||
|
||||
#define EXPECT_SYMBOL(CTX, X) do { \
|
||||
@ -509,7 +511,8 @@ ParseKeys(ParserContext& aCtx, vector<KeyIdPair>& aOutKeys)
|
||||
|
||||
/* static */ bool
|
||||
ClearKeyUtils::ParseJWK(const uint8_t* aKeyData, uint32_t aKeyDataSize,
|
||||
vector<KeyIdPair>& aOutKeys)
|
||||
vector<KeyIdPair>& aOutKeys,
|
||||
GMPSessionType aSessionType)
|
||||
{
|
||||
ParserContext ctx;
|
||||
ctx.mIter = aKeyData;
|
||||
@ -531,8 +534,7 @@ ClearKeyUtils::ParseJWK(const uint8_t* aKeyData, uint32_t aKeyDataSize,
|
||||
// Consume type string.
|
||||
string type;
|
||||
if (!GetNextLabel(ctx, type)) return false;
|
||||
// XXX todo support "persistent" session type
|
||||
if (type != "temporary") {
|
||||
if (type != SessionTypeToString(aSessionType)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
@ -553,3 +555,32 @@ ClearKeyUtils::ParseJWK(const uint8_t* aKeyData, uint32_t aKeyDataSize,
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */ const char*
|
||||
ClearKeyUtils::SessionTypeToString(GMPSessionType aSessionType)
|
||||
{
|
||||
switch (aSessionType) {
|
||||
case kGMPTemporySession: return "temporary";
|
||||
case kGMPPersistentSession: return "persistent";
|
||||
default: {
|
||||
MOZ_ASSERT(false, "Should not reach here.");
|
||||
return "invalid";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
ClearKeyUtils::IsValidSessionId(const char* aBuff, uint32_t aLength)
|
||||
{
|
||||
if (aLength > 10) {
|
||||
// 10 is the max number of characters in UINT32_MAX when
|
||||
// represented as a string; ClearKey session ids are integers.
|
||||
return false;
|
||||
}
|
||||
for (uint32_t i = 0; i < aLength; i++) {
|
||||
if (!isdigit(aBuff[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "gmp-decryption.h"
|
||||
|
||||
#define CLEARKEY_KEY_LEN ((size_t)16)
|
||||
|
||||
@ -44,10 +45,22 @@ public:
|
||||
std::vector<Key>& aOutKeys);
|
||||
|
||||
static void MakeKeyRequest(const std::vector<KeyId>& aKeyIds,
|
||||
std::string& aOutRequest);
|
||||
std::string& aOutRequest,
|
||||
GMPSessionType aSessionType);
|
||||
|
||||
static bool ParseJWK(const uint8_t* aKeyData, uint32_t aKeyDataSize,
|
||||
std::vector<KeyIdPair>& aOutKeys);
|
||||
std::vector<KeyIdPair>& aOutKeys,
|
||||
GMPSessionType aSessionType);
|
||||
static const char* SessionTypeToString(GMPSessionType aSessionType);
|
||||
|
||||
static bool IsValidSessionId(const char* aBuff, uint32_t aLength);
|
||||
};
|
||||
|
||||
template<class Container, class Element>
|
||||
inline bool
|
||||
Contains(const Container& aContainer, const Element& aElement)
|
||||
{
|
||||
return aContainer.find(aElement) != aContainer.end();
|
||||
}
|
||||
|
||||
#endif // __ClearKeyUtils_h__
|
||||
|
34
media/gmp-clearkey/0.1/RefCounted.h
Normal file
34
media/gmp-clearkey/0.1/RefCounted.h
Normal file
@ -0,0 +1,34 @@
|
||||
/* 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 __RefCount_h__
|
||||
#define __RefCount_h__
|
||||
|
||||
// Note: Not thread safe!
|
||||
class RefCounted {
|
||||
public:
|
||||
void AddRef() {
|
||||
++mRefCount;
|
||||
}
|
||||
|
||||
void Release() {
|
||||
if (mRefCount == 1) {
|
||||
delete this;
|
||||
} else {
|
||||
--mRefCount;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
RefCounted()
|
||||
: mRefCount(0)
|
||||
{
|
||||
}
|
||||
virtual ~RefCounted()
|
||||
{
|
||||
}
|
||||
uint32_t mRefCount;
|
||||
};
|
||||
|
||||
#endif // __RefCount_h__
|
1898
media/gmp-clearkey/0.1/gmp-task-utils-generated.h
Normal file
1898
media/gmp-clearkey/0.1/gmp-task-utils-generated.h
Normal file
File diff suppressed because it is too large
Load Diff
37
media/gmp-clearkey/0.1/gmp-task-utils.h
Normal file
37
media/gmp-clearkey/0.1/gmp-task-utils.h
Normal file
@ -0,0 +1,37 @@
|
||||
/* -*- 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/. */
|
||||
|
||||
// Original author: ekr@rtfm.com
|
||||
|
||||
#ifndef gmp_task_utils_h_
|
||||
#define gmp_task_utils_h_
|
||||
|
||||
#include "gmp-platform.h"
|
||||
|
||||
class gmp_task_args_base : public GMPTask {
|
||||
public:
|
||||
virtual void Destroy() { delete this; }
|
||||
virtual void Run() = 0;
|
||||
};
|
||||
|
||||
// The generated file contains four major function templates
|
||||
// (in variants for arbitrary numbers of arguments up to 10,
|
||||
// which is why it is machine generated). The four templates
|
||||
// are:
|
||||
//
|
||||
// WrapTask(o, m, ...) -- wraps a member function m of an object ptr o
|
||||
// WrapTaskRet(o, m, ..., r) -- wraps a member function m of an object ptr o
|
||||
// the function returns something that can
|
||||
// be assigned to *r
|
||||
// WrapTaskNM(f, ...) -- wraps a function f
|
||||
// WrapTaskNMRet(f, ..., r) -- wraps a function f that returns something
|
||||
// that can be assigned to *r
|
||||
//
|
||||
// All of these template functions return a GMPTask* which can be passed
|
||||
// to DispatchXX().
|
||||
#include "gmp-task-utils-generated.h"
|
||||
|
||||
#endif // gmp_task_utils_h_
|
@ -10,7 +10,9 @@ FINAL_TARGET = 'dist/bin/gmp-clearkey/0.1'
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'ClearKeyDecryptionManager.cpp',
|
||||
'ClearKeyPersistence.cpp',
|
||||
'ClearKeySession.cpp',
|
||||
'ClearKeyStorage.cpp',
|
||||
'ClearKeyUtils.cpp',
|
||||
'gmp-clearkey.cpp',
|
||||
'openaes/oaes_lib.c',
|
||||
|
Loading…
Reference in New Issue
Block a user