summaryrefslogtreecommitdiffstats
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.cpp713
1 files changed, 713 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..54a15f4fcf
--- /dev/null
+++ b/media/gmp-clearkey/0.1/ClearKeySessionManager.cpp
@@ -0,0 +1,713 @@
+/*
+ * 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 "ClearKeySessionManager.h"
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "content_decryption_module.h"
+
+#include "ClearKeyDecryptionManager.h"
+#include "ClearKeyPersistence.h"
+#include "ClearKeyStorage.h"
+#include "ClearKeyUtils.h"
+#include "psshparser/PsshParser.h"
+
+using namespace cdm;
+
+using std::function;
+using std::string;
+using std::vector;
+
+ClearKeySessionManager::ClearKeySessionManager(Host_10* aHost)
+ : mDecryptionManager(ClearKeyDecryptionManager::Get()) {
+ CK_LOGD("ClearKeySessionManager ctor %p", this);
+ AddRef();
+
+ mHost = aHost;
+ mPersistence = new ClearKeyPersistence(mHost);
+}
+
+ClearKeySessionManager::~ClearKeySessionManager() {
+ CK_LOGD("ClearKeySessionManager dtor %p", this);
+}
+
+void ClearKeySessionManager::Init(bool aDistinctiveIdentifierAllowed,
+ bool aPersistentStateAllowed) {
+ CK_LOGD("ClearKeySessionManager::Init");
+
+ RefPtr<ClearKeySessionManager> self(this);
+ function<void()> onPersistentStateLoaded = [self]() {
+ while (!self->mDeferredInitialize.empty()) {
+ function<void()> func = self->mDeferredInitialize.front();
+ self->mDeferredInitialize.pop();
+
+ func();
+ }
+ if (self->mHost) {
+ // The session manager should be the last thing the ClearKey CDM is
+ // waiting on to be initialized.
+ self->mHost->OnInitialized(true);
+ }
+ };
+
+ mPersistence->EnsureInitialized(aPersistentStateAllowed,
+ std::move(onPersistentStateLoaded));
+}
+
+void ClearKeySessionManager::CreateSession(uint32_t aPromiseId,
+ InitDataType aInitDataType,
+ const uint8_t* aInitData,
+ uint32_t aInitDataSize,
+ SessionType aSessionType) {
+ CK_LOGD("ClearKeySessionManager::CreateSession type:%u", aInitDataType);
+
+ // Copy the init data so it is correctly captured by the lambda
+ vector<uint8_t> initData(aInitData, aInitData + aInitDataSize);
+
+ RefPtr<ClearKeySessionManager> self(this);
+ function<void()> deferrer = [self, aPromiseId, aInitDataType, initData,
+ aSessionType]() {
+ self->CreateSession(aPromiseId, aInitDataType, initData.data(),
+ initData.size(), aSessionType);
+ };
+
+ // If we haven't loaded, don't do this yet
+ if (MaybeDeferTillInitialized(std::move(deferrer))) {
+ CK_LOGD("Deferring CreateSession");
+ return;
+ }
+
+ CK_LOGARRAY("ClearKeySessionManager::CreateSession initdata: ", aInitData,
+ aInitDataSize);
+
+ // If 'DecryptingComplete' has been called mHost will be null so we can't
+ // won't be able to resolve our promise
+ if (!mHost) {
+ CK_LOGD("ClearKeySessionManager::CreateSession: mHost is nullptr");
+ return;
+ }
+
+ // initDataType must be "cenc", "keyids", or "webm".
+ if (aInitDataType != InitDataType::kCenc &&
+ aInitDataType != InitDataType::kKeyIds &&
+ aInitDataType != InitDataType::kWebM) {
+ string message = "initDataType is not supported by ClearKey";
+ mHost->OnRejectPromise(aPromiseId, Exception::kExceptionNotSupportedError,
+ 0, message.c_str(), message.size());
+
+ return;
+ }
+
+ string sessionId = mPersistence->GetNewSessionId(aSessionType);
+ assert(mSessions.find(sessionId) == mSessions.end());
+
+ ClearKeySession* session = new ClearKeySession(sessionId, aSessionType);
+
+ if (!session->Init(aInitDataType, aInitData, aInitDataSize)) {
+ CK_LOGD("Failed to initialize session: %s", sessionId.c_str());
+
+ const static char* message = "Failed to initialize session";
+ mHost->OnRejectPromise(aPromiseId, Exception::kExceptionInvalidStateError,
+ 0, message, strlen(message));
+ delete session;
+
+ return;
+ }
+
+ mSessions[sessionId] = session;
+ mLastSessionId = sessionId;
+
+ 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);
+
+ // Resolve the promise with the new session information.
+ mHost->OnResolveNewSessionPromise(aPromiseId, sessionId.c_str(),
+ sessionId.size());
+
+ mHost->OnSessionMessage(sessionId.c_str(), sessionId.size(),
+ MessageType::kLicenseRequest, request.c_str(),
+ request.size());
+}
+
+void ClearKeySessionManager::LoadSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength) {
+ CK_LOGD("ClearKeySessionManager::LoadSession");
+
+ // Copy the sessionId into a string so the lambda captures it properly.
+ string sessionId(aSessionId, aSessionId + aSessionIdLength);
+
+ // Hold a reference to the SessionManager so that it isn't released before
+ // we try to use it.
+ RefPtr<ClearKeySessionManager> self(this);
+ function<void()> deferrer = [self, aPromiseId, sessionId]() {
+ self->LoadSession(aPromiseId, sessionId.data(), sessionId.size());
+ };
+
+ if (MaybeDeferTillInitialized(std::move(deferrer))) {
+ CK_LOGD("Deferring LoadSession");
+ return;
+ }
+
+ // If the SessionManager has been shutdown mHost will be null and we won't
+ // be able to resolve the promise.
+ if (!mHost) {
+ return;
+ }
+
+ if (!ClearKeyUtils::IsValidSessionId(aSessionId, aSessionIdLength)) {
+ mHost->OnResolveNewSessionPromise(aPromiseId, nullptr, 0);
+ return;
+ }
+
+ if (!mPersistence->IsPersistentSessionId(sessionId)) {
+ mHost->OnResolveNewSessionPromise(aPromiseId, nullptr, 0);
+ return;
+ }
+
+ function<void(const uint8_t*, uint32_t)> success =
+ [self, sessionId, aPromiseId](const uint8_t* data, uint32_t size) {
+ self->PersistentSessionDataLoaded(aPromiseId, sessionId, data, size);
+ };
+
+ function<void()> failure = [self, aPromiseId] {
+ if (!self->mHost) {
+ return;
+ }
+ // As per the API described in ContentDecryptionModule_8
+ self->mHost->OnResolveNewSessionPromise(aPromiseId, nullptr, 0);
+ };
+
+ ReadData(mHost, sessionId, std::move(success), std::move(failure));
+}
+
+void ClearKeySessionManager::PersistentSessionDataLoaded(
+ uint32_t aPromiseId, const string& aSessionId, const uint8_t* aKeyData,
+ uint32_t aKeyDataSize) {
+ CK_LOGD("ClearKeySessionManager::PersistentSessionDataLoaded");
+
+ // Check that the SessionManager has not been shut down before we try and
+ // resolve any promises.
+ if (!mHost) {
+ return;
+ }
+
+ if (Contains(mSessions, aSessionId) ||
+ (aKeyDataSize % (2 * CENC_KEY_LEN)) != 0) {
+ // As per the instructions in ContentDecryptionModule_8
+ mHost->OnResolveNewSessionPromise(aPromiseId, nullptr, 0);
+ return;
+ }
+
+ ClearKeySession* session =
+ new ClearKeySession(aSessionId, SessionType::kPersistentLicense);
+
+ mSessions[aSessionId] = session;
+ mLastSessionId = aSessionId;
+
+ uint32_t numKeys = aKeyDataSize / (2 * CENC_KEY_LEN);
+
+ vector<KeyInformation> keyInfos;
+ 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);
+
+ KeyInformation keyInfo = {};
+ keyInfo.key_id = &keyPairs.back().mKeyId[0];
+ keyInfo.key_id_size = keyPair.mKeyId.size();
+ keyInfo.status = KeyStatus::kUsable;
+
+ keyInfos.push_back(keyInfo);
+ }
+
+ mHost->OnSessionKeysChange(&aSessionId[0], aSessionId.size(), true,
+ keyInfos.data(), keyInfos.size());
+
+ mHost->OnResolveNewSessionPromise(aPromiseId, aSessionId.c_str(),
+ aSessionId.size());
+}
+
+void ClearKeySessionManager::UpdateSession(uint32_t aPromiseId,
+ const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const uint8_t* aResponse,
+ uint32_t aResponseSize) {
+ CK_LOGD("ClearKeySessionManager::UpdateSession");
+
+ // Copy the method arguments so we can capture them in the lambda
+ string sessionId(aSessionId, aSessionId + aSessionIdLength);
+ vector<uint8_t> response(aResponse, aResponse + aResponseSize);
+
+ // Hold a reference to the SessionManager so it isn't released before we
+ // callback.
+ RefPtr<ClearKeySessionManager> self(this);
+ function<void()> deferrer = [self, aPromiseId, sessionId, response]() {
+ self->UpdateSession(aPromiseId, sessionId.data(), sessionId.size(),
+ response.data(), response.size());
+ };
+
+ // If we haven't fully loaded, defer calling this method
+ if (MaybeDeferTillInitialized(std::move(deferrer))) {
+ CK_LOGD("Deferring LoadSession");
+ return;
+ }
+
+ // Make sure the SessionManager has not been shutdown before we try and
+ // resolve any promises.
+ if (!mHost) {
+ return;
+ }
+
+ CK_LOGD("Updating session: %s", sessionId.c_str());
+
+ auto itr = mSessions.find(sessionId);
+ if (itr == mSessions.end() || !(itr->second)) {
+ CK_LOGW("ClearKey CDM couldn't resolve session ID in UpdateSession.");
+ CK_LOGD("Unable to find session: %s", sessionId.c_str());
+ mHost->OnRejectPromise(aPromiseId, Exception::kExceptionTypeError, 0,
+ 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.");
+ CK_LOGD("Failed to parse response for session %s", sessionId.c_str());
+
+ mHost->OnRejectPromise(aPromiseId, Exception::kExceptionTypeError, 0,
+ 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.");
+
+ mHost->OnRejectPromise(aPromiseId, Exception::kExceptionTypeError, 0,
+ nullptr, 0);
+
+ return;
+ }
+
+ vector<KeyInformation> keyInfos;
+ for (size_t i = 0; i < keyPairs.size(); i++) {
+ KeyIdPair& keyPair = keyPairs[i];
+ mDecryptionManager->InitKey(keyPair.mKeyId, keyPair.mKey);
+ mKeyIds.insert(keyPair.mKeyId);
+
+ KeyInformation keyInfo = {};
+ keyInfo.key_id = &keyPair.mKeyId[0];
+ keyInfo.key_id_size = keyPair.mKeyId.size();
+ keyInfo.status = KeyStatus::kUsable;
+
+ keyInfos.push_back(keyInfo);
+ }
+
+ mHost->OnSessionKeysChange(aSessionId, aSessionIdLength, true,
+ keyInfos.data(), keyInfos.size());
+
+ if (session->Type() != SessionType::kPersistentLicense) {
+ mHost->OnResolvePromise(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);
+
+ function<void()> resolve = [self, aPromiseId]() {
+ if (!self->mHost) {
+ return;
+ }
+ self->mHost->OnResolvePromise(aPromiseId);
+ };
+
+ function<void()> reject = [self, aPromiseId]() {
+ if (!self->mHost) {
+ return;
+ }
+
+ static const char* message = "Couldn't store cenc key init data";
+ self->mHost->OnRejectPromise(aPromiseId,
+ Exception::kExceptionInvalidStateError, 0,
+ message, strlen(message));
+ };
+
+ WriteData(mHost, sessionId, keydata, std::move(resolve), std::move(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");
+
+ // Copy the sessionId into a string so we capture it properly.
+ string sessionId(aSessionId, aSessionId + aSessionIdLength);
+ // Hold a reference to the session manager, so it doesn't get deleted
+ // before we need to use it.
+ RefPtr<ClearKeySessionManager> self(this);
+ function<void()> deferrer = [self, aPromiseId, sessionId]() {
+ self->CloseSession(aPromiseId, sessionId.data(), sessionId.size());
+ };
+
+ // If we haven't loaded, call this method later.
+ if (MaybeDeferTillInitialized(std::move(deferrer))) {
+ CK_LOGD("Deferring CloseSession");
+ return;
+ }
+
+ // If DecryptingComplete has been called mHost will be null and we won't
+ // be able to resolve our promise.
+ if (!mHost) {
+ return;
+ }
+
+ auto itr = mSessions.find(sessionId);
+ if (itr == mSessions.end()) {
+ CK_LOGW("ClearKey CDM couldn't close non-existent session.");
+ mHost->OnRejectPromise(aPromiseId, Exception::kExceptionTypeError, 0,
+ nullptr, 0);
+
+ return;
+ }
+
+ ClearKeySession* session = itr->second;
+ assert(session);
+
+ ClearInMemorySessionData(session);
+
+ mHost->OnSessionClosed(aSessionId, aSessionIdLength);
+ mHost->OnResolvePromise(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");
+
+ // Copy the sessionId into a string so it can be captured for the lambda.
+ string sessionId(aSessionId, aSessionId + aSessionIdLength);
+
+ // Hold a reference to the SessionManager, so it isn't released before we
+ // try and use it.
+ RefPtr<ClearKeySessionManager> self(this);
+ function<void()> deferrer = [self, aPromiseId, sessionId]() {
+ self->RemoveSession(aPromiseId, sessionId.data(), sessionId.size());
+ };
+
+ // If we haven't fully loaded, defer calling this method.
+ if (MaybeDeferTillInitialized(std::move(deferrer))) {
+ CK_LOGD("Deferring RemoveSession");
+ return;
+ }
+
+ // Check that the SessionManager has not been shutdown before we try and
+ // resolve any promises.
+ if (!mHost) {
+ return;
+ }
+
+ auto itr = mSessions.find(sessionId);
+ if (itr == mSessions.end()) {
+ CK_LOGW("ClearKey CDM couldn't remove non-existent session.");
+
+ mHost->OnRejectPromise(aPromiseId, Exception::kExceptionTypeError, 0,
+ nullptr, 0);
+
+ return;
+ }
+
+ ClearKeySession* session = itr->second;
+ assert(session);
+ string sid = session->Id();
+ bool isPersistent = session->Type() == SessionType::kPersistentLicense;
+ ClearInMemorySessionData(session);
+
+ if (!isPersistent) {
+ mHost->OnResolvePromise(aPromiseId);
+ return;
+ }
+
+ mPersistence->PersistentSessionRemoved(sid);
+
+ vector<uint8_t> emptyKeydata;
+
+ function<void()> resolve = [self, aPromiseId]() {
+ if (!self->mHost) {
+ return;
+ }
+ self->mHost->OnResolvePromise(aPromiseId);
+ };
+
+ function<void()> reject = [self, aPromiseId]() {
+ if (!self->mHost) {
+ return;
+ }
+ static const char* message = "Could not remove session";
+ self->mHost->OnRejectPromise(aPromiseId, Exception::kExceptionTypeError, 0,
+ message, strlen(message));
+ };
+
+ WriteData(mHost, sessionId, emptyKeydata, std::move(resolve),
+ std::move(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");
+ mHost->OnRejectPromise(aPromiseId, Exception::kExceptionNotSupportedError, 0,
+ nullptr /* message */, 0 /* messageLen */);
+}
+
+Status ClearKeySessionManager::Decrypt(const InputBuffer_2& aBuffer,
+ DecryptedBlock* aDecryptedBlock) {
+ CK_LOGD("ClearKeySessionManager::Decrypt");
+
+ CK_LOGARRAY("Key: ", aBuffer.key_id, aBuffer.key_id_size);
+
+ Buffer* buffer = mHost->Allocate(aBuffer.data_size);
+ assert(buffer != nullptr);
+ assert(buffer->Data() != nullptr);
+ assert(buffer->Capacity() >= aBuffer.data_size);
+
+ memcpy(buffer->Data(), aBuffer.data, aBuffer.data_size);
+
+ Status status = Status::kSuccess;
+ // According to the comment `If |iv_size| = 0, the data is unencrypted.`
+ // Use iv_size to determine if the sample is encrypted.
+ if (aBuffer.iv_size != 0) {
+ status = mDecryptionManager->Decrypt(buffer->Data(), buffer->Size(),
+ CryptoMetaData(&aBuffer));
+ }
+
+ aDecryptedBlock->SetDecryptedBuffer(buffer);
+ aDecryptedBlock->SetTimestamp(aBuffer.timestamp);
+
+ return status;
+}
+
+void ClearKeySessionManager::DecryptingComplete() {
+ CK_LOGD("ClearKeySessionManager::DecryptingComplete %p", this);
+
+ for (auto it = mSessions.begin(); it != mSessions.end(); it++) {
+ delete it->second;
+ }
+ mSessions.clear();
+ mLastSessionId = std::nullopt;
+
+ mDecryptionManager = nullptr;
+ mHost = nullptr;
+
+ Release();
+}
+
+bool ClearKeySessionManager::MaybeDeferTillInitialized(
+ function<void()>&& aMaybeDefer) {
+ if (mPersistence->IsLoaded()) {
+ return false;
+ }
+
+ mDeferredInitialize.emplace(std::move(aMaybeDefer));
+ return true;
+}
+
+void ClearKeySessionManager::OnQueryOutputProtectionStatus(
+ QueryResult aResult, uint32_t aLinkMask, uint32_t aOutputProtectionMask) {
+ MOZ_ASSERT(mHasOutstandingOutputProtectionQuery,
+ "Should only be called if a query is outstanding");
+ CK_LOGD("ClearKeySessionManager::OnQueryOutputProtectionStatus");
+ mHasOutstandingOutputProtectionQuery = false;
+
+ if (aResult == QueryResult::kQueryFailed) {
+ // Indicate the query failed. This can happen if we're in shutdown.
+ NotifyOutputProtectionStatus(KeyStatus::kInternalError);
+ return;
+ }
+
+ if (aLinkMask & OutputLinkTypes::kLinkTypeNetwork) {
+ NotifyOutputProtectionStatus(KeyStatus::kOutputRestricted);
+ return;
+ }
+
+ NotifyOutputProtectionStatus(KeyStatus::kUsable);
+}
+
+void ClearKeySessionManager::QueryOutputProtectionStatusIfNeeded() {
+ MOZ_ASSERT(
+ mHost,
+ "Should not query protection status if we're shutdown (mHost == null)!");
+ CK_LOGD(
+ "ClearKeySessionManager::UpdateOutputProtectionStatusAndQueryIfNeeded");
+ if (mLastOutputProtectionQueryTime.IsNull()) {
+ // We haven't perfomed a check yet, get a query going.
+ MOZ_ASSERT(
+ !mHasOutstandingOutputProtectionQuery,
+ "Shouldn't have an outstanding query if we haven't recorded a time");
+ QueryOutputProtectionStatusFromHost();
+ return;
+ }
+
+ MOZ_ASSERT(!mLastOutputProtectionQueryTime.IsNull(),
+ "Should have already handled the case where we don't yet have a "
+ "previous check time");
+ const mozilla::TimeStamp now = mozilla::TimeStamp::NowLoRes();
+ const mozilla::TimeDuration timeSinceQuery =
+ now - mLastOutputProtectionQueryTime;
+
+ // The time between output protection checks to the host. I.e. if this amount
+ // of time has passed since the last check with the host, another should be
+ // performed (provided the first check has been handled).
+ static const mozilla::TimeDuration kOutputProtectionQueryInterval =
+ mozilla::TimeDuration::FromSeconds(0.2);
+ // The number of kOutputProtectionQueryInterval intervals we can miss before
+ // we decide a check has failed. I.e. if this value is 2, if we have not
+ // received a reply to a check after kOutputProtectionQueryInterval * 2
+ // time, we consider the check failed.
+ constexpr uint32_t kMissedIntervalsBeforeFailure = 2;
+ // The length of time after which we will restrict output until we get a
+ // query response.
+ static const mozilla::TimeDuration kTimeToWaitBeforeFailure =
+ kOutputProtectionQueryInterval * kMissedIntervalsBeforeFailure;
+
+ if ((timeSinceQuery > kOutputProtectionQueryInterval) &&
+ !mHasOutstandingOutputProtectionQuery) {
+ // We don't have an outstanding query and enough time has passed we should
+ // query again.
+ QueryOutputProtectionStatusFromHost();
+ return;
+ }
+
+ if ((timeSinceQuery > kTimeToWaitBeforeFailure) &&
+ mHasOutstandingOutputProtectionQuery) {
+ // A reponse was not received fast enough, notify.
+ NotifyOutputProtectionStatus(KeyStatus::kInternalError);
+ }
+}
+
+void ClearKeySessionManager::QueryOutputProtectionStatusFromHost() {
+ MOZ_ASSERT(
+ mHost,
+ "Should not query protection status if we're shutdown (mHost == null)!");
+ CK_LOGD("ClearKeySessionManager::QueryOutputProtectionStatusFromHost");
+ if (mHost) {
+ mLastOutputProtectionQueryTime = mozilla::TimeStamp::NowLoRes();
+ mHost->QueryOutputProtectionStatus();
+ mHasOutstandingOutputProtectionQuery = true;
+ }
+}
+
+void ClearKeySessionManager::NotifyOutputProtectionStatus(KeyStatus aStatus) {
+ MOZ_ASSERT(aStatus == KeyStatus::kUsable ||
+ aStatus == KeyStatus::kOutputRestricted ||
+ aStatus == KeyStatus::kInternalError,
+ "aStatus should have an expected value");
+ CK_LOGD("ClearKeySessionManager::NotifyOutputProtectionStatus");
+ if (!mLastSessionId.has_value()) {
+ // If we don't have a session id, either because we're too early, or are
+ // shutting down, don't notify.
+ return;
+ }
+
+ string& lastSessionId = mLastSessionId.value();
+
+ // Use 'output-protection' as the key ID. This helps tests disambiguate key
+ // status updates related to this.
+ const uint8_t kKeyId[] = {'o', 'u', 't', 'p', 'u', 't', '-', 'p', 'r',
+ 'o', 't', 'e', 'c', 't', 'i', 'o', 'n'};
+ KeyInformation keyInfo = {};
+ keyInfo.key_id = kKeyId;
+ keyInfo.key_id_size = std::size(kKeyId);
+ keyInfo.status = aStatus;
+
+ vector<KeyInformation> keyInfos;
+ keyInfos.push_back(keyInfo);
+
+ // At time of writing, Gecko's higher level handling doesn't use this arg.
+ // However, we set it to false to mimic Chromium's similar case. Since
+ // Clearkey is used to test the Chromium CDM path, it doesn't hurt to try
+ // and mimic their behaviour.
+ bool hasAdditionalUseableKey = false;
+ mHost->OnSessionKeysChange(lastSessionId.c_str(), lastSessionId.size(),
+ hasAdditionalUseableKey, keyInfos.data(),
+ keyInfos.size());
+}