blob: c754e0ffaa385a2d5cf6489b5c2eb6cbd97eedab [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 "CDMThunder.h"
#if ENABLE(ENCRYPTED_MEDIA) && ENABLE(THUNDER)
#include "CDMKeySystemConfiguration.h"
#include "CDMProxyThunder.h"
#include "CDMRestrictions.h"
#include "CDMSessionType.h"
#include "CDMUtilities.h"
#include "GStreamerEMEUtilities.h"
#include "Logging.h"
#include "MediaKeyMessageType.h"
#include "NotImplemented.h"
#include "SharedBuffer.h"
#include "WebKitThunderDecryptorGStreamer.h"
#include <algorithm>
#include <iterator>
#include <wtf/MainThread.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/PrintStream.h>
#include <wtf/text/Base64.h>
#include <wtf/text/StringToIntegerConversion.h>
GST_DEBUG_CATEGORY(webkitMediaThunderDebugCategory);
#define GST_CAT_DEFAULT webkitMediaThunderDebugCategory
namespace {
// We are doing this to avoid conflict with CDMInstanceSession::KeyStatus.
using ThunderKeyStatus = KeyStatus;
}
static LicenseType thunderLicenseType(WebCore::CDMInstanceSession::LicenseType licenseType)
{
switch (licenseType) {
case WebCore::CDMInstanceSession::LicenseType::Temporary:
return Temporary;
case WebCore::CDMInstanceSession::LicenseType::PersistentUsageRecord:
return PersistentUsageRecord;
case WebCore::CDMInstanceSession::LicenseType::PersistentLicense:
return PersistentLicense;
default:
ASSERT_NOT_REACHED();
return Temporary;
}
}
namespace WebCore {
static CDMInstanceSession::SessionLoadFailure sessionLoadFailureFromThunder(const StringView& loadStatus)
{
if (loadStatus == "None")
return CDMInstanceSession::SessionLoadFailure::None;
if (loadStatus == "SessionNotFound")
return CDMInstanceSession::SessionLoadFailure::NoSessionData;
if (loadStatus == "MismatchedSessionType")
return CDMInstanceSession::SessionLoadFailure::MismatchedSessionType;
if (loadStatus == "QuotaExceeded")
return CDMInstanceSession::SessionLoadFailure::QuotaExceeded;
return CDMInstanceSession::SessionLoadFailure::Other;
}
CDMFactoryThunder& CDMFactoryThunder::singleton()
{
static std::once_flag onceFlag;
std::call_once(onceFlag, [] {
ensureGStreamerInitialized();
GST_DEBUG_CATEGORY_INIT(webkitMediaThunderDebugCategory, "webkitthunder", 0, "Thunder");
});
static NeverDestroyed<CDMFactoryThunder> s_factory;
return s_factory;
}
std::unique_ptr<CDMPrivate> CDMFactoryThunder::createCDM(const String& keySystem)
{
ASSERT(supportsKeySystem(keySystem));
return makeUnique<CDMPrivateThunder>(keySystem);
}
RefPtr<CDMProxy> CDMFactoryThunder::createCDMProxy(const String& keySystem)
{
ASSERT(supportsKeySystem(keySystem));
return adoptRef(new CDMProxyThunder(keySystem));
}
const Vector<String>& CDMFactoryThunder::supportedKeySystems() const
{
ASSERT(isMainThread());
static Vector<String> supportedKeySystems;
if (supportedKeySystems.isEmpty()) {
std::string emptyString;
if (opencdm_is_type_supported(GStreamerEMEUtilities::s_WidevineKeySystem, emptyString.c_str()) == ERROR_NONE)
supportedKeySystems.append(GStreamerEMEUtilities::s_WidevineKeySystem);
if (!supportedKeySystems.isEmpty()) {
unsigned thunderRank = isThunderRanked() ? 300 : 100;
gst_element_register(nullptr, "webkitthunder", GST_RANK_PRIMARY + thunderRank, WEBKIT_TYPE_MEDIA_THUNDER_DECRYPT);
}
#ifndef NDEBUG
else if (isThunderRanked())
GST_WARNING("Thunder is up-ranked as preferred decryptor but Thunder is not supporting any encryption system. Is "
"Thunder running? Are the plugins built?");
#endif
GST_DEBUG("%zu supported key systems", supportedKeySystems.size());
};
return supportedKeySystems;
}
bool CDMFactoryThunder::supportsKeySystem(const String& keySystem)
{
return CDMFactoryThunder::singleton().supportedKeySystems().contains(keySystem);
}
CDMPrivateThunder::CDMPrivateThunder(const String& keySystem)
: m_keySystem(keySystem)
, m_thunderSystem(opencdm_create_system(keySystem.utf8().data()))
{
};
Vector<AtomString> CDMPrivateThunder::supportedInitDataTypes() const
{
static std::once_flag onceFlag;
static Vector<AtomString> supportedInitDataTypes;
std::call_once(onceFlag, [] {
supportedInitDataTypes.reserveInitialCapacity(4);
supportedInitDataTypes.uncheckedAppend(AtomString("keyids"));
supportedInitDataTypes.uncheckedAppend(AtomString("cenc"));
supportedInitDataTypes.uncheckedAppend(AtomString("webm"));
supportedInitDataTypes.uncheckedAppend(AtomString("cbcs"));
});
return supportedInitDataTypes;
}
bool CDMPrivateThunder::supportsConfiguration(const CDMKeySystemConfiguration& configuration) const
{
for (auto& audioCapability : configuration.audioCapabilities) {
if (opencdm_is_type_supported(m_keySystem.utf8().data(), audioCapability.contentType.utf8().data()))
return false;
}
for (auto& videoCapability : configuration.videoCapabilities) {
if (opencdm_is_type_supported(m_keySystem.utf8().data(), videoCapability.contentType.utf8().data()))
return false;
}
return true;
}
Vector<AtomString> CDMPrivateThunder::supportedRobustnesses() const
{
return { emptyAtom(),
"SW_SECURE_DECODE",
"SW_SECURE_CRYPTO" };
}
CDMRequirement CDMPrivateThunder::distinctiveIdentifiersRequirement(const CDMKeySystemConfiguration&, const CDMRestrictions&) const
{
return CDMRequirement::Optional;
}
CDMRequirement CDMPrivateThunder::persistentStateRequirement(const CDMKeySystemConfiguration&, const CDMRestrictions&) const
{
return CDMRequirement::Optional;
}
bool CDMPrivateThunder::distinctiveIdentifiersAreUniquePerOriginAndClearable(const CDMKeySystemConfiguration&) const
{
return false;
}
RefPtr<CDMInstance> CDMPrivateThunder::createInstance()
{
return adoptRef(new CDMInstanceThunder(m_keySystem));
}
void CDMPrivateThunder::loadAndInitialize()
{
// No-op.
}
bool CDMPrivateThunder::supportsServerCertificates() const
{
bool isSupported = opencdm_system_supports_server_certificate(m_thunderSystem.get());
GST_DEBUG("server certificate supported %s", boolForPrinting(isSupported));
return isSupported;
}
bool CDMPrivateThunder::supportsSessions() const
{
// Sessions are supported.
return true;
}
bool CDMPrivateThunder::supportsInitData(const AtomString& initDataType, const SharedBuffer& initData) const
{
// Validate the initData buffer as an JSON object in keyids case.
if (equalLettersIgnoringASCIICase(initDataType, "keyids") && CDMUtilities::parseJSONObject(initData))
return true;
// Validate the initData buffer as CENC initData. FIXME: Validate it is actually CENC.
if (equalLettersIgnoringASCIICase(initDataType, "cenc") && !initData.isEmpty())
return true;
// Validate the initData buffer as WebM initData.
if (equalLettersIgnoringASCIICase(initDataType, "webm") && !initData.isEmpty())
return true;
return false;
}
RefPtr<SharedBuffer> CDMPrivateThunder::sanitizeInitData(const AtomString& initDataType, const SharedBuffer& initData) const
{
// Validate the initData buffer as CENC initData. FIXME: Validate it is actually CENC.
if (equalLettersIgnoringASCIICase(initDataType, "cenc") && !initData.isEmpty())
return initData.copy();
return CDMPrivate::sanitizeInitData(initDataType, initData);
}
RefPtr<SharedBuffer> CDMPrivateThunder::sanitizeResponse(const SharedBuffer& response) const
{
return response.copy();
}
std::optional<String> CDMPrivateThunder::sanitizeSessionId(const String& sessionId) const
{
return sessionId;
}
CDMInstanceThunder::CDMInstanceThunder(const String& keySystem)
: CDMInstanceProxy(keySystem)
, m_thunderSystem(opencdm_create_system(keySystem.utf8().data()))
, m_keySystem(keySystem)
{
}
void CDMInstanceThunder::initializeWithConfiguration(const CDMKeySystemConfiguration&, AllowDistinctiveIdentifiers, AllowPersistentState,
SuccessCallback&& callback)
{
callback(Succeeded);
}
void CDMInstanceThunder::setServerCertificate(Ref<SharedBuffer>&& certificate, SuccessCallback&& callback)
{
OpenCDMError error = opencdm_system_set_server_certificate(m_thunderSystem.get(), const_cast<uint8_t*>(certificate->data()),
certificate->size());
callback(!error ? Succeeded : Failed);
}
void CDMInstanceThunder::setStorageDirectory(const String& storageDirectory)
{
FileSystem::makeAllDirectories(storageDirectory);
}
CDMInstanceSessionThunder::CDMInstanceSessionThunder(CDMInstanceThunder& instance)
: CDMInstanceSessionProxy(instance)
{
ASSERT(isMainThread());
m_thunderSessionCallbacks.process_challenge_callback = [](OpenCDMSession*, void* userData, const char[], const uint8_t challenge[],
const uint16_t challengeLength) {
GST_DEBUG("Got 'challenge' OCDM notification with length %hu", challengeLength);
ASSERT(challengeLength > 0);
callOnMainThread([session = WeakPtr { static_cast<CDMInstanceSessionThunder*>(userData) }, buffer = WebCore::SharedBuffer::create(challenge,
challengeLength)]() mutable {
if (!session)
return;
session->challengeGeneratedCallback(WTFMove(buffer));
});
};
m_thunderSessionCallbacks.key_update_callback = [](OpenCDMSession*, void* userData, const uint8_t keyIDData[], const uint8_t keyIDLength) {
GST_DEBUG("Got 'key updated' OCDM notification");
KeyIDType keyID;
keyID.append(keyIDData, keyIDLength);
callOnMainThread([session = WeakPtr { static_cast<CDMInstanceSessionThunder*>(userData) }, keyID = WTFMove(keyID)]() mutable {
if (!session)
return;
session->keyUpdatedCallback(WTFMove(keyID));
});
};
m_thunderSessionCallbacks.keys_updated_callback = [](const OpenCDMSession*, void* userData) {
GST_DEBUG("Got 'all keys updated' OCDM notification");
callOnMainThread([session = WeakPtr { static_cast<CDMInstanceSessionThunder*>(userData) }]() {
if (!session)
return;
session->keysUpdateDoneCallback();
});
};
m_thunderSessionCallbacks.error_message_callback = [](OpenCDMSession*, void* userData, const char message[]) {
GST_ERROR("Got 'error' OCDM notification: %s", message);
callOnMainThread([session = WeakPtr { static_cast<CDMInstanceSessionThunder*>(userData) }, buffer = WebCore::SharedBuffer::create(message,
strlen(message))]() mutable {
if (!session)
return;
session->errorCallback(WTFMove(buffer));
});
};
}
RefPtr<CDMInstanceSession> CDMInstanceThunder::createSession()
{
RefPtr<CDMInstanceSessionThunder> newSession = adoptRef(new CDMInstanceSessionThunder(*this));
ASSERT(newSession);
return newSession;
}
class ParsedResponseMessage {
public:
ParsedResponseMessage(const RefPtr<SharedBuffer>& buffer)
{
if (!buffer || !buffer->size())
return;
GST_DEBUG("parsing buffer of size %zu", buffer->size());
GST_MEMDUMP("buffer", buffer->data(), buffer->size());
StringView payload(reinterpret_cast<const LChar*>(buffer->data()), buffer->size());
static NeverDestroyed<StringView> type(reinterpret_cast<const LChar*>(":Type:"), 6);
size_t typePosition = payload.find(type, 0);
StringView requestType(payload.characters8(), typePosition != notFound ? typePosition : 0);
unsigned offset = 0u;
if (!requestType.isEmpty() && requestType.length() != payload.length())
offset = typePosition + 6;
if (requestType.length() == 1) {
// FIXME: There are simpler ways to convert a single digit to a number than calling parseInteger.
m_type = std::make_optional(static_cast<WebCore::MediaKeyMessageType>(parseInteger<int>(requestType).value_or(0)));
}
m_payload = SharedBuffer::create(payload.characters8() + offset, payload.length() - offset);
m_isValid = true;
}
bool isValid() const { return m_isValid; }
bool hasPayload() const { return static_cast<bool>(m_payload); }
const Ref<SharedBuffer>& payload() const& { ASSERT(m_payload); return m_payload.value(); }
Ref<SharedBuffer>& payload() & { ASSERT(m_payload); return m_payload.value(); }
bool hasType() const { return m_type.has_value(); }
WebCore::MediaKeyMessageType type() const { ASSERT(m_type); return m_type.value(); }
WebCore::MediaKeyMessageType typeOr(WebCore::MediaKeyMessageType alternate) const { return m_type ? m_type.value() : alternate; }
explicit operator bool() const { return m_isValid; }
bool operator!() const { return !m_isValid; }
private:
bool m_isValid { false };
std::optional<Ref<SharedBuffer>> m_payload;
std::optional<WebCore::MediaKeyMessageType> m_type;
};
void CDMInstanceSessionThunder::challengeGeneratedCallback(RefPtr<SharedBuffer>&& buffer)
{
ParsedResponseMessage parsedResponseMessage(buffer);
if (!parsedResponseMessage) {
GST_ERROR("response message parsing failed");
ASSERT_NOT_REACHED();
return;
}
if (!m_challengeCallbacks.isEmpty()) {
m_message = WTFMove(parsedResponseMessage.payload());
m_needsIndividualization = parsedResponseMessage.hasType()
&& parsedResponseMessage.type() == CDMInstanceSession::MessageType::IndividualizationRequest;
for (const auto& challengeCallback : m_challengeCallbacks)
challengeCallback();
m_challengeCallbacks.clear();
} else if (!m_sessionChangedCallbacks.isEmpty()) {
for (auto& sessionChangedCallback : m_sessionChangedCallbacks)
sessionChangedCallback(true, parsedResponseMessage.payload().copyRef());
m_sessionChangedCallbacks.clear();
} else {
if (m_client && parsedResponseMessage.hasType()) {
m_client->sendMessage(static_cast<CDMMessageType>(parsedResponseMessage.type()),
WTFMove(parsedResponseMessage.payload()));
}
}
}
#if !defined(GST_DISABLE_GST_DEBUG) || !GST_DISABLE_GST_DEBUG
static const char* toString(CDMInstanceSession::KeyStatus status)
{
switch (status) {
case CDMInstanceSession::KeyStatus::Usable:
return "Usable";
case CDMInstanceSession::KeyStatus::Expired:
return "Expired";
case CDMInstanceSession::KeyStatus::Released:
return "Released";
case CDMInstanceSession::KeyStatus::OutputRestricted:
return "OutputRestricted";
case CDMInstanceSession::KeyStatus::OutputDownscaled:
return "OutputDownscaled";
case CDMInstanceSession::KeyStatus::StatusPending:
return "StatusPending";
case CDMInstanceSession::KeyStatus::InternalError:
return "InternalError";
default:
ASSERT_NOT_REACHED();
return "unknown";
}
}
#endif
CDMInstanceSession::KeyStatus CDMInstanceSessionThunder::status(const KeyIDType& keyID) const
{
ThunderKeyStatus status = m_session && !m_sessionID.isEmpty() ? opencdm_session_status(m_session->get(), keyID.data(), keyID.size()) : StatusPending;
switch (status) {
case Usable:
return CDMInstanceSession::KeyStatus::Usable;
case Expired:
return CDMInstanceSession::KeyStatus::Expired;
case Released:
return CDMInstanceSession::KeyStatus::Released;
case OutputRestricted:
return CDMInstanceSession::KeyStatus::OutputRestricted;
case OutputDownscaled:
return CDMInstanceSession::KeyStatus::OutputDownscaled;
case StatusPending:
return CDMInstanceSession::KeyStatus::StatusPending;
case InternalError:
return CDMInstanceSession::KeyStatus::InternalError;
default:
ASSERT_NOT_REACHED();
return CDMInstanceSession::KeyStatus::InternalError;
}
}
void CDMInstanceSessionThunder::keyUpdatedCallback(KeyIDType&& keyID)
{
GST_MEMDUMP("updated key", keyID.data(), keyID.size());
auto keyStatus = status(keyID);
GST_DEBUG("updated with with key status %s", toString(keyStatus));
m_doesKeyStoreNeedMerging |= m_keyStore.add(KeyHandle::create(keyStatus, WTFMove(keyID), BoxPtr<OpenCDMSession>(m_session)));
}
void CDMInstanceSessionThunder::keysUpdateDoneCallback()
{
GST_DEBUG("update done");
if (m_doesKeyStoreNeedMerging) {
m_doesKeyStoreNeedMerging = false;
if (auto instance = cdmInstanceThunder())
instance->mergeKeysFrom(m_keyStore);
}
if (m_sessionChangedCallbacks.isEmpty() && m_client) {
m_client->updateKeyStatuses(m_keyStore.convertToJSKeyStatusVector());
return;
}
for (auto& sessionChangedCallback : m_sessionChangedCallbacks)
sessionChangedCallback(true, nullptr);
m_sessionChangedCallbacks.clear();
}
void CDMInstanceSessionThunder::errorCallback(RefPtr<SharedBuffer>&& message)
{
GST_ERROR("CDM error");
GST_MEMDUMP("error dump", message->data(), message->size());
for (const auto& challengeCallback : m_challengeCallbacks)
challengeCallback();
m_challengeCallbacks.clear();
for (auto& sessionChangedCallback : m_sessionChangedCallbacks)
sessionChangedCallback(false, message.copyRef());
m_sessionChangedCallbacks.clear();
}
void CDMInstanceSessionThunder::requestLicense(LicenseType licenseType, const AtomString& initDataType, Ref<SharedBuffer>&& initDataSharedBuffer,
LicenseCallback&& callback)
{
ASSERT(isMainThread());
// FIXME: UUID or system ID?
auto instance = cdmInstanceThunder();
ASSERT(instance);
m_initData = InitData(instance->keySystem(), WTFMove(initDataSharedBuffer));
GST_TRACE("Going to request a new session id, init data size %zu", m_initData.payload()->size());
GST_MEMDUMP("init data", m_initData.payload()->data(), m_initData.payload()->size());
OpenCDMSession* session = nullptr;
opencdm_construct_session(&instance->thunderSystem(), thunderLicenseType(licenseType), initDataType.string().utf8().data(),
m_initData.payload()->data(), m_initData.payload()->size(), nullptr, 0, &m_thunderSessionCallbacks, this, &session);
if (!session) {
GST_ERROR("Could not create session");
RefPtr<SharedBuffer> initData = m_initData.payload();
callback(initData.releaseNonNull(), { }, false, Failed);
return;
}
m_session = adoptInBoxPtr(session);
m_sessionID = String::fromUTF8(opencdm_session_id(m_session->get()));
auto generateChallenge = [this, callback = WTFMove(callback)]() mutable {
ASSERT(isMainThread());
RefPtr<SharedBuffer> initData = m_initData.payload();
if (m_sessionID.isEmpty()) {
GST_ERROR("could not create session id");
callback(initData.releaseNonNull(), { }, false, Failed);
return;
}
if (!isValid()) {
GST_WARNING("created invalid session %s", m_sessionID.utf8().data());
callback(initData.releaseNonNull(), m_sessionID, false, Failed);
return;
}
GST_DEBUG("created valid session %s", m_sessionID.utf8().data());
callback(m_message.copyRef().releaseNonNull(), m_sessionID, m_needsIndividualization, Succeeded);
};
if (m_sessionID.isEmpty() || isValid())
generateChallenge();
else
m_challengeCallbacks.append(WTFMove(generateChallenge));
}
void CDMInstanceSessionThunder::sessionFailure()
{
for (auto& sessionChangedCallback : m_sessionChangedCallbacks)
sessionChangedCallback(false, nullptr);
m_sessionChangedCallbacks.clear();
}
void CDMInstanceSessionThunder::updateLicense(const String& sessionID, LicenseType, Ref<SharedBuffer>&& response, LicenseUpdateCallback&& callback)
{
ASSERT_UNUSED(sessionID, sessionID == m_sessionID);
GST_TRACE("Updating session %s", sessionID.utf8().data());
m_sessionChangedCallbacks.append([this, callback = WTFMove(callback)](bool success, RefPtr<SharedBuffer>&& responseMessage) mutable {
ASSERT(isMainThread());
if (success) {
if (!responseMessage)
callback(false, m_keyStore.convertToJSKeyStatusVector(), std::nullopt, std::nullopt, SuccessValue::Succeeded);
else {
// FIXME: Using JSON reponse messages is much cleaner than using string prefixes, I believe there
// will even be other parts of the spec where not having structured data will be bad.
ParsedResponseMessage parsedResponseMessage(responseMessage);
ASSERT(parsedResponseMessage);
if (parsedResponseMessage.hasPayload()) {
Ref<SharedBuffer> message = WTFMove(parsedResponseMessage.payload());
GST_DEBUG("got message of size %zu", message->size());
GST_MEMDUMP("message", message->data(), message->size());
callback(false, std::nullopt, std::nullopt,
std::make_pair(parsedResponseMessage.typeOr(MediaKeyMessageType::LicenseRequest),
WTFMove(message)), SuccessValue::Succeeded);
} else {
GST_ERROR("message of size %zu incorrectly formatted", responseMessage ? responseMessage->size() : 0);
callback(false, std::nullopt, std::nullopt, std::nullopt, SuccessValue::Failed);
}
}
} else {
GST_ERROR("update license reported error state");
callback(false, std::nullopt, std::nullopt, std::nullopt, SuccessValue::Failed);
}
});
if (!m_session || m_sessionID.isEmpty() || opencdm_session_update(m_session->get(), response->data(), response->size()))
sessionFailure();
}
void CDMInstanceSessionThunder::loadSession(LicenseType, const String& sessionID, const String&, LoadSessionCallback&& callback)
{
ASSERT_UNUSED(sessionID, sessionID == m_sessionID);
m_sessionChangedCallbacks.append([this, callback = WTFMove(callback)](bool success, RefPtr<SharedBuffer>&& responseMessage) mutable {
ASSERT(isMainThread());
if (success) {
if (!responseMessage)
callback(m_keyStore.convertToJSKeyStatusVector(), std::nullopt, std::nullopt, SuccessValue::Succeeded, SessionLoadFailure::None);
else {
// FIXME: Using JSON reponse messages is much cleaner than using string prefixes, I believe there
// will even be other parts of the spec where not having structured data will be bad.
ParsedResponseMessage parsedResponseMessage(responseMessage);
ASSERT(parsedResponseMessage);
if (parsedResponseMessage.hasPayload()) {
Ref<SharedBuffer> message = WTFMove(parsedResponseMessage.payload());
GST_DEBUG("got message of size %zu", message->size());
GST_MEMDUMP("message", message->data(), message->size());
callback(std::nullopt, std::nullopt, std::make_pair(parsedResponseMessage.typeOr(MediaKeyMessageType::LicenseRequest),
WTFMove(message)), SuccessValue::Succeeded, SessionLoadFailure::None);
} else {
GST_ERROR("message of size %zu incorrectly formatted", responseMessage ? responseMessage->size() : 0);
callback(std::nullopt, std::nullopt, std::nullopt, SuccessValue::Failed, SessionLoadFailure::Other);
}
}
} else {
auto responseMessageData = responseMessage ? responseMessage->data() : nullptr;
auto responseMessageSize = responseMessage ? responseMessage->size() : 0;
StringView response(reinterpret_cast<const LChar*>(responseMessageData), responseMessageSize);
GST_ERROR("session %s not loaded, reason %s", m_sessionID.utf8().data(), response.utf8().data());
callback(std::nullopt, std::nullopt, std::nullopt, SuccessValue::Failed, sessionLoadFailureFromThunder(response));
}
});
if (!m_session || m_sessionID.isEmpty() || opencdm_session_load(m_session->get()))
sessionFailure();
}
void CDMInstanceSessionThunder::closeSession(const String& sessionID, CloseSessionCallback&& callback)
{
ASSERT_UNUSED(sessionID, m_sessionID == sessionID);
if (m_session && !m_sessionID.isEmpty()) {
opencdm_session_close(m_session->get());
m_session = BoxPtr<OpenCDMSession>();
auto instance = cdmInstanceThunder();
if (instance) {
instance->unrefAllKeysFrom(m_keyStore);
m_keyStore.unrefAllKeys();
}
}
callback();
}
void CDMInstanceSessionThunder::removeSessionData(const String& sessionID, LicenseType, RemoveSessionDataCallback&& callback)
{
ASSERT_UNUSED(sessionID, m_sessionID == sessionID);
m_sessionChangedCallbacks.append([this, callback = WTFMove(callback)](bool success, RefPtr<SharedBuffer>&& buffer) mutable {
ASSERT(isMainThread());
if (success) {
if (!buffer)
callback(m_keyStore.allKeysAs(MediaKeyStatus::Released), std::nullopt, SuccessValue::Succeeded);
else {
ParsedResponseMessage parsedResponseMessage(buffer);
ASSERT(parsedResponseMessage);
if (parsedResponseMessage.hasPayload()) {
Ref<SharedBuffer> message = WTFMove(parsedResponseMessage.payload());
GST_DEBUG("session %s removed, message length %zu", m_sessionID.utf8().data(), message->size());
callback(m_keyStore.allKeysAs(MediaKeyStatus::Released), WTFMove(message), SuccessValue::Succeeded);
} else {
GST_WARNING("message of size %zu incorrectly formatted as session %s removal answer", buffer ? buffer->size() : 0,
m_sessionID.utf8().data());
callback(m_keyStore.allKeysAs(MediaKeyStatus::InternalError), std::nullopt, SuccessValue::Failed);
}
}
} else {
GST_WARNING("could not remove session %s", m_sessionID.utf8().data());
callback(m_keyStore.allKeysAs(MediaKeyStatus::InternalError), std::nullopt, SuccessValue::Failed);
}
});
if (!m_session || m_sessionID.isEmpty() || opencdm_session_remove(m_session->get()))
sessionFailure();
}
void CDMInstanceSessionThunder::storeRecordOfKeyUsage(const String&)
{
notImplemented();
}
CDMInstanceThunder* CDMInstanceSessionThunder::cdmInstanceThunder() const
{
auto proxy = cdmInstanceProxy();
return static_cast<CDMInstanceThunder*>(proxy.get());
}
} // namespace WebCore
#endif // ENABLE(ENCRYPTED_MEDIA) && ENABLE(THUNDER)