blob: 0ddccfa4bd0b36a1e3c25234c673c67232c5b92a [file] [log] [blame]
/*
* Copyright (C) 2020 Metrological Group B.V.
* Copyright (C) 2020 Igalia S.L.
* Copyright (C) 2022 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "CDMProxy.h"
#if ENABLE(ENCRYPTED_MEDIA)
#include "Logging.h"
#include "MediaPlayer.h"
#include <wtf/HexNumber.h>
#include <wtf/Scope.h>
#include <wtf/StringPrintStream.h>
#include <wtf/text/StringBuilder.h>
namespace WebCore {
Vector<CDMProxyFactory*>& CDMProxyFactory::registeredFactories()
{
static NeverDestroyed factories = platformRegisterFactories();
return factories;
}
void CDMProxyFactory::registerFactory(CDMProxyFactory& factory)
{
ASSERT(!registeredFactories().contains(&factory));
registeredFactories().append(&factory);
}
void CDMProxyFactory::unregisterFactory(CDMProxyFactory& factory)
{
ASSERT(registeredFactories().contains(&factory));
registeredFactories().removeAll(&factory);
}
RefPtr<CDMProxy> CDMProxyFactory::createCDMProxyForKeySystem(const String& keySystem)
{
RefPtr<CDMProxy> cdmProxy;
for (auto* factory : CDMProxyFactory::registeredFactories()) {
if (factory->supportsKeySystem(keySystem)) {
cdmProxy = factory->createCDMProxy(keySystem);
break;
}
}
#if USE(GSTREAMER)
ASSERT(cdmProxy);
#endif
return cdmProxy;
}
#if !USE(GSTREAMER)
Vector<CDMProxyFactory*> CDMProxyFactory::platformRegisterFactories()
{
Vector<CDMProxyFactory*> factories;
return factories;
}
#endif
namespace {
static String vectorToHexString(const Vector<uint8_t>& vec)
{
StringBuilder stringBuilder;
for (auto byte : vec)
stringBuilder.append(pad('0', 2, hex(byte)));
return stringBuilder.toString();
}
} // namespace {}
String KeyHandle::idAsString() const
{
return makeString("[", vectorToHexString(m_id), "]");
}
bool KeyHandle::takeValueIfDifferent(KeyHandleValueVariant&& value)
{
if (m_value != value) {
m_value = WTFMove(value);
return true;
}
return false;
}
bool KeyStore::containsKeyID(const KeyIDType& keyID) const
{
return m_keys.findIf([&](const RefPtr<KeyHandle>& storedKey) {
return *storedKey == keyID;
}) != notFound;
}
void KeyStore::merge(const KeyStore& other)
{
ASSERT(isMainThread());
LOG(EME, "EME - CDMProxy - merging %u new keys into a key store of %u keys", other.numKeys(), numKeys());
for (const auto& key : other)
add(key.copyRef());
#if !LOG_DISABLED
LOG(EME, "EME - CDMProxy - key store now has %u keys", numKeys());
for (const auto& key : m_keys)
LOG(EME, "\tEME - CDMProxy - Key ID: %s", key->idAsString().ascii().data());
#endif // !LOG_DISABLED
}
CDMInstanceSession::KeyStatusVector KeyStore::allKeysAs(CDMInstanceSession::KeyStatus status) const
{
CDMInstanceSession::KeyStatusVector keyStatusVector = convertToJSKeyStatusVector();
for (auto& keyStatus : keyStatusVector)
keyStatus.second = status;
return keyStatusVector;
}
bool KeyStore::addKeys(Vector<RefPtr<KeyHandle>>&& newKeys)
{
bool didKeyStoreChange = false;
for (auto& key : newKeys) {
if (add(WTFMove(key)))
didKeyStoreChange = true;
}
return didKeyStoreChange;
}
bool KeyStore::add(RefPtr<KeyHandle>&& key)
{
bool didStoreChange = false;
size_t keyWithMatchingKeyIDIndex = m_keys.findIf([&] (const RefPtr<KeyHandle>& storedKey) {
return *key == *storedKey;
});
addSessionReferenceTo(key);
if (keyWithMatchingKeyIDIndex != notFound) {
auto& keyWithMatchingKeyID = m_keys[keyWithMatchingKeyIDIndex];
didStoreChange = keyWithMatchingKeyID != key;
if (didStoreChange)
keyWithMatchingKeyID->mergeKeyInto(WTFMove(key));
} else {
LOG(EME, "EME - ClearKey - New key with ID %s getting added to key store", key->idAsString().ascii().data());
m_keys.append(WTFMove(key));
didStoreChange = true;
}
if (didStoreChange) {
// Sort the keys lexicographically.
// NOTE: This is not as pathological as it may seem, for all
// practical purposes the store has a maximum of 2 keys.
std::sort(m_keys.begin(), m_keys.end(),
[](const RefPtr<KeyHandle>& a, const RefPtr<KeyHandle>& b) {
return *a < *b;
});
}
return didStoreChange;
}
void KeyStore::unrefAllKeysFrom(const KeyStore& other)
{
for (const auto& key : other)
unref(key);
}
void KeyStore::unrefAllKeys()
{
KeyStore store(*this);
unrefAllKeysFrom(store);
}
bool KeyStore::unref(const RefPtr<KeyHandle>& key)
{
bool storeChanged = false;
size_t keyWithMatchingKeyIDIndex = m_keys.find(key);
LOG(EME, "EME - ClearKey - requested to unref key with ID %s and %d session references", key->idAsString().ascii().data(), key->numSessionReferences());
if (keyWithMatchingKeyIDIndex != notFound) {
auto& keyWithMatchingKeyID = m_keys[keyWithMatchingKeyIDIndex];
removeSessionReferenceFrom(keyWithMatchingKeyID);
if (!keyWithMatchingKeyID->hasReferences()) {
LOG(EME, "EME - ClearKey - unref key with ID %s", keyWithMatchingKeyID->idAsString().ascii().data());
m_keys.remove(keyWithMatchingKeyIDIndex);
storeChanged = true;
}
} else
LOG(EME, "EME - ClearKey - attempt to unref key with ID %s ignored, does not exist", key->idAsString().ascii().data());
return storeChanged;
}
const RefPtr<KeyHandle>& KeyStore::keyHandle(const KeyIDType& keyID) const
{
for (const auto& key : m_keys) {
if (*key == keyID)
return key;
}
RELEASE_ASSERT(false && "key must exist to call this method");
UNREACHABLE();
}
CDMInstanceSession::KeyStatusVector KeyStore::convertToJSKeyStatusVector() const
{
CDMInstanceSession::KeyStatusVector keyStatusVector;
keyStatusVector.reserveInitialCapacity(numKeys());
for (const auto& key : m_keys)
keyStatusVector.uncheckedAppend(std::pair<Ref<FragmentedSharedBuffer>, CDMInstanceSession::KeyStatus> { key->idAsSharedBuffer(), key->status() });
return keyStatusVector;
}
void CDMProxy::updateKeyStore(const KeyStore& newKeyStore)
{
Locker locker { m_keysLock };
m_keyStore.merge(newKeyStore);
LOG(EME, "EME - CDMProxy - updating key store from a session update");
m_keysCondition.notifyAll();
}
const CDMInstanceProxy* CDMProxy::instance() const
{
Locker locker { m_instanceLock };
return m_instance;
}
void CDMProxy::unrefAllKeysFrom(const KeyStore& keyStore)
{
Locker locker { m_keysLock };
m_keyStore.unrefAllKeysFrom(keyStore);
LOG(EME, "EME - CDMProxy - removing from key store from a session closure");
m_keysCondition.notifyAll();
}
void CDMProxy::setInstance(CDMInstanceProxy* instance)
{
Locker locker { m_instanceLock };
m_instance = instance;
}
RefPtr<KeyHandle> CDMProxy::keyHandle(const KeyIDType& keyID) const
{
Locker locker { m_keysLock };
ASSERT(m_keyStore.containsKeyID(keyID));
return m_keyStore.keyHandle(keyID);
}
void CDMProxy::startedWaitingForKey() const
{
Locker locker { m_instanceLock };
LOG(EME, "EME - CDMProxy - started waiting for a key");
ASSERT(m_instance);
m_instance->startedWaitingForKey();
}
void CDMProxy::stoppedWaitingForKey() const
{
Locker locker { m_instanceLock };
LOG(EME, "EME - CDMProxy - stopped waiting for a key");
ASSERT(m_instance);
m_instance->stoppedWaitingForKey();
}
void CDMProxy::abortWaitingForKey() const
{
LOG(EME, "EME - CDMProxy - abort waiting for key ID");
m_keysCondition.notifyAll();
}
std::optional<Ref<KeyHandle>> CDMProxy::tryWaitForKeyHandle(const KeyIDType& keyID, WeakPtr<CDMProxyDecryptionClient>&& client) const
{
startedWaitingForKey();
// Unconditionally saying we have stopped waiting for a key means that decryptors only get
// one shot at fetching a key. If MaxKeyWaitTimeSeconds expires, that's it, no more clear bytes
// for you.
auto stopWaitingForKeyOnReturn = makeScopeExit([this] {
stoppedWaitingForKey();
});
LOG(EME, "EME - CDMProxy - trying to wait for key ID %s", vectorToHexString(keyID).ascii().data());
bool wasKeyAvailable = false;
{
Locker locker { m_keysLock };
m_keysCondition.waitFor(m_keysLock, CDMProxy::MaxKeyWaitTimeSeconds, [this, keyID, client = WTFMove(client), &wasKeyAvailable]() {
assertIsHeld(m_keysLock);
if (!client || client->isAborting())
return true;
wasKeyAvailable = keyAvailableUnlocked(keyID);
return wasKeyAvailable;
});
}
if (wasKeyAvailable) {
RefPtr<KeyHandle> handle = keyHandle(keyID);
LOG(EME, "EME - CDMProxy - successfully waited for key ID %s", vectorToHexString(keyID).ascii().data());
return std::make_optional(handle.releaseNonNull());
}
LOG(EME, "EME - CDMProxy - key ID %s not available or operation aborted", vectorToHexString(keyID).ascii().data());
return std::nullopt;
}
bool CDMProxy::keyAvailableUnlocked(const KeyIDType& keyID) const
{
return m_keyStore.containsKeyID(keyID);
}
bool CDMProxy::keyAvailable(const KeyIDType& keyID) const
{
Locker locker { m_keysLock };
return keyAvailableUnlocked(keyID);
}
std::optional<Ref<KeyHandle>> CDMProxy::getOrWaitForKeyHandle(const KeyIDType& keyID, WeakPtr<CDMProxyDecryptionClient>&& client) const
{
if (!keyAvailable(keyID)) {
LOG(EME, "EME - CDMProxy key cache does not contain key ID %s", vectorToHexString(keyID).ascii().data());
return tryWaitForKeyHandle(keyID, WTFMove(client));
}
RefPtr<KeyHandle> handle = keyHandle(keyID);
return std::make_optional(handle.releaseNonNull());
}
std::optional<KeyHandleValueVariant> CDMProxy::getOrWaitForKeyValue(const KeyIDType& keyID, WeakPtr<CDMProxyDecryptionClient>&& client) const
{
if (auto keyHandle = getOrWaitForKeyHandle(keyID, WTFMove(client)))
return std::make_optional((*keyHandle)->value());
return std::nullopt;
}
void CDMInstanceProxy::startedWaitingForKey()
{
ASSERT(!isMainThread());
ASSERT(m_player);
bool wasWaitingForKey = m_numDecryptorsWaitingForKey > 0;
m_numDecryptorsWaitingForKey++;
callOnMainThread([player = m_player, wasWaitingForKey] {
if (player && !wasWaitingForKey)
player->waitingForKeyChanged();
});
}
void CDMInstanceProxy::stoppedWaitingForKey()
{
ASSERT(!isMainThread());
ASSERT(m_player);
ASSERT(m_numDecryptorsWaitingForKey > 0);
m_numDecryptorsWaitingForKey--;
bool isNobodyWaitingForKey = !m_numDecryptorsWaitingForKey;
callOnMainThread([player = m_player, isNobodyWaitingForKey] {
if (player && isNobodyWaitingForKey)
player->waitingForKeyChanged();
});
}
void CDMInstanceProxy::mergeKeysFrom(const KeyStore& keyStore)
{
// FIXME: Notify JS when appropriate.
ASSERT(isMainThread());
if (m_cdmProxy) {
LOG(EME, "EME - CDMInstanceProxy - merging keys into proxy instance and notifying CDMProxy of changes");
m_cdmProxy->updateKeyStore(keyStore);
}
}
void CDMInstanceProxy::unrefAllKeysFrom(const KeyStore& keyStore)
{
ASSERT(isMainThread());
if (m_cdmProxy) {
LOG(EME, "EME - CDMInstanceProxy - removing keys from proxy instance and notifying CDMProxy of changes");
m_cdmProxy->unrefAllKeysFrom(keyStore);
}
}
CDMInstanceSessionProxy::CDMInstanceSessionProxy(CDMInstanceProxy& instance)
: m_instance(instance)
{
}
} // namespace WebCore
#endif // ENABLE(ENCRYPTED_MEDIA)