| /* |
| * 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) |