summaryrefslogtreecommitdiff
path: root/media/gmp-clearkey/0.1/ClearKeySessionManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'media/gmp-clearkey/0.1/ClearKeySessionManager.cpp')
-rw-r--r--media/gmp-clearkey/0.1/ClearKeySessionManager.cpp418
1 files changed, 418 insertions, 0 deletions
diff --git a/media/gmp-clearkey/0.1/ClearKeySessionManager.cpp b/media/gmp-clearkey/0.1/ClearKeySessionManager.cpp
new file mode 100644
index 0000000000..4dbb062992
--- /dev/null
+++ b/media/gmp-clearkey/0.1/ClearKeySessionManager.cpp
@@ -0,0 +1,418 @@
+/*
+ * Copyright 2015, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "ClearKeyDecryptionManager.h"
+#include "ClearKeySessionManager.h"
+#include "ClearKeyUtils.h"
+#include "ClearKeyStorage.h"
+#include "ClearKeyPersistence.h"
+#include "gmp-task-utils.h"
+#include <assert.h>
+
+using namespace std;
+
+ClearKeySessionManager::ClearKeySessionManager()
+ : mDecryptionManager(ClearKeyDecryptionManager::Get())
+{
+ CK_LOGD("ClearKeySessionManager ctor %p", this);
+ AddRef();
+
+ if (GetPlatform()->createthread(&mThread) != GMPNoErr) {
+ CK_LOGD("failed to create thread in clearkey cdm");
+ mThread = nullptr;
+ }
+}
+
+ClearKeySessionManager::~ClearKeySessionManager()
+{
+ CK_LOGD("ClearKeySessionManager dtor %p", this);
+}
+
+void
+ClearKeySessionManager::Init(GMPDecryptorCallback* aCallback,
+ bool aDistinctiveIdentifierAllowed,
+ bool aPersistentStateAllowed)
+{
+ CK_LOGD("ClearKeySessionManager::Init");
+ mCallback = aCallback;
+ ClearKeyPersistence::EnsureInitialized();
+}
+
+void
+ClearKeySessionManager::CreateSession(uint32_t aCreateSessionToken,
+ uint32_t aPromiseId,
+ const char* aInitDataType,
+ uint32_t aInitDataTypeSize,
+ const uint8_t* aInitData,
+ uint32_t aInitDataSize,
+ GMPSessionType aSessionType)
+{
+ CK_LOGD("ClearKeySessionManager::CreateSession type:%s", aInitDataType);
+
+ string initDataType(aInitDataType, aInitDataType + aInitDataTypeSize);
+ // initDataType must be "cenc", "keyids", or "webm".
+ if (initDataType != "cenc" &&
+ initDataType != "keyids" &&
+ initDataType != "webm") {
+ string message = "'" + initDataType + "' is an initDataType unsupported by ClearKey";
+ mCallback->RejectPromise(aPromiseId, kGMPNotSupportedError,
+ message.c_str(), message.size());
+ return;
+ }
+
+ if (ClearKeyPersistence::DeferCreateSessionIfNotReady(this,
+ aCreateSessionToken,
+ aPromiseId,
+ initDataType,
+ aInitData,
+ aInitDataSize,
+ aSessionType)) {
+ return;
+ }
+
+ string sessionId = ClearKeyPersistence::GetNewSessionId(aSessionType);
+ assert(mSessions.find(sessionId) == mSessions.end());
+
+ ClearKeySession* session = new ClearKeySession(sessionId, mCallback, aSessionType);
+ session->Init(aCreateSessionToken, aPromiseId, initDataType, aInitData, aInitDataSize);
+ mSessions[sessionId] = session;
+
+ const vector<KeyId>& sessionKeys = session->GetKeyIds();
+ vector<KeyId> neededKeys;
+ for (auto it = sessionKeys.begin(); it != sessionKeys.end(); it++) {
+ // Need to request this key ID from the client. We always send a key
+ // request, whether or not another session has sent a request with the same
+ // key ID. Otherwise a script can end up waiting for another script to
+ // respond to the request (which may not necessarily happen).
+ neededKeys.push_back(*it);
+ mDecryptionManager->ExpectKeyId(*it);
+ }
+
+ if (neededKeys.empty()) {
+ CK_LOGD("No keys needed from client.");
+ return;
+ }
+
+ // Send a request for needed key data.
+ string request;
+ ClearKeyUtils::MakeKeyRequest(neededKeys, request, aSessionType);
+ mCallback->SessionMessage(&sessionId[0], sessionId.length(),
+ kGMPLicenseRequest,
+ (uint8_t*)&request[0], request.length());
+}
+
+void
+ClearKeySessionManager::LoadSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength)
+{
+ CK_LOGD("ClearKeySessionManager::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
+ClearKeySessionManager::PersistentSessionDataLoaded(GMPErr aStatus,
+ uint32_t aPromiseId,
+ const string& aSessionId,
+ const uint8_t* aKeyData,
+ uint32_t aKeyDataSize)
+{
+ CK_LOGD("ClearKeySessionManager::PersistentSessionDataLoaded");
+ if (GMP_FAILED(aStatus) ||
+ Contains(mSessions, aSessionId) ||
+ (aKeyDataSize % (2 * CENC_KEY_LEN)) != 0) {
+ mCallback->ResolveLoadSessionPromise(aPromiseId, false);
+ return;
+ }
+
+ ClearKeySession* session = new ClearKeySession(aSessionId,
+ mCallback,
+ kGMPPersistentSession);
+ mSessions[aSessionId] = session;
+
+ uint32_t numKeys = aKeyDataSize / (2 * CENC_KEY_LEN);
+
+ vector<GMPMediaKeyInfo> key_infos;
+ vector<KeyIdPair> keyPairs;
+ for (uint32_t i = 0; i < numKeys; i ++) {
+ const uint8_t* base = aKeyData + 2 * CENC_KEY_LEN * i;
+
+ KeyIdPair keyPair;
+
+ keyPair.mKeyId = KeyId(base, base + CENC_KEY_LEN);
+ assert(keyPair.mKeyId.size() == CENC_KEY_LEN);
+
+ keyPair.mKey = Key(base + CENC_KEY_LEN, base + 2 * CENC_KEY_LEN);
+ assert(keyPair.mKey.size() == CENC_KEY_LEN);
+
+ session->AddKeyId(keyPair.mKeyId);
+
+ mDecryptionManager->ExpectKeyId(keyPair.mKeyId);
+ mDecryptionManager->InitKey(keyPair.mKeyId, keyPair.mKey);
+ mKeyIds.insert(keyPair.mKey);
+
+ keyPairs.push_back(keyPair);
+ key_infos.push_back(GMPMediaKeyInfo(&keyPairs[i].mKeyId[0],
+ keyPairs[i].mKeyId.size(),
+ kGMPUsable));
+ }
+ mCallback->BatchedKeyStatusChanged(&aSessionId[0], aSessionId.size(),
+ key_infos.data(), key_infos.size());
+
+ mCallback->ResolveLoadSessionPromise(aPromiseId, true);
+}
+
+void
+ClearKeySessionManager::UpdateSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const uint8_t* aResponse,
+ uint32_t aResponseSize)
+{
+ CK_LOGD("ClearKeySessionManager::UpdateSession");
+ string sessionId(aSessionId, aSessionId + aSessionIdLength);
+
+ 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;
+
+ // Verify the size of session response.
+ if (aResponseSize >= kMaxSessionResponseLength) {
+ CK_LOGW("Session response size is not within a reasonable size.");
+ mCallback->RejectPromise(aPromiseId, kGMPTypeError, nullptr, 0);
+ return;
+ }
+
+ // Parse the response for any (key ID, key) pairs.
+ vector<KeyIdPair> keyPairs;
+ if (!ClearKeyUtils::ParseJWK(aResponse, aResponseSize, keyPairs, session->Type())) {
+ CK_LOGW("ClearKey CDM failed to parse JSON Web Key.");
+ mCallback->RejectPromise(aPromiseId, kGMPTypeError, nullptr, 0);
+ return;
+ }
+
+ vector<GMPMediaKeyInfo> key_infos;
+ for (size_t i = 0; i < keyPairs.size(); i++) {
+ KeyIdPair& keyPair = keyPairs[i];
+ mDecryptionManager->InitKey(keyPair.mKeyId, keyPair.mKey);
+ mKeyIds.insert(keyPair.mKeyId);
+ key_infos.push_back(GMPMediaKeyInfo(&keyPair.mKeyId[0],
+ keyPair.mKeyId.size(),
+ kGMPUsable));
+ }
+ mCallback->BatchedKeyStatusChanged(aSessionId, aSessionIdLength,
+ key_infos.data(), key_infos.size());
+
+ 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
+ClearKeySessionManager::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 (!mDecryptionManager->HasKeyForKeyId(keyId)) {
+ continue;
+ }
+ assert(keyId.size() == CENC_KEY_LEN);
+ aOutKeyData.insert(aOutKeyData.end(), keyId.begin(), keyId.end());
+ const Key& key = mDecryptionManager->GetDecryptionKey(keyId);
+ assert(key.size() == CENC_KEY_LEN);
+ aOutKeyData.insert(aOutKeyData.end(), key.begin(), key.end());
+ }
+}
+
+void
+ClearKeySessionManager::CloseSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength)
+{
+ CK_LOGD("ClearKeySessionManager::CloseSession");
+
+ string sessionId(aSessionId, aSessionId + aSessionIdLength);
+ 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;
+ assert(session);
+
+ ClearInMemorySessionData(session);
+ mCallback->SessionClosed(aSessionId, aSessionIdLength);
+ mCallback->ResolvePromise(aPromiseId);
+}
+
+void
+ClearKeySessionManager::ClearInMemorySessionData(ClearKeySession* aSession)
+{
+ mSessions.erase(aSession->Id());
+ delete aSession;
+}
+
+void
+ClearKeySessionManager::RemoveSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength)
+{
+ CK_LOGD("ClearKeySessionManager::RemoveSession");
+ 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;
+ 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
+ClearKeySessionManager::SetServerCertificate(uint32_t aPromiseId,
+ const uint8_t* aServerCert,
+ uint32_t aServerCertSize)
+{
+ // ClearKey CDM doesn't support this method by spec.
+ CK_LOGD("ClearKeySessionManager::SetServerCertificate");
+ mCallback->RejectPromise(aPromiseId, kGMPNotSupportedError,
+ nullptr /* message */, 0 /* messageLen */);
+}
+
+void
+ClearKeySessionManager::Decrypt(GMPBuffer* aBuffer,
+ GMPEncryptedBufferMetadata* aMetadata)
+{
+ CK_LOGD("ClearKeySessionManager::Decrypt");
+
+ if (!mThread) {
+ CK_LOGW("No decrypt thread");
+ mCallback->Decrypted(aBuffer, GMPGenericErr);
+ return;
+ }
+
+ mThread->Post(WrapTaskRefCounted(this,
+ &ClearKeySessionManager::DoDecrypt,
+ aBuffer, aMetadata));
+}
+
+void
+ClearKeySessionManager::DoDecrypt(GMPBuffer* aBuffer,
+ GMPEncryptedBufferMetadata* aMetadata)
+{
+ CK_LOGD("ClearKeySessionManager::DoDecrypt");
+
+ GMPErr rv = mDecryptionManager->Decrypt(aBuffer->Data(), aBuffer->Size(),
+ CryptoMetaData(aMetadata));
+ CK_LOGD("DeDecrypt finished with code %x\n", rv);
+ mCallback->Decrypted(aBuffer, rv);
+}
+
+void
+ClearKeySessionManager::Shutdown()
+{
+ CK_LOGD("ClearKeySessionManager::Shutdown %p", this);
+
+ for (auto it = mSessions.begin(); it != mSessions.end(); it++) {
+ delete it->second;
+ }
+ mSessions.clear();
+}
+
+void
+ClearKeySessionManager::DecryptingComplete()
+{
+ CK_LOGD("ClearKeySessionManager::DecryptingComplete %p", this);
+
+ GMPThread* thread = mThread;
+ thread->Join();
+
+ Shutdown();
+ mDecryptionManager = nullptr;
+ Release();
+}