blob: 81b2521d03242cdb22a2c88163b929920100b3c8 [file] [log] [blame]
/*
* Copyright (C) 2020 Metrological Group B.V.
* Copyright (C) 2020 Igalia S.L.
*
* 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 <wtf/HexNumber.h>
#include <wtf/Scope.h>
#include <wtf/text/StringBuilder.h>
namespace WebCore {
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 Key::idAsString() const
{
return makeString("[", vectorToHexString(m_id), "]");
}
String Key::valueAsString() const
{
return makeString("[", vectorToHexString(m_value), "]");
}
bool KeyStore::containsKeyID(const Vector<uint8_t>& keyID) const
{
return m_keys.findMatching([&](const RefPtr<Key>& storedKey) {
return *storedKey == keyID;
}) != WTF::notFound;
}
void KeyStore::merge(const KeyStore& other)
{
LOG(EME, "EME - CDMProxy - merging %u new keys into a key store of %u keys", other.numKeys(), numKeys());
for (const auto& key : other) {
// NOTE: Do we care that we will not append a key if it matches a key ID
// in the keystore and has different data. Should we overwrite? Which is "newer"?
// Don't think we need this extra complexity.
if (m_keys.findMatching([&] (const RefPtr<Key>& storedKey) { return *key == *storedKey; }) == WTF::notFound)
m_keys.append(key);
else
// NOTE: This does happen with DASH players, it seems harmless to ignore it.
LOG(EME, "EME - CDMProxy - ignored a key with the same ID and different data");
}
#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::allKeysAsReleased() const
{
CDMInstanceSession::KeyStatusVector keyStatusVector = convertToJSKeyStatusVector();
for (auto& keyStatus : keyStatusVector)
keyStatus.second = CDMInstanceSession::KeyStatus::Released;
return keyStatusVector;
}
bool KeyStore::addKeys(Vector<RefPtr<Key>>&& newKeys)
{
bool didKeyStoreChange = false;
for (auto& key : newKeys) {
if (add(WTFMove(key)))
didKeyStoreChange = true;
}
return didKeyStoreChange;
}
bool KeyStore::add(RefPtr<Key>&& key)
{
bool didStoreChange = false;
size_t keyWithMatchingKeyIDIndex = m_keys.findMatching([&] (const RefPtr<Key>& storedKey) {
return *key == *storedKey;
});
if (keyWithMatchingKeyIDIndex != WTF::notFound) {
auto& keyWithMatchingKeyID = m_keys[keyWithMatchingKeyIDIndex];
RELEASE_ASSERT(keyWithMatchingKeyID->value() == key->value(), "Can this really happen?");
} else {
LOG(EME, "EME - ClearKey - New key with ID %s getting added to key store", key->idAsString().ascii().data());
m_keys.append(key);
didStoreChange = true;
}
key->addSessionReference();
return didStoreChange;
}
void KeyStore::removeAllKeysFrom(const KeyStore& other)
{
for (const auto& key : other)
remove(key);
}
bool KeyStore::remove(const RefPtr<Key>& key)
{
bool storeChanged = false;
size_t keyWithMatchingKeyIDIndex = m_keys.find(key);
LOG(EME, "EME - ClearKey - requested to remove key with ID %s and %u session references", key->idAsString().ascii().data(), key->numSessionReferences());
if (keyWithMatchingKeyIDIndex != WTF::notFound) {
auto& keyWithMatchingKeyID = m_keys[keyWithMatchingKeyIDIndex];
keyWithMatchingKeyID->removeSessionReference();
if (!keyWithMatchingKeyID->numSessionReferences()) {
LOG(EME, "EME - ClearKey - remove key with ID %s", keyWithMatchingKeyID->idAsString().ascii().data());
m_keys.remove(keyWithMatchingKeyIDIndex);
storeChanged = true;
}
} else
LOG(EME, "EME - ClearKey - attempt to remove key with ID %s ignored, does not exist", key->idAsString().ascii().data());
return storeChanged;
}
const Vector<uint8_t>& KeyStore::keyValue(const Vector<uint8_t>& keyID) const
{
for (const auto& key : m_keys) {
if (*key == keyID)
return key->value();
}
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<SharedBuffer>, CDMInstanceSession::KeyStatus> { key->idAsSharedBuffer(), key->status() });
return keyStatusVector;
}
void CDMProxy::updateKeyStore(const KeyStore& newKeyStore)
{
auto locker = holdLock(m_keysMutex);
m_keyStore.merge(newKeyStore);
LOG(EME, "EME - CDMProxy - updating key store from a session update");
m_keysCondition.notifyAll();
}
void CDMProxy::setInstance(CDMInstanceProxy* instance)
{
auto locker = holdLock(m_instanceMutex);
m_instance = instance;
}
Vector<uint8_t> CDMProxy::keyValue(const Vector<uint8_t>& keyID) const
{
auto locker = holdLock(m_keysMutex);
ASSERT(m_keyStore.containsKeyID(keyID));
return m_keyStore.keyValue(keyID);
}
void CDMProxy::startedWaitingForKey() const
{
auto locker = holdLock(m_instanceMutex);
LOG(EME, "EME - CDMProxy - started waiting for a key");
ASSERT(m_instance);
m_instance->startedWaitingForKey();
}
void CDMProxy::stoppedWaitingForKey() const
{
auto locker = holdLock(m_instanceMutex);
LOG(EME, "EME - CDMProxy - stopped waiting for a key");
ASSERT(m_instance);
m_instance->stoppedWaitingForKey();
}
Optional<Vector<uint8_t>> CDMProxy::tryWaitForKey(const Vector<uint8_t>& keyID) 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 wasKeyReceived = false;
{
auto locker = holdLock(m_keysMutex);
wasKeyReceived = m_keysCondition.waitFor(m_keysMutex, CDMProxy::MaxKeyWaitTimeSeconds, [&, this, keyID]() {
return keyAvailableUnlocked(keyID);
});
}
if (wasKeyReceived) {
LOG(EME, "EME - CDMProxy - successfully waited for key ID %s", vectorToHexString(keyID).ascii().data());
return keyValue(keyID);
}
LOG(EME, "EME - CDMProxy - key ID %s not available", vectorToHexString(keyID).ascii().data());
return WTF::nullopt;
}
bool CDMProxy::keyAvailableUnlocked(const Vector<uint8_t>& keyID) const
{
return m_keyStore.containsKeyID(keyID);
}
bool CDMProxy::keyAvailable(const Vector<uint8_t>& keyID) const
{
auto locker = holdLock(m_keysMutex);
return keyAvailableUnlocked(keyID);
}
Optional<Vector<uint8_t>> CDMProxy::getOrWaitForKey(const Vector<uint8_t>& keyID) const
{
if (!keyAvailable(keyID)) {
LOG(EME, "EME - CDMProxy key cache does not contain key ID %s", vectorToHexString(keyID).ascii().data());
return tryWaitForKey(keyID);
}
return keyValue(keyID);
}
void CDMInstanceProxy::startedWaitingForKey()
{
ASSERT(!isMainThread() && m_player);
bool wasWaitingForKey = m_numDecryptorsWaitingForKey > 0;
m_numDecryptorsWaitingForKey++;
callOnMainThread([player = m_player, wasWaitingForKey] {
if (player && wasWaitingForKey)
player->waitingForKeyChanged();
});
}
void CDMInstanceProxy::stoppedWaitingForKey()
{
ASSERT(!isMainThread() && m_player && 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());
m_keyStore.merge(keyStore);
LOG(EME, "EME - CDMInstanceProxy - merging keys into proxy instance and notifying CDMProxy of changes");
ASSERT(m_cdmProxy);
m_cdmProxy->updateKeyStore(keyStore);
}
void CDMInstanceProxy::removeAllKeysFrom(const KeyStore& keyStore)
{
ASSERT(isMainThread());
m_keyStore.removeAllKeysFrom(keyStore);
}
} // namespace WebCore
#endif // ENABLE(ENCRYPTED_MEDIA)