mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-25 20:01:50 +00:00
Bug 1711912 - Implement output protection query in Clearkey CDM. r=alwu
This implements logic in the clear key CDM to emulate protection queries. The CDM will only do so if configured using the new test key system added prior to this patch. Differential Revision: https://phabricator.services.mozilla.com/D122633
This commit is contained in:
parent
b7fc1c78ef
commit
4416660111
@ -77,6 +77,12 @@ void ClearKeyCDM::TimerExpired(void* aContext) {
|
||||
|
||||
Status ClearKeyCDM::Decrypt(const InputBuffer_2& aEncryptedBuffer,
|
||||
DecryptedBlock* aDecryptedBuffer) {
|
||||
if (mIsProtectionQueryEnabled) {
|
||||
// Piggyback this check onto Decrypt calls. If Clearkey implements a timer
|
||||
// based approach for firing events, we could instead trigger the check
|
||||
// using that mechanism.
|
||||
mSessionManager->QueryOutputProtectionStatusIfNeeded();
|
||||
}
|
||||
return mSessionManager->Decrypt(aEncryptedBuffer, aDecryptedBuffer);
|
||||
}
|
||||
|
||||
@ -118,6 +124,12 @@ void ClearKeyCDM::ResetDecoder(StreamType aDecoderType) {
|
||||
Status ClearKeyCDM::DecryptAndDecodeFrame(const InputBuffer_2& aEncryptedBuffer,
|
||||
VideoFrame* aVideoFrame) {
|
||||
#ifdef ENABLE_WMF
|
||||
if (mIsProtectionQueryEnabled) {
|
||||
// Piggyback this check onto Decrypt + Decode. If Clearkey implements a
|
||||
// timer based approach for firing events, we could instead trigger the
|
||||
// check using that mechanism.
|
||||
mSessionManager->QueryOutputProtectionStatusIfNeeded();
|
||||
}
|
||||
return mVideoDecoder->Decode(aEncryptedBuffer, aVideoFrame);
|
||||
#else
|
||||
return Status::kDecodeError;
|
||||
@ -140,8 +152,16 @@ void ClearKeyCDM::OnPlatformChallengeResponse(
|
||||
|
||||
void ClearKeyCDM::OnQueryOutputProtectionStatus(
|
||||
QueryResult aResult, uint32_t aLinkMask, uint32_t aOutputProtectionMask) {
|
||||
// This function should never be called and is not supported.
|
||||
assert(false);
|
||||
// The higher level GMP machinery should not forward us query information
|
||||
// unless we've requested it (even if mutiple CDMs exist at once and some
|
||||
// others are reqeusting this info). If this assert fires we're violating
|
||||
// that.
|
||||
MOZ_ASSERT(mIsProtectionQueryEnabled,
|
||||
"Should only receive a protection status "
|
||||
"mIsProtectionQueryEnabled is true");
|
||||
// The session manager handles the guts of this for ClearKey.
|
||||
mSessionManager->OnQueryOutputProtectionStatus(aResult, aLinkMask,
|
||||
aOutputProtectionMask);
|
||||
}
|
||||
|
||||
void ClearKeyCDM::OnStorageId(uint32_t aVersion, const uint8_t* aStorageId,
|
||||
|
@ -133,6 +133,7 @@ void ClearKeySessionManager::CreateSession(uint32_t aPromiseId,
|
||||
}
|
||||
|
||||
mSessions[sessionId] = session;
|
||||
mLastSessionId = sessionId;
|
||||
|
||||
const vector<KeyId>& sessionKeys = session->GetKeyIds();
|
||||
vector<KeyId> neededKeys;
|
||||
@ -238,6 +239,7 @@ void ClearKeySessionManager::PersistentSessionDataLoaded(
|
||||
new ClearKeySession(aSessionId, SessionType::kPersistentLicense);
|
||||
|
||||
mSessions[aSessionId] = session;
|
||||
mLastSessionId = aSessionId;
|
||||
|
||||
uint32_t numKeys = aKeyDataSize / (2 * CENC_KEY_LEN);
|
||||
|
||||
@ -571,6 +573,7 @@ void ClearKeySessionManager::DecryptingComplete() {
|
||||
delete it->second;
|
||||
}
|
||||
mSessions.clear();
|
||||
mLastSessionId = std::nullopt;
|
||||
|
||||
mDecryptionManager = nullptr;
|
||||
mHost = nullptr;
|
||||
@ -587,3 +590,124 @@ bool ClearKeySessionManager::MaybeDeferTillInitialized(
|
||||
mDeferredInitialize.emplace(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());
|
||||
}
|
||||
|
@ -23,6 +23,7 @@
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <string>
|
||||
@ -33,6 +34,7 @@
|
||||
#include "ClearKeyUtils.h"
|
||||
#include "RefCounted.h"
|
||||
#include "content_decryption_module.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
|
||||
class ClearKeySessionManager final : public RefCounted {
|
||||
public:
|
||||
@ -70,6 +72,23 @@ class ClearKeySessionManager final : public RefCounted {
|
||||
const uint8_t* aKeyData,
|
||||
uint32_t aKeyDataSize);
|
||||
|
||||
// Receives the result of an output protection query from the user agent.
|
||||
// This may trigger a key status change.
|
||||
// @param aResult indicates if the query succeeded or not. If a query did
|
||||
// not succeed then that other arguments are ignored.
|
||||
// @param aLinkMask is used to indicate if output could be captured by the
|
||||
// user agent. It should be set to `kLinkTypeNetwork` if capture is possible,
|
||||
// otherwise it should be zero.
|
||||
// @param aOutputProtectionMask this argument is unused.
|
||||
void OnQueryOutputProtectionStatus(cdm::QueryResult aResult,
|
||||
uint32_t aLinkMask,
|
||||
uint32_t aOutputProtectionMask);
|
||||
|
||||
// Prompts the session manager to query the output protection status if we
|
||||
// haven't yet, or if enough time has passed since the last check. Will also
|
||||
// notify if a check has not been responded to on time.
|
||||
void QueryOutputProtectionStatusIfNeeded();
|
||||
|
||||
private:
|
||||
~ClearKeySessionManager();
|
||||
|
||||
@ -78,6 +97,21 @@ class ClearKeySessionManager final : public RefCounted {
|
||||
void Serialize(const ClearKeySession* aSession,
|
||||
std::vector<uint8_t>& aOutKeyData);
|
||||
|
||||
// Signals the host to perform an output protection check.
|
||||
void QueryOutputProtectionStatusFromHost();
|
||||
|
||||
// Called to notify the result of an output protection status call. The
|
||||
// following arguments are expected, along with their intended use:
|
||||
// - KeyStatus::kUsable indicates that the query was responded to and the
|
||||
// response showed output is protected.
|
||||
// - KeyStatus::kOutputRestricted indicates that the query was responded to
|
||||
// and the response showed output is not protected.
|
||||
// - KeyStatus::kInternalError indicates a query was not repsonded to on
|
||||
// time, or that a query was responded to with a failed cdm::QueryResult.
|
||||
// The status passed to this function will be used to update the status of
|
||||
// the keyId "output-protection", which tests an observe.
|
||||
void NotifyOutputProtectionStatus(cdm::KeyStatus aStatus);
|
||||
|
||||
RefPtr<ClearKeyDecryptionManager> mDecryptionManager;
|
||||
RefPtr<ClearKeyPersistence> mPersistence;
|
||||
|
||||
@ -86,7 +120,18 @@ class ClearKeySessionManager final : public RefCounted {
|
||||
std::set<KeyId> mKeyIds;
|
||||
std::map<std::string, ClearKeySession*> mSessions;
|
||||
|
||||
// The session id of the last session created or loaded from persistent
|
||||
// storage. Used to fire test messages at that session.
|
||||
std::optional<std::string> mLastSessionId;
|
||||
|
||||
std::queue<std::function<void()>> mDeferredInitialize;
|
||||
|
||||
// If there is an inflight query to the host to check the output protection
|
||||
// status. Multiple in flight queries should not be allowed, avoid firing
|
||||
// more if this is true.
|
||||
bool mHasOutstandingOutputProtectionQuery = false;
|
||||
// The last time the manager called QueryOutputProtectionStatus on the host.
|
||||
mozilla::TimeStamp mLastOutputProtectionQueryTime;
|
||||
};
|
||||
|
||||
#endif // __ClearKeyDecryptor_h__
|
||||
|
Loading…
x
Reference in New Issue
Block a user