From 2df2bec6597fd13eea00c011953aaa463d86734b Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Sun, 25 Oct 2026 13:00:15 +0200 Subject: [EME] Add Widevine to the external CDM backend https://bugs.webkit.org/show_bug.cgi?id=328385 Reviewed by NOBODY (OOPS!). Add com.widevine.alpha to the GStreamer external CDM backend, driving the Chromium Widevine CDM in process through inputstream.adaptive's cdm_adapter, vendored and built in a unit of its own because it needs exceptions and RTTI. KeyHandleValueVariant gains a BoxPtr alternative under ENABLE(EXTERNAL_CDM), mirroring the Thunder BoxPtr, so a decryptor reaches the CDM by key id while the key bytes stay inside it. CDMInstanceExternalCDM owns one CdmAdapter per instance and routes its host callbacks onto the main thread; the session maps requestLicense, updateLicense and close/remove onto CreateSessionAndGenerateRequest, UpdateSession and Close/RemoveSession, and fills the key store from OnSessionKeysChange. CDMProxyExternalCDM dispatches on the key-handle variant: ClearKey keeps the libgcrypt path, Widevine builds a cdm::InputBuffer_2 and decrypts in place at L3, carrying the cbcs crypt/skip pattern, the subsample map from the protection metadata and the buffer presentation timestamp in microseconds. This Widevine CDM returns kNoKey from Decrypt() for video and yields pictures only through DecryptAndDecodeFrame(), so encrypted video is handled by webkitexternalcdmvideodec, a GstVideoDecoder that hands encrypted AV1, VP9, VP8 and H.264 with the Widevine protection system to the CDM to decrypt and decode into I420 and its 10- and 12-bit variants, clear-lead frames included. H.264 is passed as Annex B with the parameter sets ahead of each keyframe, the form the CDM expects. Ranked above the decryptor, parsebin routes encrypted Widevine video to this decoder while audio and ClearKey keep the decryptor, and the sample handling common to both is factored out. The decoder serialises its own CDM calls and the instance publishes its adapter so clear frames decode before the first key arrives. The factory offers Widevine when the CDM blob is present. ClearKey is unchanged and the feature stays behind ENABLE_EXTERNAL_CDM. * Source/WebCore/platform/GStreamer.cmake: * Source/WebCore/platform/SourcesGStreamer.txt: * Source/WebCore/platform/encryptedmedia/CDMExternalCDMTypes.h: Copied from Source/WebCore/platform/graphics/gstreamer/eme/CDMProxyExternalCDM.h. * Source/WebCore/platform/encryptedmedia/CDMProxy.h: * Source/WebCore/platform/graphics/gstreamer/GStreamerCommon.cpp: (WebCore::registerWebKitGStreamerElements): * Source/WebCore/platform/graphics/gstreamer/eme/CDMExternalCDM.cpp: (WebCore::widevineBlobPath): (WebCore::keyStatusFromCdm): (WebCore::CDMFactoryExternalCDM::CDMFactoryExternalCDM): (WebCore::CDMPrivateExternalCDM::isWidevine const): (WebCore::CDMPrivateExternalCDM::supportedInitDataTypes const): (WebCore::CDMPrivateExternalCDM::supportsConfiguration const): (WebCore::CDMPrivateExternalCDM::supportsConfigurationWithRestrictions const): (WebCore::CDMPrivateExternalCDM::supportedRobustnesses const): (WebCore::CDMPrivateExternalCDM::supportsServerCertificates const): (WebCore::CDMPrivateExternalCDM::supportsInitData const): (WebCore::CDMPrivateExternalCDM::sanitizeResponse const): (WebCore::CDMPrivateExternalCDM::sanitizeSessionId const): (WebCore::ExternalCdmClient::ExternalCdmClient): (WebCore::ExternalCdmClient::clearInstance): (WebCore::ExternalCdmClient::OnCDMMessage): (WebCore::ExternalCdmClient::OnKeyStatusChange): (WebCore::ExternalCdmClient::AllocateBuffer): (WebCore::ExternalCdmClient::OnPromiseResolved): (WebCore::ExternalCdmClient::OnPromiseRejected): (WebCore::CDMInstanceExternalCDM::~CDMInstanceExternalCDM): (WebCore::CDMInstanceExternalCDM::registerPromise): (WebCore::CDMInstanceExternalCDM::settlePromise): (WebCore::CDMInstanceExternalCDM::isWidevine const): (WebCore::CDMInstanceExternalCDM::ensureAdapter): (WebCore::CDMInstanceExternalCDM::initializeWithConfiguration): (WebCore::CDMInstanceExternalCDM::setServerCertificate): (WebCore::CDMInstanceExternalCDM::setStorageDirectory): (WebCore::CDMInstanceExternalCDM::registerPendingSession): (WebCore::CDMInstanceExternalCDM::unregisterSession): (WebCore::CDMInstanceExternalCDM::sessionForID): (WebCore::CDMInstanceExternalCDM::takePendingSession): (WebCore::CDMInstanceExternalCDM::dispatchCdmMessage): (WebCore::CDMInstanceExternalCDM::dispatchKeyStatusChange): (WebCore::CDMInstanceSessionExternalCDM::setCdmSessionID): (WebCore::CDMInstanceSessionExternalCDM::onCdmMessage): (WebCore::CDMInstanceSessionExternalCDM::onKeyStatusChange): (WebCore::CDMInstanceSessionExternalCDM::requestLicense): (WebCore::CDMInstanceSessionExternalCDM::requestLicenseClearKey): (WebCore::CDMInstanceSessionExternalCDM::requestLicenseWidevine): (WebCore::CDMInstanceSessionExternalCDM::updateLicense): (WebCore::CDMInstanceSessionExternalCDM::updateLicenseClearKey): (WebCore::CDMInstanceSessionExternalCDM::updateLicenseWidevine): (WebCore::CDMInstanceSessionExternalCDM::closeSession): (WebCore::CDMInstanceSessionExternalCDM::removeSessionData): * Source/WebCore/platform/graphics/gstreamer/eme/CDMExternalCDM.h: * Source/WebCore/platform/graphics/gstreamer/eme/CDMProxyExternalCDM.cpp: (WebCore::externalCdmSessionDestroy): (WebCore::CDMProxyExternalCDM::clearKeyDecrypt): (WebCore::CDMProxyExternalCDM::externalCdmInputFromContext): (WebCore::CDMProxyExternalCDM::externalCdmDecrypt): (WebCore::CDMProxyExternalCDM::cencDecrypt): (WebCore::CDMProxyExternalCDM::setAdapter): (WebCore::CDMProxyExternalCDM::abortWaitingForAdapter): (WebCore::CDMProxyExternalCDM::getOrWaitForVideoAdapter): (WebCore::CDMProxyExternalCDM::decryptAndDecodeVideoFrame): * Source/WebCore/platform/graphics/gstreamer/eme/CDMProxyExternalCDM.h: (WebCore::ExternalCdmSession::ExternalCdmSession): (WebCore::ExternalCdmSession::adapter const): * Source/WebCore/platform/graphics/gstreamer/eme/WebKitCommonEncryptionDecryptorGStreamer.cpp: (webKitMediaCommonEncryptionStripProtection): (transformCaps): (webKitMediaCommonEncryptionParseProtectionMeta): (transformInPlace): (webKitMediaCommonEncryptionCDMProxyFromContext): (installCDMProxyIfNotAvailable): (getCDMProxyFromGstContext): Deleted. * Source/WebCore/platform/graphics/gstreamer/eme/WebKitCommonEncryptionDecryptorGStreamer.h: * Source/WebCore/platform/graphics/gstreamer/eme/WebKitExternalCDMDecryptorGStreamer.cpp: (decrypt): (WebKitExternalCDMDecryptInput::map): * Source/WebCore/platform/graphics/gstreamer/eme/WebKitExternalCDMDecryptorGStreamer.h: * Source/WebCore/platform/graphics/gstreamer/eme/WebKitExternalCDMVideoDecoderGStreamer.cpp: Added. (CdmBufferDestroyer::operator() const): (ExternalCDMVideoDecoderClient::isAborting): (createSinkPadTemplateCaps): (videoFormatFromCdm): (videoFormatFromCaps): (videoCodecFromCaps): (h264ParameterSetsFromCodecData): (h264SampleToAnnexB): (abortWaits): (attachCDMProxyFromContext): (waitForCDMProxy): (deinitializeDecoder): (ensureDecoder): (ensureOutputState): (takePendingFrame): (wrapCdmBuffer): (copyToOutputBuffer): (pushDecodedFrame): (decodeFrame): (drainOneFrame): (drainFrames): (handleFrame): (setFormat): (decideAllocation): (flush): (finish): (stop): (decodableCapsEvent): (sinkEvent): (changeState): (setContext): (constructed): (webkit_external_cdm_video_decoder_class_init): * Source/WebCore/platform/graphics/gstreamer/eme/WebKitExternalCDMVideoDecoderGStreamer.h: Added. * Source/WebCore/platform/graphics/gstreamer/eme/cdm/base/base_export.h: Added. * Source/WebCore/platform/graphics/gstreamer/eme/cdm/base/basictypes.h: Added. * Source/WebCore/platform/graphics/gstreamer/eme/cdm/base/compiler_specific.h: Added. * Source/WebCore/platform/graphics/gstreamer/eme/cdm/base/macros.h: Added. (bit_cast): (ignore_result): * Source/WebCore/platform/graphics/gstreamer/eme/cdm/base/native_library.cc: Added. * Source/WebCore/platform/graphics/gstreamer/eme/cdm/base/native_library.h: Added. * Source/WebCore/platform/graphics/gstreamer/eme/cdm/base/native_library_posix.cc: Added. * Source/WebCore/platform/graphics/gstreamer/eme/cdm/build/build_config.h: Added. * Source/WebCore/platform/graphics/gstreamer/eme/cdm/debug.cpp: Added. (CDM_DBG::SetDBGMsgCallback): * Source/WebCore/platform/graphics/gstreamer/eme/cdm/debug.h: Added. * Source/WebCore/platform/graphics/gstreamer/eme/cdm/media/base/cdm_config.h: Added. (media::CdmConfig::CdmConfig): * Source/WebCore/platform/graphics/gstreamer/eme/cdm/media/base/limits.h: Added. * Source/WebCore/platform/graphics/gstreamer/eme/cdm/media/cdm/api/content_decryption_module.h: Added. * Source/WebCore/platform/graphics/gstreamer/eme/cdm/media/cdm/api/content_decryption_module_export.h: Added. * Source/WebCore/platform/graphics/gstreamer/eme/cdm/media/cdm/cdm_adapter.cc: Added. * Source/WebCore/platform/graphics/gstreamer/eme/cdm/media/cdm/cdm_adapter.h: Added. * Source/WebKit/UIProcess/Launcher/glib/BubblewrapLauncher.cpp: (WebKit::bindExternalCDM): (WebKit::bubblewrapSpawn): --- Source/WebCore/platform/GStreamer.cmake | 25 + Source/WebCore/platform/SourcesGStreamer.txt | 5 +- .../encryptedmedia/CDMExternalCDMTypes.h | 53 + .../platform/encryptedmedia/CDMProxy.h | 7 + .../graphics/gstreamer/GStreamerCommon.cpp | 4 + .../graphics/gstreamer/eme/CDMExternalCDM.cpp | 472 ++++- .../graphics/gstreamer/eme/CDMExternalCDM.h | 87 +- .../gstreamer/eme/CDMProxyExternalCDM.cpp | 231 ++- .../gstreamer/eme/CDMProxyExternalCDM.h | 93 +- ...bKitCommonEncryptionDecryptorGStreamer.cpp | 176 +- ...WebKitCommonEncryptionDecryptorGStreamer.h | 15 + .../WebKitExternalCDMDecryptorGStreamer.cpp | 86 +- .../eme/WebKitExternalCDMDecryptorGStreamer.h | 16 + ...WebKitExternalCDMVideoDecoderGStreamer.cpp | 808 ++++++++ .../WebKitExternalCDMVideoDecoderGStreamer.h | 50 + .../gstreamer/eme/cdm/base/base_export.h | 33 + .../gstreamer/eme/cdm/base/basictypes.h | 37 + .../eme/cdm/base/compiler_specific.h | 195 ++ .../graphics/gstreamer/eme/cdm/base/macros.h | 161 ++ .../gstreamer/eme/cdm/base/native_library.cc | 38 + .../gstreamer/eme/cdm/base/native_library.h | 107 + .../eme/cdm/base/native_library_posix.cc | 64 + .../gstreamer/eme/cdm/build/build_config.h | 203 ++ .../graphics/gstreamer/eme/cdm/debug.cpp | 57 + .../graphics/gstreamer/eme/cdm/debug.h | 26 + .../gstreamer/eme/cdm/media/base/cdm_config.h | 38 + .../gstreamer/eme/cdm/media/base/limits.h | 68 + .../media/cdm/api/content_decryption_module.h | 1793 +++++++++++++++++ .../api/content_decryption_module_export.h | 42 + .../eme/cdm/media/cdm/cdm_adapter.cc | 1051 ++++++++++ .../gstreamer/eme/cdm/media/cdm/cdm_adapter.h | 330 +++ .../Launcher/glib/BubblewrapLauncher.cpp | 15 + 32 files changed, 6245 insertions(+), 141 deletions(-) create mode 100644 Source/WebCore/platform/encryptedmedia/CDMExternalCDMTypes.h create mode 100644 Source/WebCore/platform/graphics/gstreamer/eme/WebKitExternalCDMVideoDecoderGStreamer.cpp create mode 100644 Source/WebCore/platform/graphics/gstreamer/eme/WebKitExternalCDMVideoDecoderGStreamer.h create mode 100644 Source/WebCore/platform/graphics/gstreamer/eme/cdm/base/base_export.h create mode 100644 Source/WebCore/platform/graphics/gstreamer/eme/cdm/base/basictypes.h create mode 100644 Source/WebCore/platform/graphics/gstreamer/eme/cdm/base/compiler_specific.h create mode 100644 Source/WebCore/platform/graphics/gstreamer/eme/cdm/base/macros.h create mode 100644 Source/WebCore/platform/graphics/gstreamer/eme/cdm/base/native_library.cc create mode 100644 Source/WebCore/platform/graphics/gstreamer/eme/cdm/base/native_library.h create mode 100644 Source/WebCore/platform/graphics/gstreamer/eme/cdm/base/native_library_posix.cc create mode 100644 Source/WebCore/platform/graphics/gstreamer/eme/cdm/build/build_config.h create mode 100644 Source/WebCore/platform/graphics/gstreamer/eme/cdm/debug.cpp create mode 100644 Source/WebCore/platform/graphics/gstreamer/eme/cdm/debug.h create mode 100644 Source/WebCore/platform/graphics/gstreamer/eme/cdm/media/base/cdm_config.h create mode 100644 Source/WebCore/platform/graphics/gstreamer/eme/cdm/media/base/limits.h create mode 100644 Source/WebCore/platform/graphics/gstreamer/eme/cdm/media/cdm/api/content_decryption_module.h create mode 100644 Source/WebCore/platform/graphics/gstreamer/eme/cdm/media/cdm/api/content_decryption_module_export.h create mode 100644 Source/WebCore/platform/graphics/gstreamer/eme/cdm/media/cdm/cdm_adapter.cc create mode 100644 Source/WebCore/platform/graphics/gstreamer/eme/cdm/media/cdm/cdm_adapter.h --- a/Source/WebCore/platform/GStreamer.cmake +++ b/Source/WebCore/platform/GStreamer.cmake @@ -193,6 +193,31 @@ if (ENABLE_ENCRYPTED_MEDIA AND ENABLE_TH endif () endif () +if (ENABLE_ENCRYPTED_MEDIA AND ENABLE_EXTERNAL_CDM) + # inputstream.adaptive's Chromium CDM adapter, vendored verbatim and built in + # a unit of its own because it uses C++ exceptions and RTTI, which the rest + # of WebKit disables globally. Re-enable them here, and pin C++17 as the + # upstream project builds it. + set(CDM_ADAPTER_DIR "${WEBCORE_DIR}/platform/graphics/gstreamer/eme/cdm") + add_library(cdmadapter STATIC + "${CDM_ADAPTER_DIR}/media/cdm/cdm_adapter.cc" + "${CDM_ADAPTER_DIR}/base/native_library.cc" + "${CDM_ADAPTER_DIR}/base/native_library_posix.cc" + "${CDM_ADAPTER_DIR}/debug.cpp" + ) + target_include_directories(cdmadapter PRIVATE "${CDM_ADAPTER_DIR}") + target_compile_options(cdmadapter PRIVATE -std=c++17 -fexceptions -frtti) + set_target_properties(cdmadapter PROPERTIES POSITION_INDEPENDENT_CODE ON) + + list(APPEND WebCore_PRIVATE_INCLUDE_DIRECTORIES + "${WEBCORE_DIR}/platform/graphics/gstreamer/eme" + ) + list(APPEND WebCore_LIBRARIES + cdmadapter + ${CMAKE_DL_LIBS} + ) +endif () + if (ENABLE_SPEECH_SYNTHESIS) if (USE_SPIEL) list(APPEND WebCore_SOURCES --- a/Source/WebCore/platform/SourcesGStreamer.txt +++ b/Source/WebCore/platform/SourcesGStreamer.txt @@ -82,14 +82,15 @@ platform/graphics/gstreamer/VideoTrackPr platform/graphics/gstreamer/WebKitAudioSinkGStreamer.cpp platform/graphics/gstreamer/WebKitWebSourceGStreamer.cpp -platform/graphics/gstreamer/eme/CDMExternalCDM.cpp +platform/graphics/gstreamer/eme/CDMExternalCDM.cpp @no-unify platform/graphics/gstreamer/eme/CDMFactoryGStreamer.cpp -platform/graphics/gstreamer/eme/CDMProxyExternalCDM.cpp +platform/graphics/gstreamer/eme/CDMProxyExternalCDM.cpp @no-unify platform/graphics/gstreamer/eme/CDMProxyThunder.cpp platform/graphics/gstreamer/eme/CDMThunder.cpp platform/graphics/gstreamer/eme/GStreamerEMEUtilities.cpp platform/graphics/gstreamer/eme/WebKitCommonEncryptionDecryptorGStreamer.cpp platform/graphics/gstreamer/eme/WebKitExternalCDMDecryptorGStreamer.cpp @no-unify +platform/graphics/gstreamer/eme/WebKitExternalCDMVideoDecoderGStreamer.cpp @no-unify platform/graphics/gstreamer/eme/WebKitThunderDecryptorGStreamer.cpp platform/graphics/gstreamer/eme/WebKitThunderParser.cpp --- /dev/null +++ b/Source/WebCore/platform/encryptedmedia/CDMExternalCDMTypes.h @@ -0,0 +1,53 @@ +/* + * 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. + */ + +#pragma once + +#if ENABLE(ENCRYPTED_MEDIA) && ENABLE(EXTERNAL_CDM) + +#include + +namespace WebCore { + +// Opaque handle to a session living inside an external Chromium CDM. The +// decryptor holds this (by key id, through the shared key store) so it can +// reach the CDM to decrypt; the key bytes never leave the CDM. The full type +// is defined in the GStreamer external-CDM backend, so this header carries only +// what the KeyHandleValueVariant needs, mirroring CDMOpenCDMTypes.h. +class ExternalCdmSession; +void externalCdmSessionDestroy(ExternalCdmSession*); + +} // namespace WebCore + +namespace WTF { + +WTF_DEFINE_BOXPTR_DELETER(WebCore::ExternalCdmSession, WebCore::externalCdmSessionDestroy) + +} // namespace WTF + +#endif // ENABLE(ENCRYPTED_MEDIA) && ENABLE(EXTERNAL_CDM) --- a/Source/WebCore/platform/encryptedmedia/CDMProxy.h +++ b/Source/WebCore/platform/encryptedmedia/CDMProxy.h @@ -48,6 +48,10 @@ #include "CDMOpenCDMTypes.h" #endif +#if ENABLE(EXTERNAL_CDM) +#include "CDMExternalCDMTypes.h" +#endif + namespace WebCore { class MediaPlayer; @@ -59,6 +63,9 @@ using KeyHandleValueVariant = Variant< #if ENABLE(THUNDER) , BoxPtr #endif +#if ENABLE(EXTERNAL_CDM) + , BoxPtr +#endif >; class KeyHandle : public ThreadSafeRefCounted { --- a/Source/WebCore/platform/graphics/gstreamer/GStreamerCommon.cpp +++ b/Source/WebCore/platform/graphics/gstreamer/GStreamerCommon.cpp @@ -92,6 +92,7 @@ #if ENABLE(ENCRYPTED_MEDIA) && ENABLE(EXTERNAL_CDM) #include "CDMExternalCDM.h" #include "WebKitExternalCDMDecryptorGStreamer.h" +#include "WebKitExternalCDMVideoDecoderGStreamer.h" #endif #if ENABLE(VIDEO) @@ -574,6 +575,9 @@ void registerWebKitGStreamerElements() #if ENABLE(ENCRYPTED_MEDIA) && ENABLE(EXTERNAL_CDM) if (!CDMFactoryExternalCDM::singleton().supportedKeySystems().isEmpty()) gst_element_register(nullptr, "webkitexternalcdm", GST_RANK_PRIMARY + 100, WEBKIT_TYPE_MEDIA_EXTERNAL_CDM_DECRYPT); + // Ranked above the decryptor, parsebin then hands encrypted Widevine video to decodebin3 for this decoder. + if (CDMFactoryExternalCDM::singleton().supportsKeySystem(GStreamerEMEUtilities::s_WidevineKeySystem)) + gst_element_register(nullptr, "webkitexternalcdmvideodec", GST_RANK_PRIMARY + 101, WEBKIT_TYPE_EXTERNAL_CDM_VIDEO_DECODER); #endif #if ENABLE(MEDIA_STREAM) --- a/Source/WebCore/platform/graphics/gstreamer/eme/CDMExternalCDM.cpp +++ b/Source/WebCore/platform/graphics/gstreamer/eme/CDMExternalCDM.cpp @@ -33,6 +33,7 @@ #include "CDMClearKey.h" #include "CDMKeySystemConfiguration.h" +#include "CDMMessageType.h" #include "CDMProxyExternalCDM.h" #include "CDMRestrictions.h" #include "CDMSessionType.h" @@ -40,12 +41,15 @@ #include "GStreamerCommon.h" #include "GStreamerEMEUtilities.h" #include "Logging.h" +#include #include +#include #include #include #include #include #include +#include #include namespace WebCore { @@ -54,6 +58,37 @@ WTF_MAKE_TZONE_ALLOCATED_IMPL(CDMFactory WTF_MAKE_TZONE_ALLOCATED_IMPL(CDMPrivateExternalCDM); WTF_MAKE_TZONE_ALLOCATED_IMPL(CDMInstanceExternalCDM); +static String widevineBlobPath() +{ + if (const char* env = getenv("WPE_WIDEVINE_CDM_PATH")) { + if (*env) + return String::fromUTF8(env); + } + return { }; +} + +static CDMInstanceSession::KeyStatus keyStatusFromCdm(media::CdmAdapterClient::CDMKeyStatus status) +{ + switch (status) { + case media::CdmAdapterClient::CDMKeyStatus::kUsable: + return CDMInstanceSession::KeyStatus::Usable; + case media::CdmAdapterClient::CDMKeyStatus::kExpired: + return CDMInstanceSession::KeyStatus::Expired; + case media::CdmAdapterClient::CDMKeyStatus::kReleased: + return CDMInstanceSession::KeyStatus::Released; + case media::CdmAdapterClient::CDMKeyStatus::kOutputRestricted: + return CDMInstanceSession::KeyStatus::OutputRestricted; + case media::CdmAdapterClient::CDMKeyStatus::kOutputDownscaled: + return CDMInstanceSession::KeyStatus::OutputDownscaled; + case media::CdmAdapterClient::CDMKeyStatus::kStatusPending: + case media::CdmAdapterClient::CDMKeyStatus::kUsableInFuture: + return CDMInstanceSession::KeyStatus::StatusPending; + case media::CdmAdapterClient::CDMKeyStatus::kInternalError: + default: + return CDMInstanceSession::KeyStatus::InternalError; + } +} + CDMFactoryExternalCDM& CDMFactoryExternalCDM::singleton() { static std::once_flag onceFlag; @@ -68,9 +103,12 @@ CDMFactoryExternalCDM::CDMFactoryExterna { ASSERT(isMainThread()); - // ClearKey is software-only and always available. Widevine and other - // blob-backed key systems are a later iteration of this backend. + // ClearKey is software-only and always available. Widevine is offered when + // the Chromium CDM blob is present; it is loaded lazily per instance. m_supportedKeySystems.append(String::fromLatin1(GStreamerEMEUtilities::s_ClearKeyKeySystem)); + + if (FileSystem::fileExists(widevineBlobPath())) + m_supportedKeySystems.append(String::fromLatin1(GStreamerEMEUtilities::s_WidevineKeySystem)); } std::unique_ptr CDMFactoryExternalCDM::createCDM(const String& keySystem, const String& mediaKeysHashSalt, const CDMPrivateClient&) @@ -96,8 +134,15 @@ CDMPrivateExternalCDM::CDMPrivateExterna { } +bool CDMPrivateExternalCDM::isWidevine() const +{ + return GStreamerEMEUtilities::isWidevineKeySystem(m_keySystem); +} + Vector CDMPrivateExternalCDM::supportedInitDataTypes() const { + if (isWidevine()) + return { "keyids"_s, "cenc"_s, "cbcs"_s, "webm"_s }; return { "keyids"_s, "cenc"_s, "webm"_s }; } @@ -111,7 +156,10 @@ bool CDMPrivateExternalCDM::supportsConf if (configuration.distinctiveIdentifier == CDMRequirement::Required) return false; - if (configuration.persistentState == CDMRequirement::Required && !containsPersistentLicenseType(configuration.sessionTypes)) + // Widevine persists device state through CreateFileIO regardless of session + // type; the persistent-license requirement is a ClearKey-only rule. + if (configuration.persistentState == CDMRequirement::Required && !isWidevine() + && !containsPersistentLicenseType(configuration.sessionTypes)) return false; return true; @@ -126,7 +174,9 @@ bool CDMPrivateExternalCDM::supportsConf if (configuration.persistentState == CDMRequirement::Optional && restrictions.persistentStateDenied) return false; - if (configuration.persistentState == CDMRequirement::Required && !containsPersistentLicenseType(configuration.sessionTypes)) + if (configuration.persistentState == CDMRequirement::Required + && (restrictions.persistentStateDenied + || (!isWidevine() && !containsPersistentLicenseType(configuration.sessionTypes)))) return false; return true; @@ -141,6 +191,8 @@ bool CDMPrivateExternalCDM::supportsSess Vector CDMPrivateExternalCDM::supportedRobustnesses() const { + if (isWidevine()) + return { emptyString(), "SW_SECURE_CRYPTO"_s, "SW_SECURE_DECODE"_s }; return { emptyString() }; } @@ -175,8 +227,7 @@ void CDMPrivateExternalCDM::loadAndIniti bool CDMPrivateExternalCDM::supportsServerCertificates() const { - // ClearKey does not use server certificates. - return false; + return isWidevine(); } bool CDMPrivateExternalCDM::supportsSessions() const @@ -192,6 +243,9 @@ bool CDMPrivateExternalCDM::supportsInit if (equalLettersIgnoringASCIICase(initDataType, "cenc"_s) && !initData.isEmpty()) return true; + if (isWidevine() && equalLettersIgnoringASCIICase(initDataType, "cbcs"_s) && !initData.isEmpty()) + return true; + if (equalLettersIgnoringASCIICase(initDataType, "webm"_s) && !initData.isEmpty()) return true; @@ -200,6 +254,10 @@ bool CDMPrivateExternalCDM::supportsInit RefPtr CDMPrivateExternalCDM::sanitizeResponse(const SharedBuffer& response) const { + // A Widevine licence is an opaque binary blob; ClearKey is JSON. + if (isWidevine()) + return response.makeContiguous(); + if (!CDMUtilities::parseJSONObject(response)) return nullptr; return response.makeContiguous(); @@ -207,31 +265,183 @@ RefPtr CDMPrivateExternalC std::optional CDMPrivateExternalCDM::sanitizeSessionId(const String& sessionId) const { + if (isWidevine()) + return sessionId.isEmpty() ? std::nullopt : std::make_optional(sessionId); + if (!parseInteger(sessionId)) return std::nullopt; return sessionId; } +ExternalCdmClient::ExternalCdmClient(CDMInstanceExternalCDM& instance) + : m_instance(&instance) +{ + ASSERT(isMainThread()); +} + +void ExternalCdmClient::clearInstance() +{ + ASSERT(isMainThread()); + m_instance = nullptr; +} + +void ExternalCdmClient::OnCDMMessage(const char* session, uint32_t sessionSize, CDMADPMSG msg, const uint8_t* data, size_t dataSize, uint32_t) +{ + String sessionID = session ? String::fromUTF8(unsafeMakeSpan(session, static_cast(sessionSize))) : emptyString(); + auto buffer = (data && dataSize) ? SharedBuffer::create(unsafeMakeSpan(data, dataSize)) : SharedBuffer::create(); + callOnMainThread([protectedThis = Ref { *this }, sessionID = sessionID.isolatedCopy(), msg, buffer = WTF::move(buffer)]() mutable { + if (protectedThis->m_instance) + protectedThis->m_instance->dispatchCdmMessage(sessionID, msg, WTF::move(buffer)); + }); +} + +void ExternalCdmClient::OnKeyStatusChange(const std::string& sessionId, const std::vector& keysInfo) +{ + String sessionID = String::fromUTF8(unsafeMakeSpan(sessionId.data(), sessionId.size())); + Vector keys; + keys.reserveInitialCapacity(keysInfo.size()); + for (const auto& key : keysInfo) + keys.append(key); + callOnMainThread([protectedThis = Ref { *this }, sessionID = sessionID.isolatedCopy(), keys = WTF::move(keys)]() mutable { + if (protectedThis->m_instance) + protectedThis->m_instance->dispatchKeyStatusChange(sessionID, WTF::move(keys)); + }); +} + +cdm::Buffer* ExternalCdmClient::AllocateBuffer(size_t size) +{ + return new ExternalCdmHeapBuffer(size); +} + +void ExternalCdmClient::OnPromiseResolved(uint32_t promiseId) +{ + callOnMainThread([protectedThis = Ref { *this }, promiseId] { + if (protectedThis->m_instance) + protectedThis->m_instance->settlePromise(promiseId, true); + }); +} + +void ExternalCdmClient::OnPromiseRejected(uint32_t promiseId, uint32_t systemCode, const std::string& message) +{ + String reason = String::fromUTF8(unsafeMakeSpan(message.data(), message.size())); + callOnMainThread([protectedThis = Ref { *this }, promiseId, systemCode, reason = reason.isolatedCopy()] { + LOG(EME, "EME - ExternalCDM - promise %u rejected, system code %u: %s", promiseId, systemCode, reason.utf8().data()); + if (protectedThis->m_instance) + protectedThis->m_instance->settlePromise(promiseId, false); + }); +} + CDMInstanceExternalCDM::CDMInstanceExternalCDM(const String& keySystem) : CDMInstanceProxy(keySystem) , m_keySystem(keySystem) { } +CDMInstanceExternalCDM::~CDMInstanceExternalCDM() +{ + if (m_adapter) + m_adapter->RemoveClient(); + if (m_client) + m_client->clearInstance(); + for (auto& callback : std::exchange(m_pendingPromises, { }).values()) + callback(false); +} + +uint32_t CDMInstanceExternalCDM::registerPromise(PromiseCallback&& callback) +{ + ASSERT(isMainThread()); + uint32_t promiseId = nextPromiseId(); + m_pendingPromises.add(promiseId, WTF::move(callback)); + return promiseId; +} + +void CDMInstanceExternalCDM::settlePromise(uint32_t promiseId, bool succeeded) +{ + ASSERT(isMainThread()); + if (auto callback = m_pendingPromises.take(promiseId)) + callback(succeeded); +} + +bool CDMInstanceExternalCDM::isWidevine() const +{ + return GStreamerEMEUtilities::isWidevineKeySystem(m_keySystem); +} + +std::shared_ptr CDMInstanceExternalCDM::ensureAdapter() +{ + ASSERT(isMainThread()); + + if (m_adapter) + return m_adapter; + if (m_adapterLoadFailed) + return nullptr; + + String base = m_storageDirectory.isEmpty() ? String("/tmp/wpe-cdm"_s) : m_storageDirectory; + if (!base.endsWith('/')) + base = makeString(base, '/'); + FileSystem::makeAllDirectories(base); + + m_client = ExternalCdmClient::create(*this); + + auto adapter = std::make_shared( + std::string(m_keySystem.utf8().data()), + std::string(widevineBlobPath().utf8().data()), + std::string(base.utf8().data()), + media::CdmConfig(false, m_allowPersistentState, false), + m_client.get()); + + if (!adapter->LoadCDM() || !adapter->Initialize()) { + LOG(EME, "EME - ExternalCDM - failed to load or initialise the Widevine CDM"); + adapter->RemoveClient(); + m_client = nullptr; + m_adapterLoadFailed = true; + return nullptr; + } + + m_adapter = WTF::move(adapter); + if (RefPtr cdmProxy = proxy()) + static_cast(*cdmProxy).setAdapter(m_adapter); + return m_adapter; +} + void CDMInstanceExternalCDM::initializeWithConfiguration(const CDMKeySystemConfiguration&, AllowDistinctiveIdentifiers distinctiveIdentifiers, AllowPersistentState persistentState, SuccessCallback&& callback) { + if (isWidevine()) { + m_allowPersistentState = persistentState == AllowPersistentState::Yes; + callback(CDMInstanceSuccessValue::Succeeded); + return; + } + SuccessValue succeeded = (distinctiveIdentifiers == AllowDistinctiveIdentifiers::No && persistentState == AllowPersistentState::No) ? CDMInstanceSuccessValue::Succeeded : CDMInstanceSuccessValue::Failed; callback(succeeded); } -void CDMInstanceExternalCDM::setServerCertificate(Ref&&, SuccessCallback&& callback) +void CDMInstanceExternalCDM::setServerCertificate(Ref&& certificate, SuccessCallback&& callback) { - callback(CDMInstanceSuccessValue::Failed); + if (!isWidevine()) { + callback(CDMInstanceSuccessValue::Failed); + return; + } + + auto adapter = ensureAdapter(); + if (!adapter) { + callback(CDMInstanceSuccessValue::Failed); + return; + } + + auto data = certificate->extractData(); + uint32_t promiseId = registerPromise([callback = WTF::move(callback)](bool succeeded) mutable { + callback(succeeded ? CDMInstanceSuccessValue::Succeeded : CDMInstanceSuccessValue::Failed); + }); + adapter->SetServerCertificate(promiseId, data.span().data(), static_cast(data.size())); } -void CDMInstanceExternalCDM::setStorageDirectory(const String&) +void CDMInstanceExternalCDM::setStorageDirectory(const String& storageDirectory) { + m_storageDirectory = storageDirectory; + if (!storageDirectory.isEmpty()) + FileSystem::makeAllDirectories(storageDirectory); } RefPtr CDMInstanceExternalCDM::createSession() @@ -239,6 +449,84 @@ RefPtr CDMInstanceEx return adoptRef(new CDMInstanceSessionExternalCDM(*this)); } +void CDMInstanceExternalCDM::registerPendingSession(CDMInstanceSessionExternalCDM& session) +{ + ASSERT(isMainThread()); + m_pendingSessions.append(WeakPtr { session }); +} + +void CDMInstanceExternalCDM::unregisterSession(const String& sessionID) +{ + ASSERT(isMainThread()); + if (!sessionID.isEmpty()) + m_sessionsByID.remove(sessionID); +} + +RefPtr CDMInstanceExternalCDM::sessionForID(const String& sessionID) +{ + auto it = m_sessionsByID.find(sessionID); + if (it == m_sessionsByID.end()) + return nullptr; + RefPtr base = it->value.get(); + if (!base) { + m_sessionsByID.remove(it); + return nullptr; + } + return static_cast(base.get()); +} + +RefPtr CDMInstanceExternalCDM::takePendingSession() +{ + while (!m_pendingSessions.isEmpty()) { + RefPtr base = m_pendingSessions.takeFirst().get(); + if (base) + return static_cast(base.get()); + } + return nullptr; +} + +void CDMInstanceExternalCDM::dispatchCdmMessage(const String& sessionID, media::CdmAdapterClient::CDMADPMSG msg, Ref&& data) +{ + ASSERT(isMainThread()); + + RefPtr session = sessionForID(sessionID); + if (!session) { + session = takePendingSession(); + if (session) { + session->setCdmSessionID(sessionID); + m_sessionsByID.set(sessionID, WeakPtr { *session }); + } + } + + if (!session) { + LOG(EME, "EME - ExternalCDM - message for unknown session %s dropped", sessionID.utf8().data()); + return; + } + + session->onCdmMessage(msg, WTF::move(data)); +} + +void CDMInstanceExternalCDM::dispatchKeyStatusChange(const String& sessionID, Vector&& keys) +{ + ASSERT(isMainThread()); + + RefPtr session = sessionForID(sessionID); + if (!session) { + session = takePendingSession(); + if (session) { + session->setCdmSessionID(sessionID); + m_sessionsByID.set(sessionID, WeakPtr { *session }); + } + } + + if (!session) { + LOG(EME, "EME - ExternalCDM - key status change for unknown session %s dropped", sessionID.utf8().data()); + return; + } + + session->onKeyStatusChange(keys); +} + CDMInstanceSessionExternalCDM::CDMInstanceSessionExternalCDM(CDMInstanceExternalCDM& instance) : CDMInstanceSessionProxy(instance) { @@ -249,7 +537,65 @@ CDMInstanceExternalCDM* CDMInstanceSessi return dynamicDowncast(cdmInstanceProxy().get()); } -void CDMInstanceSessionExternalCDM::requestLicense(LicenseType, KeyGroupingStrategy, const String& initDataType, Ref&& initData, LicenseCallback&& callback) +void CDMInstanceSessionExternalCDM::setCdmSessionID(const String& sessionID) +{ + ASSERT(isMainThread()); + m_sessionID = sessionID; + if (RefPtr instance = cdmInstanceExternalCDM()) { + if (auto adapter = instance->ensureAdapter()) + m_externalSession = adoptInBoxPtr(new ExternalCdmSession(adapter, sessionID)); + } +} + +void CDMInstanceSessionExternalCDM::onCdmMessage(media::CdmAdapterClient::CDMADPMSG msg, Ref&& data) +{ + ASSERT(isMainThread()); + + if (msg != media::CdmAdapterClient::kSessionMessage) + return; + + if (m_challengeCallback) { + m_challengeCallback(WTF::move(data), m_sessionID, false, Succeeded); + return; + } + + if (m_client) + m_client->sendMessage(CDMMessageType::LicenseRenewal, WTF::move(data)); +} + +void CDMInstanceSessionExternalCDM::onKeyStatusChange(const Vector& keys) +{ + ASSERT(isMainThread()); + + RefPtr instance = cdmInstanceExternalCDM(); + if (!instance) + return; + + bool keysChanged = false; + for (const auto& key : keys) { + KeyIDType keyID; + keyID.append(unsafeMakeSpan(key.kid.data(), key.kid.size())); + keysChanged |= m_keyStore.add(KeyHandle::create(keyStatusFromCdm(key.status), WTF::move(keyID), BoxPtr(m_externalSession))); + } + + if (keysChanged) + instance->mergeKeysFrom(m_keyStore); + + if (m_client) + m_client->updateKeyStatuses(m_keyStore.convertToJSKeyStatusVector()); +} + +void CDMInstanceSessionExternalCDM::requestLicense(LicenseType licenseType, KeyGroupingStrategy, const String& initDataType, Ref&& initData, LicenseCallback&& callback) +{ + RefPtr instance = cdmInstanceExternalCDM(); + if (instance && instance->isWidevine()) { + requestLicenseWidevine(licenseType, initDataType, WTF::move(initData), WTF::move(callback)); + return; + } + requestLicenseClearKey(initDataType, WTF::move(initData), WTF::move(callback)); +} + +void CDMInstanceSessionExternalCDM::requestLicenseClearKey(const String& initDataType, Ref&& initData, LicenseCallback&& callback) { if (RefPtr instance = cdmInstanceExternalCDM()) m_sessionID = String::number(instance->getNextSessionIdValue()); @@ -270,8 +616,56 @@ void CDMInstanceSessionExternalCDM::requ }); } +void CDMInstanceSessionExternalCDM::requestLicenseWidevine(LicenseType licenseType, const String& initDataType, Ref&& initData, LicenseCallback&& callback) +{ + ASSERT(isMainThread()); + + RefPtr instance = cdmInstanceExternalCDM(); + auto adapter = instance ? instance->ensureAdapter() : nullptr; + if (!adapter) { + callback(WTF::move(initData), emptyString(), false, Failed); + return; + } + + cdm::InitDataType cdmInitDataType = cdm::InitDataType::kCenc; + if (equalLettersIgnoringASCIICase(initDataType, "keyids"_s)) + cdmInitDataType = cdm::InitDataType::kKeyIds; + else if (equalLettersIgnoringASCIICase(initDataType, "webm"_s)) + cdmInitDataType = cdm::InitDataType::kWebM; + + cdm::SessionType cdmSessionType = (licenseType == LicenseType::PersistentLicense) + ? cdm::SessionType::kPersistentLicense : cdm::SessionType::kTemporary; + + m_challengeCallback = WTF::move(callback); + instance->registerPendingSession(*this); + + // The CDM delivers the challenge through a session message on success, so + // only a rejection needs handling here: without it generateRequest() hangs + // until the player times out. The session id is learned from the message. + uint32_t promiseId = instance->registerPromise([weakThis = WeakPtr { *this }](bool succeeded) mutable { + RefPtr protectedThis = weakThis.get(); + if (!protectedThis || succeeded) + return; + if (auto callback = std::exchange(protectedThis->m_challengeCallback, { })) + callback(SharedBuffer::create(), emptyString(), false, Failed); + }); + + auto data = initData->extractData(); + adapter->CreateSessionAndGenerateRequest(promiseId, cdmSessionType, cdmInitDataType, data.span().data(), static_cast(data.size())); +} + void CDMInstanceSessionExternalCDM::updateLicense(const String& sessionID, LicenseType, Ref&& response, LicenseUpdateCallback&& callback) { + RefPtr instance = cdmInstanceExternalCDM(); + if (instance && instance->isWidevine()) { + updateLicenseWidevine(sessionID, WTF::move(response), WTF::move(callback)); + return; + } + updateLicenseClearKey(sessionID, WTF::move(response), WTF::move(callback)); +} + +void CDMInstanceSessionExternalCDM::updateLicenseClearKey(const String& sessionID, Ref&& response, LicenseUpdateCallback&& callback) +{ auto dispatchCallback = [weakThis = WeakPtr { *this }, &callback](bool sessionWasClosed, std::optional&& changedKeys, SuccessValue succeeded) { callOnMainThread( @@ -320,6 +714,28 @@ void CDMInstanceSessionExternalCDM::upda dispatchCallback(false, std::nullopt, SuccessValue::Failed); } +void CDMInstanceSessionExternalCDM::updateLicenseWidevine(const String& sessionID, Ref&& response, LicenseUpdateCallback&& callback) +{ + ASSERT(isMainThread()); + ASSERT_UNUSED(sessionID, sessionID == m_sessionID); + + RefPtr instance = cdmInstanceExternalCDM(); + auto adapter = instance ? instance->ensureAdapter() : nullptr; + if (!adapter || m_sessionID.isEmpty()) { + callback(false, std::nullopt, std::nullopt, std::nullopt, Failed); + return; + } + + // Key statuses arrive separately, through onKeyStatusChange(). + uint32_t promiseId = instance->registerPromise([callback = WTF::move(callback)](bool succeeded) mutable { + callback(false, std::nullopt, std::nullopt, std::nullopt, succeeded ? Succeeded : Failed); + }); + + auto data = response->extractData(); + auto sid = m_sessionID.utf8(); + adapter->UpdateSession(promiseId, sid.data(), sid.length(), data.span().data(), static_cast(data.size())); +} + void CDMInstanceSessionExternalCDM::loadSession(LicenseType, const String& sessionID, const String&, LoadSessionCallback&& callback) { ASSERT_UNUSED(sessionID, sessionID == m_sessionID); @@ -333,6 +749,25 @@ void CDMInstanceSessionExternalCDM::load void CDMInstanceSessionExternalCDM::closeSession(const String&, CloseSessionCallback&& callback) { + RefPtr instance = cdmInstanceExternalCDM(); + if (instance && instance->isWidevine()) { + auto adapter = instance->ensureAdapter(); + bool isClosingInCdm = adapter && !m_sessionID.isEmpty(); + if (isClosingInCdm) { + uint32_t promiseId = instance->registerPromise([callback = WTF::move(callback)](bool) mutable { + callback(); + }); + auto sid = m_sessionID.utf8(); + adapter->CloseSession(promiseId, sid.data(), sid.length()); + } + instance->unrefAllKeysFrom(m_keyStore); + instance->unregisterSession(m_sessionID); + m_keyStore.clear(); + m_externalSession = BoxPtr(); + if (isClosingInCdm) + return; + } + callOnMainThread([weakThis = WeakPtr { *this }, callback = WTF::move(callback)]() mutable { if (!weakThis) return; @@ -368,9 +803,24 @@ void CDMInstanceSessionExternalCDM::remo message = SharedBuffer::create(messageString.utf8().span()); } - if (RefPtr instance = cdmInstanceExternalCDM()) + RefPtr instance = cdmInstanceExternalCDM(); + std::shared_ptr adapter; + if (instance && instance->isWidevine()) + adapter = instance->ensureAdapter(); + + if (instance) instance->unrefAllKeysFrom(m_keyStore); m_keyStore.clear(); + + if (adapter && !m_sessionID.isEmpty()) { + uint32_t promiseId = instance->registerPromise([callback = WTF::move(callback), keyStatusVector = WTF::move(keyStatusVector), message = WTF::move(message)](bool succeeded) mutable { + callback(WTF::move(keyStatusVector), WTF::move(message), succeeded ? SuccessValue::Succeeded : SuccessValue::Failed); + }); + auto sid = m_sessionID.utf8(); + adapter->RemoveSession(promiseId, sid.data(), sid.length()); + return; + } + dispatchCallback(WTF::move(keyStatusVector), Ref(*message), SuccessValue::Succeeded); } --- a/Source/WebCore/platform/graphics/gstreamer/eme/CDMExternalCDM.h +++ b/Source/WebCore/platform/graphics/gstreamer/eme/CDMExternalCDM.h @@ -34,12 +34,23 @@ #include "CDMInstanceSession.h" #include "CDMPrivate.h" #include "CDMProxy.h" +#include "CDMProxyExternalCDM.h" #include "SharedBuffer.h" +#include "cdm/media/cdm/cdm_adapter.h" +#include +#include +#include +#include +#include #include #include +#include namespace WebCore { +class CDMInstanceExternalCDM; +class CDMInstanceSessionExternalCDM; + class CDMFactoryExternalCDM final : public CDMFactory, public CDMProxyFactory { WTF_MAKE_TZONE_ALLOCATED(CDMFactoryExternalCDM); public: @@ -86,17 +97,45 @@ public: std::optional sanitizeSessionId(const String&) const final; private: + bool isWidevine() const; String m_keySystem; }; +// Routes the Chromium CDM's host callbacks, which arrive on CDM-owned threads, +// onto the main thread and into the owning instance. Kept alive by the posted +// tasks so a late callback after the instance is gone is a safe no-op. +class ExternalCdmClient final : public media::CdmAdapterClient, public ThreadSafeRefCounted { +public: + static Ref create(CDMInstanceExternalCDM& instance) + { + return adoptRef(*new ExternalCdmClient(instance)); + } + + // Main thread only. + void clearInstance(); + + void OnCDMMessage(const char* session, uint32_t session_size, CDMADPMSG, const uint8_t* data, size_t data_size, uint32_t status) final; + void OnKeyStatusChange(const std::string& sessionId, const std::vector&) final; + cdm::Buffer* AllocateBuffer(size_t) final; + void OnPromiseResolved(uint32_t promiseId) final; + void OnPromiseRejected(uint32_t promiseId, uint32_t systemCode, const std::string& message) final; + +private: + explicit ExternalCdmClient(CDMInstanceExternalCDM&); + // Read only on the main thread; set at construction and cleared by the + // instance destructor after the adapter's client has been removed. + CDMInstanceExternalCDM* m_instance { nullptr }; +}; + class CDMInstanceExternalCDM final : public CDMInstanceProxy { WTF_MAKE_TZONE_ALLOCATED(CDMInstanceExternalCDM); WTF_OVERRIDE_DELETE_FOR_CHECKED_PTR(CDMInstanceExternalCDM); public: CDMInstanceExternalCDM(const String& keySystem); - virtual ~CDMInstanceExternalCDM() = default; + virtual ~CDMInstanceExternalCDM(); uint32_t getNextSessionIdValue() { return m_nextSessionIdValue++; } + bool isWidevine() const; // CDMInstance ImplementationType implementationType() const final { return ImplementationType::ExternalCDM; } @@ -106,9 +145,36 @@ public: const String& keySystem() const LIFETIME_BOUND final { return m_keySystem; } RefPtr createSession() final; + // Widevine path, all main thread. + std::shared_ptr ensureAdapter(); + uint32_t nextPromiseId() { return m_nextPromiseId++; } + using PromiseCallback = CompletionHandler; + uint32_t registerPromise(PromiseCallback&&); + void settlePromise(uint32_t promiseId, bool succeeded); + void registerPendingSession(CDMInstanceSessionExternalCDM&); + void unregisterSession(const String& sessionID); + + // Called on the main thread by ExternalCdmClient. + void dispatchCdmMessage(const String& sessionID, media::CdmAdapterClient::CDMADPMSG, Ref&&); + void dispatchKeyStatusChange(const String& sessionID, Vector&&); + private: + RefPtr sessionForID(const String& sessionID); + RefPtr takePendingSession(); + String m_keySystem; uint32_t m_nextSessionIdValue { 0 }; + + std::shared_ptr m_adapter; + RefPtr m_client; + String m_storageDirectory; + bool m_adapterLoadFailed { false }; + bool m_allowPersistentState { true }; + uint32_t m_nextPromiseId { 1 }; + HashMap m_pendingPromises; + + HashMap> m_sessionsByID; + Deque> m_pendingSessions; }; class CDMInstanceSessionExternalCDM final : public CDMInstanceSessionProxy { @@ -122,11 +188,30 @@ public: void removeSessionData(const String&, LicenseType, RemoveSessionDataCallback&&) final; void storeRecordOfKeyUsage(const String&) final; + void setClient(WeakPtr&& client) final { m_client = WTF::move(client); } + void clearClient() final { m_client.clear(); } + + // Main thread, driven by the instance for the Widevine path. + void setCdmSessionID(const String& sessionID); + const String& cdmSessionID() const LIFETIME_BOUND { return m_sessionID; } + void onCdmMessage(media::CdmAdapterClient::CDMADPMSG, Ref&&); + void onKeyStatusChange(const Vector&); + private: CDMInstanceExternalCDM* cdmInstanceExternalCDM() const; + void requestLicenseClearKey(const String& initDataType, Ref&& initData, LicenseCallback&&); + void requestLicenseWidevine(LicenseType, const String& initDataType, Ref&& initData, LicenseCallback&&); + void updateLicenseClearKey(const String&, Ref&&, LicenseUpdateCallback&&); + void updateLicenseWidevine(const String&, Ref&&, LicenseUpdateCallback&&); + String m_sessionID; KeyStore m_keyStore; + + // Widevine only. + BoxPtr m_externalSession; + LicenseCallback m_challengeCallback; + WeakPtr m_client; }; } // namespace WebCore --- a/Source/WebCore/platform/graphics/gstreamer/eme/CDMProxyExternalCDM.cpp +++ b/Source/WebCore/platform/graphics/gstreamer/eme/CDMProxyExternalCDM.cpp @@ -33,10 +33,16 @@ #include "CDMClearKey.h" #include "Logging.h" +#include #include namespace WebCore { +void externalCdmSessionDestroy(ExternalCdmSession* session) +{ + delete session; +} + static bool readUInt32(const uint8_t* buffer, size_t bufferSize, size_t& offset, uint32_t& value) { ASSERT_ARG(offset, offset <= bufferSize); @@ -149,21 +155,9 @@ bool CDMProxyExternalCDM::cencDecryptSub return true; } -DecryptionResult CDMProxyExternalCDM::cencDecrypt(CDMProxyExternalCDM::cencDecryptContext& input) +DecryptionResult CDMProxyExternalCDM::clearKeyDecrypt(cencDecryptContext& input, const Vector& keyValue) { - Vector keyIDVec; - keyIDVec.append(unsafeMakeSpan(input.keyID, input.keyIDSizeInBytes)); - - auto keyData = getOrWaitForKeyValue(keyIDVec, WTF::move(input.cdmProxyDecryptionClient)); - if (!keyData) { - LOG(EME, "EME - CDMProxyExternalCDM - key unavailable or operation aborted"); - return DecryptionResult::NoKey; - } - - ASSERT(std::holds_alternative>(keyData.value())); - auto& keyDataValue = std::get>(keyData.value()); - - if (gcry_error_t cipherError = gcry_cipher_setkey(gCryptHandle(), keyDataValue.span().data(), keyDataValue.size())) { + if (gcry_error_t cipherError = gcry_cipher_setkey(gCryptHandle(), keyValue.span().data(), keyValue.size())) { #if !LOG_DISABLED LOG(EME, "EME - CDMProxyExternalCDM - ERROR(gcry_cipher_setkey): %s", gpg_strerror(cipherError)); #else @@ -179,6 +173,215 @@ DecryptionResult CDMProxyExternalCDM::ce return success ? DecryptionResult::Success : DecryptionResult::Failure; } +namespace { + +class HeapDecryptedBlock final : public cdm::DecryptedBlock { +public: + void SetDecryptedBuffer(cdm::Buffer* buffer) override { m_buffer = buffer; } + cdm::Buffer* DecryptedBuffer() override { return m_buffer; } + void SetTimestamp(int64_t timestamp) override { m_timestamp = timestamp; } + int64_t Timestamp() const override { return m_timestamp; } + +private: + cdm::Buffer* m_buffer { nullptr }; + int64_t m_timestamp { 0 }; +}; + +} // namespace + +bool CDMProxyExternalCDM::externalCdmInputFromContext(const cencDecryptContext& input, ExternalCdmInput& cdmInput) +{ + // cbcs restarts the CBC chain and the pattern in every subsample, so the + // CDM gets the whole sample with its subsample table, as Chromium passes it. + size_t coveredBytes = 0; + + if (input.isSubsampled()) { + size_t subOffset = 0; + for (size_t i = 0; i < input.numSubsamples; ++i) { + uint16_t clearBytes = 0; + uint32_t cipherBytes = 0; + if (!readUInt16(input.subsamplesBuffer, input.subsamplesBufferSizeInBytes, subOffset, clearBytes) + || !readUInt32(input.subsamplesBuffer, input.subsamplesBufferSizeInBytes, subOffset, cipherBytes)) { + LOG(EME, "EME - CDMProxyExternalCDM - could not read subsample %zu", i); + return false; + } + cdmInput.subsamples.append(cdm::SubsampleEntry { clearBytes, cipherBytes }); + coveredBytes += clearBytes + static_cast(cipherBytes); + cdmInput.cipherBytes += cipherBytes; + } + } else { + cdmInput.subsamples.append(cdm::SubsampleEntry { 0, static_cast(input.encryptedBufferSizeInBytes) }); + coveredBytes = cdmInput.cipherBytes = input.encryptedBufferSizeInBytes; + } + + if (coveredBytes > input.encryptedBufferSizeInBytes) { + LOG(EME, "EME - CDMProxyExternalCDM - subsample layout overruns the buffer"); + return false; + } + if (coveredBytes < input.encryptedBufferSizeInBytes) + cdmInput.subsamples.append(cdm::SubsampleEntry { static_cast(input.encryptedBufferSizeInBytes - coveredBytes), 0 }); + + memset(cdmInput.iv, 0, sizeof(cdmInput.iv)); + memcpy(cdmInput.iv, input.iv, std::min(input.ivSizeInBytes, sizeof(cdmInput.iv))); + + cdmInput.buffer.data = input.encryptedBuffer; + cdmInput.buffer.data_size = static_cast(input.encryptedBufferSizeInBytes); + cdmInput.buffer.encryption_scheme = input.encryptionScheme; + cdmInput.buffer.key_id = input.keyID; + cdmInput.buffer.key_id_size = static_cast(input.keyIDSizeInBytes); + cdmInput.buffer.iv = cdmInput.iv; + cdmInput.buffer.iv_size = sizeof(cdmInput.iv); + cdmInput.buffer.subsamples = cdmInput.subsamples.span().data(); + cdmInput.buffer.num_subsamples = static_cast(cdmInput.subsamples.size()); + cdmInput.buffer.pattern = { input.cryptByteBlock, input.skipByteBlock }; + cdmInput.buffer.timestamp = input.timestampUs; + return true; +} + +DecryptionResult CDMProxyExternalCDM::externalCdmDecrypt(cencDecryptContext& input, const BoxPtr& session) +{ + if (!session || !session->get() || !session->get()->adapter()) { + LOG(EME, "EME - CDMProxyExternalCDM - no CDM session to decrypt against"); + return DecryptionResult::Failure; + } + media::CdmAdapter* cdm = session->get()->adapter(); + + ExternalCdmInput cdmInput; + if (!externalCdmInputFromContext(input, cdmInput)) + return DecryptionResult::Failure; + + if (!cdmInput.cipherBytes) + return DecryptionResult::Success; + + ExternalCdmHeapBuffer output(input.encryptedBufferSizeInBytes); + HeapDecryptedBlock block; + block.SetDecryptedBuffer(&output); + + cdm::Status status = cdm->Decrypt(cdmInput.buffer, &block); + if (status != cdm::kSuccess) { + LOG(EME, "EME - CDMProxyExternalCDM - CDM Decrypt failed, status %d", static_cast(status)); + return status == cdm::kNoKey ? DecryptionResult::NoKey : DecryptionResult::Failure; + } + + if (output.Size() < input.encryptedBufferSizeInBytes) { + LOG(EME, "EME - CDMProxyExternalCDM - CDM returned %u bytes, expected %zu", output.Size(), input.encryptedBufferSizeInBytes); + return DecryptionResult::Failure; + } + + memcpy(input.encryptedBuffer, output.Data(), input.encryptedBufferSizeInBytes); + return DecryptionResult::Success; +} + +DecryptionResult CDMProxyExternalCDM::cencDecrypt(CDMProxyExternalCDM::cencDecryptContext& input) +{ + Vector keyIDVec; + keyIDVec.append(unsafeMakeSpan(input.keyID, input.keyIDSizeInBytes)); + + auto keyHandle = getOrWaitForKeyHandle(keyIDVec, WTF::move(input.cdmProxyDecryptionClient)); + if (!keyHandle) { + LOG(EME, "EME - CDMProxyExternalCDM - key unavailable or operation aborted"); + return DecryptionResult::NoKey; + } + + const KeyHandleValueVariant& keyValue = keyHandle.value()->value(); + + if (std::holds_alternative>(keyValue)) + return clearKeyDecrypt(input, std::get>(keyValue)); + + if (std::holds_alternative>(keyValue)) { + if (!keyHandle.value()->isStatusCurrentlyValid()) { + LOG(EME, "EME - CDMProxyExternalCDM - key present but not usable"); + return DecryptionResult::NoKey; + } + return externalCdmDecrypt(input, std::get>(keyValue)); + } + + LOG(EME, "EME - CDMProxyExternalCDM - key handle carries no usable value"); + return DecryptionResult::Failure; +} + +void CDMProxyExternalCDM::setAdapter(std::shared_ptr adapter) +{ + Locker locker { m_adapterLock }; + m_adapter = WTF::move(adapter); + m_adapterCondition.notifyAll(); +} + +void CDMProxyExternalCDM::abortWaitingForAdapter() +{ + Locker locker { m_adapterLock }; + m_adapterCondition.notifyAll(); +} + +std::shared_ptr CDMProxyExternalCDM::getOrWaitForVideoAdapter(const cencDecryptContext& input, DecryptionResult& result) +{ + result = DecryptionResult::Failure; + + if (!input.isEncrypted()) { + Locker locker { m_adapterLock }; + m_adapterCondition.waitFor(m_adapterLock, CDMProxy::MaxKeyWaitTimeSeconds, [this, &input] { + assertIsHeld(m_adapterLock); + CheckedPtr client = input.cdmProxyDecryptionClient.get(); + return m_adapter || !client || client->isAborting(); + }); + if (!m_adapter) { + LOG(EME, "EME - CDMProxyExternalCDM - no CDM loaded to decode clear video with"); + return nullptr; + } + result = DecryptionResult::Success; + return m_adapter; + } + + Vector keyIDVec; + keyIDVec.append(unsafeMakeSpan(input.keyID, input.keyIDSizeInBytes)); + + WeakPtr client = input.cdmProxyDecryptionClient; + auto keyHandle = getOrWaitForKeyHandle(keyIDVec, WTF::move(client)); + if (!keyHandle) { + LOG(EME, "EME - CDMProxyExternalCDM - key unavailable or operation aborted"); + result = DecryptionResult::NoKey; + return nullptr; + } + + const KeyHandleValueVariant& keyValue = keyHandle.value()->value(); + if (!std::holds_alternative>(keyValue)) { + LOG(EME, "EME - CDMProxyExternalCDM - key is not held by a CDM that decodes video"); + return nullptr; + } + + if (!keyHandle.value()->isStatusCurrentlyValid()) { + LOG(EME, "EME - CDMProxyExternalCDM - key present but not usable"); + result = DecryptionResult::NoKey; + return nullptr; + } + + const auto& session = std::get>(keyValue); + if (!session || !session->get() || !session->get()->sharedAdapter()) { + LOG(EME, "EME - CDMProxyExternalCDM - no CDM session to decode against"); + return nullptr; + } + + result = DecryptionResult::Success; + return session->get()->sharedAdapter(); +} + +cdm::Status CDMProxyExternalCDM::decryptAndDecodeVideoFrame(media::CdmAdapter& adapter, const cencDecryptContext* input, media::CdmVideoFrame& frame) +{ + ExternalCdmInput cdmInput; + + if (input && input->isEncrypted()) { + if (!externalCdmInputFromContext(*input, cdmInput)) + return cdm::kDecryptError; + } else if (input) { + cdmInput.buffer.data = input->encryptedBuffer; + cdmInput.buffer.data_size = static_cast(input->encryptedBufferSizeInBytes); + cdmInput.buffer.encryption_scheme = cdm::EncryptionScheme::kUnencrypted; + cdmInput.buffer.timestamp = input->timestampUs; + } + + return adapter.DecryptAndDecodeFrame(cdmInput.buffer, &frame); +} + void CDMProxyExternalCDM::closeGCryptHandle() { if (m_gCryptHandle) { --- a/Source/WebCore/platform/graphics/gstreamer/eme/CDMProxyExternalCDM.h +++ b/Source/WebCore/platform/graphics/gstreamer/eme/CDMProxyExternalCDM.h @@ -33,14 +33,68 @@ #include "CDMProxy.h" #include "GStreamerEMEUtilities.h" #include "SharedBuffer.h" +#include "cdm/media/cdm/cdm_adapter.h" +#include #include +#include +#include +#include +#include namespace WebCore { -// Thread-safe API that decode threads use to drive the external CDM. For the -// ClearKey key system the decryption runs in-process against the keys the -// CDMInstanceSession placed in the shared key store; blob-backed key systems -// are a later iteration. +// Heap-backed cdm::Buffer, as proven by the de-risk harness: a thin owner over +// a plain allocation the CDM fills, used for the decrypt output and as the +// fallback the CDM host Allocate() falls back to. +class ExternalCdmHeapBuffer final : public cdm::Buffer { +public: + explicit ExternalCdmHeapBuffer(size_t capacity) + : m_capacity(capacity) + { + m_data = capacity ? static_cast(malloc(capacity)) : nullptr; + } + + ~ExternalCdmHeapBuffer() override { free(m_data); } + + void Destroy() override { delete this; } + uint32_t Capacity() const override { return static_cast(m_capacity); } + uint8_t* Data() override { return m_data; } + void SetSize(uint32_t size) override { m_size = size; } + uint32_t Size() const override { return m_size; } + +private: + uint8_t* m_data { nullptr }; + size_t m_capacity { 0 }; + uint32_t m_size { 0 }; +}; + +// Handle to one session inside an external Chromium CDM, boxed into the shared +// key store as the value of a KeyHandle (the Widevine analogue of Thunder's +// BoxPtr). The key bytes stay in the CDM; this carries a strong +// reference to the CdmAdapter so a decode thread can call Decrypt() by key id +// after the JS-facing instance is gone, plus the CDM's session id string. +class ExternalCdmSession { +public: + ExternalCdmSession(std::shared_ptr adapter, const String& sessionID) + : m_adapter(WTF::move(adapter)) + , m_sessionID(sessionID) + { + } + + media::CdmAdapter* adapter() const { return m_adapter.get(); } + const std::shared_ptr& sharedAdapter() const LIFETIME_BOUND { return m_adapter; } + const String& sessionID() const LIFETIME_BOUND { return m_sessionID; } + +private: + std::shared_ptr m_adapter; + String m_sessionID; +}; + +// Thread-safe API that decode threads use to drive the external CDM. ClearKey +// decrypts in process with libgcrypt against the raw key bytes the session +// placed in the shared key store; Widevine (and other blob-backed key systems) +// decrypt through the Chromium CDM, which the session reaches by the boxed +// ExternalCdmSession handle. The decryptor dispatches on the key-handle variant. class CDMProxyExternalCDM final : public CDMProxy, public CanMakeWeakPtr { public: CDMProxyExternalCDM(const String& keySystem) @@ -62,21 +116,52 @@ public: size_t numSubsamples; WeakPtr cdmProxyDecryptionClient; + // Only consulted by blob-backed key systems; ClearKey is always AES-CTR. + cdm::EncryptionScheme encryptionScheme { cdm::EncryptionScheme::kCenc }; + uint32_t cryptByteBlock { 0 }; + uint32_t skipByteBlock { 0 }; + int64_t timestampUs { 0 }; + bool isSubsampled() const { return numSubsamples; } + bool isEncrypted() const { return keyID; } }; DecryptionResult cencDecrypt(cencDecryptContext&); const String& keySystem() const LIFETIME_BOUND { return m_keySystem; } + void setAdapter(std::shared_ptr); + + // The Widevine CDM does not hand decrypted video back to the host. + std::shared_ptr getOrWaitForVideoAdapter(const cencDecryptContext&, DecryptionResult&); + static cdm::Status decryptAndDecodeVideoFrame(media::CdmAdapter&, const cencDecryptContext*, media::CdmVideoFrame&); + void abortWaitingForAdapter(); + private: + struct ExternalCdmInput { + Vector subsamples; + uint8_t iv[16]; + cdm::InputBuffer_2 buffer { }; + size_t cipherBytes { 0 }; + }; + static bool externalCdmInputFromContext(const cencDecryptContext&, ExternalCdmInput&); + + // ClearKey, libgcrypt in process. gcry_cipher_hd_t& gCryptHandle(); bool cencSetCounterVector(const cencDecryptContext&); bool cencDecryptFullSample(cencDecryptContext&); bool cencDecryptSubsampled(cencDecryptContext&); + DecryptionResult clearKeyDecrypt(cencDecryptContext&, const Vector& keyValue); void closeGCryptHandle(); + // Blob-backed key systems, Chromium CDM. + DecryptionResult externalCdmDecrypt(cencDecryptContext&, const BoxPtr&); + String m_keySystem; std::optional m_gCryptHandle { std::nullopt }; + + Lock m_adapterLock; + Condition m_adapterCondition; + std::shared_ptr m_adapter WTF_GUARDED_BY_LOCK(m_adapterLock); }; } // namespace WebCore --- a/Source/WebCore/platform/graphics/gstreamer/eme/WebKitCommonEncryptionDecryptorGStreamer.cpp +++ b/Source/WebCore/platform/graphics/gstreamer/eme/WebKitCommonEncryptionDecryptorGStreamer.cpp @@ -76,8 +76,6 @@ struct _WebKitMediaCommonEncryptionDecry enum DecryptionState decryptionState { DecryptionState::Idle }; }; -static constexpr Seconds MaxSecondsToWaitForCDMProxy = 5_s; - static void constructed(GObject*); static GstStateChangeReturn changeState(GstElement*, GstStateChange transition); static GstCaps* transformCaps(GstBaseTransform*, GstPadDirection, GstCaps*, GstCaps*); @@ -127,6 +125,15 @@ static void constructed(GObject* object) priv->cdmProxyDecryptionClientImplementation = makeUnique(self); } +void webKitMediaCommonEncryptionStripProtection(GstStructure* structure) +{ + if (auto originalMediaType = WebCore::gstStructureGetString(structure, "original-media-type"_s)) + gst_structure_set_name(structure, originalMediaType.utf8()); + + // Filter out the DRM related fields from the down-stream caps. + gst_structure_remove_fields(structure, "protection-system", "original-media-type", "encryption-algorithm", "encoding-scope", "cipher-mode", nullptr); +} + static GstCaps* transformCaps(GstBaseTransform* base, GstPadDirection direction, GstCaps* caps, GstCaps* filter) { if (direction == GST_PAD_UNKNOWN) @@ -157,15 +164,7 @@ static GstCaps* transformCaps(GstBaseTra } outgoingStructure = GUniquePtr(gst_structure_copy(incomingStructure)); - - if (!canDoPassthrough) { - auto originalMediaType = WebCore::gstStructureGetString(outgoingStructure.get(), "original-media-type"_s); - RELEASE_ASSERT(originalMediaType); - gst_structure_set_name(outgoingStructure.get(), originalMediaType.utf8()); - } - - // Filter out the DRM related fields from the down-stream caps. - gst_structure_remove_fields(outgoingStructure.get(), "protection-system", "original-media-type", "encryption-algorithm", "encoding-scope", "cipher-mode", nullptr); + webKitMediaCommonEncryptionStripProtection(outgoingStructure.get()); } else { outgoingStructure = GUniquePtr(gst_structure_copy(incomingStructure)); if (!gst_base_transform_is_passthrough(base)) { @@ -232,60 +231,16 @@ static gboolean acceptCaps(GstBaseTransf return result; } -static GstFlowReturn transformInPlace(GstBaseTransform* base, GstBuffer* buffer) +bool webKitMediaCommonEncryptionParseProtectionMeta(GstElement* element, GstProtectionMeta* protectionMeta, WebKitMediaCommonEncryptionSampleInfo& sampleInfo) { - WebKitMediaCommonEncryptionDecrypt* self = WEBKIT_MEDIA_CENC_DECRYPT(base); - WebKitMediaCommonEncryptionDecryptPrivate* priv = WEBKIT_MEDIA_CENC_DECRYPT_GET_PRIVATE(self); - - GstProtectionMeta* protectionMeta = reinterpret_cast(gst_buffer_get_protection_meta(buffer)); - if (!protectionMeta) { - GST_TRACE_OBJECT(self, "Buffer %p does not contain protection meta, not decrypting", buffer); - return GST_FLOW_OK; - } - - Locker locker { priv->lock }; - - if (priv->isFlushing) { - GST_DEBUG_OBJECT(self, "Decryption aborted because of flush"); - return GST_FLOW_FLUSHING; - } - - // The CDM instance needs to be negotiated before we can begin decryption. - if (!priv->cdmProxy) { - GST_DEBUG_OBJECT(self, "CDM not available, going to wait for it"); - priv->condition.waitFor(priv->lock, MaxSecondsToWaitForCDMProxy, [priv] { - return priv->isFlushing || priv->cdmProxy || priv->isStopped; - }); - // Note that waitFor() releases the lock internally while it waits, so isFlushing may have been changed. - if (priv->isFlushing) { - GST_DEBUG_OBJECT(self, "Decryption aborted because of flush"); - return GST_FLOW_FLUSHING; - } - if (priv->isStopped) { - GST_DEBUG_OBJECT(self, "Element is closing"); - return GST_FLOW_OK; - } - if (!priv->cdmProxy) { - GST_ELEMENT_ERROR(self, STREAM, FAILED, ("CDMProxy was not retrieved in time"), (nullptr)); - return GST_FLOW_NOT_SUPPORTED; - } - GST_DEBUG_OBJECT(self, "CDM now available with address %p", priv->cdmProxy.get()); - } - - // We are still going to be locked when running this, so no need to lock here. - auto scopeExit = makeScopeExit([buffer, protectionMeta, priv] { - gst_buffer_remove_meta(buffer, reinterpret_cast(protectionMeta)); - priv->decryptionState = DecryptionState::Idle; - }); - bool isCbcs = false; if (auto cipherMode = WebCore::gstStructureGetString(protectionMeta->info, "cipher-mode"_s)) isCbcs = WTF::equalIgnoringASCIICase(cipherMode.span(), "cbcs"_s); auto ivSizeFromMeta = WebCore::gstStructureGet(protectionMeta->info, "iv_size"_s); if (!ivSizeFromMeta) { - GST_ELEMENT_ERROR(self, STREAM, FAILED, ("Failed to get iv_size"), (nullptr)); - return GST_FLOW_NOT_SUPPORTED; + GST_ELEMENT_ERROR(element, STREAM, FAILED, ("Failed to get iv_size"), (nullptr)); + return false; } unsigned ivSize; @@ -294,31 +249,32 @@ static GstFlowReturn transformInPlace(Gs else { auto constantIvSize = WebCore::gstStructureGet(protectionMeta->info, "constant_iv_size"_s); if (!constantIvSize) { - GST_ELEMENT_ERROR(self, STREAM, FAILED, ("No iv_size and failed to get constant_iv_size"), (nullptr)); + GST_ELEMENT_ERROR(element, STREAM, FAILED, ("No iv_size and failed to get constant_iv_size"), (nullptr)); ASSERT(isCbcs); - return GST_FLOW_NOT_SUPPORTED; + return false; } ivSize = *constantIvSize; } auto isEncrypted = WebCore::gstStructureGet(protectionMeta->info, "encrypted"_s); if (!isEncrypted) { - GST_ELEMENT_ERROR(self, STREAM, FAILED, ("Failed to get encrypted flag"), (nullptr)); - return GST_FLOW_NOT_SUPPORTED; + GST_ELEMENT_ERROR(element, STREAM, FAILED, ("Failed to get encrypted flag"), (nullptr)); + return false; } if (!ivSize || !*isEncrypted) { - GST_TRACE_OBJECT(self, "iv size %u, encrypted %s, bailing out OK as unencrypted", ivSize, boolForPrinting(*isEncrypted)); - return GST_FLOW_OK; + GST_TRACE_OBJECT(element, "iv size %u, encrypted %s, bailing out OK as unencrypted", ivSize, boolForPrinting(*isEncrypted)); + sampleInfo.isEncrypted = false; + return true; } - GST_DEBUG_OBJECT(base, "protection meta: %" GST_PTR_FORMAT, protectionMeta->info); + GST_DEBUG_OBJECT(element, "protection meta: %" GST_PTR_FORMAT, protectionMeta->info); auto subSampleCountFromMeta = WebCore::gstStructureGet(protectionMeta->info, "subsample_count"_s); // cbcs could not include the subsample_count. if (!subSampleCountFromMeta && !isCbcs) { - GST_ELEMENT_ERROR(self, STREAM, FAILED, ("Failed to get subsample_count"), (nullptr)); - return GST_FLOW_NOT_SUPPORTED; + GST_ELEMENT_ERROR(element, STREAM, FAILED, ("Failed to get subsample_count"), (nullptr)); + return false; } const GValue* value; @@ -327,31 +283,91 @@ static GstFlowReturn transformInPlace(Gs if (subSampleCount) { value = gst_structure_get_value(protectionMeta->info, "subsamples"); if (!value) { - GST_ELEMENT_ERROR(self, STREAM, FAILED, ("Failed to get subsamples"), (nullptr)); - return GST_FLOW_NOT_SUPPORTED; + GST_ELEMENT_ERROR(element, STREAM, FAILED, ("Failed to get subsamples"), (nullptr)); + return false; } subSamplesBuffer = gst_value_get_buffer(value); if (!subSamplesBuffer) { - GST_ELEMENT_ERROR(self, STREAM, FAILED, ("There is no subsamples buffer, but a positive subsample count"), (nullptr)); - return GST_FLOW_NOT_SUPPORTED; + GST_ELEMENT_ERROR(element, STREAM, FAILED, ("There is no subsamples buffer, but a positive subsample count"), (nullptr)); + return false; } } value = gst_structure_get_value(protectionMeta->info, "kid"); if (!value) { - GST_ELEMENT_ERROR(self, STREAM, FAILED, ("Failed to get key id for buffer"), (nullptr)); - return GST_FLOW_NOT_SUPPORTED; + GST_ELEMENT_ERROR(element, STREAM, FAILED, ("Failed to get key id for buffer"), (nullptr)); + return false; } GstBuffer* keyIDBuffer = gst_value_get_buffer(value); value = gst_structure_get_value(protectionMeta->info, "iv"); if (!value) { - GST_ELEMENT_ERROR(self, STREAM, FAILED, ("Failed to get IV for sample"), (nullptr)); - return GST_FLOW_NOT_SUPPORTED; + GST_ELEMENT_ERROR(element, STREAM, FAILED, ("Failed to get IV for sample"), (nullptr)); + return false; + } + + sampleInfo.ivBuffer = gst_value_get_buffer(value); + sampleInfo.keyIDBuffer = keyIDBuffer; + sampleInfo.subsamplesBuffer = subSamplesBuffer; + sampleInfo.subsampleCount = subSampleCount; + sampleInfo.isEncrypted = true; + return true; +} + +static GstFlowReturn transformInPlace(GstBaseTransform* base, GstBuffer* buffer) +{ + WebKitMediaCommonEncryptionDecrypt* self = WEBKIT_MEDIA_CENC_DECRYPT(base); + WebKitMediaCommonEncryptionDecryptPrivate* priv = WEBKIT_MEDIA_CENC_DECRYPT_GET_PRIVATE(self); + + GstProtectionMeta* protectionMeta = reinterpret_cast(gst_buffer_get_protection_meta(buffer)); + if (!protectionMeta) { + GST_TRACE_OBJECT(self, "Buffer %p does not contain protection meta, not decrypting", buffer); + return GST_FLOW_OK; + } + + Locker locker { priv->lock }; + + if (priv->isFlushing) { + GST_DEBUG_OBJECT(self, "Decryption aborted because of flush"); + return GST_FLOW_FLUSHING; + } + + // The CDM instance needs to be negotiated before we can begin decryption. + if (!priv->cdmProxy) { + GST_DEBUG_OBJECT(self, "CDM not available, going to wait for it"); + priv->condition.waitFor(priv->lock, webKitMediaCommonEncryptionMaxSecondsToWaitForCDMProxy, [priv] { + return priv->isFlushing || priv->cdmProxy || priv->isStopped; + }); + // Note that waitFor() releases the lock internally while it waits, so isFlushing may have been changed. + if (priv->isFlushing) { + GST_DEBUG_OBJECT(self, "Decryption aborted because of flush"); + return GST_FLOW_FLUSHING; + } + if (priv->isStopped) { + GST_DEBUG_OBJECT(self, "Element is closing"); + return GST_FLOW_OK; + } + if (!priv->cdmProxy) { + GST_ELEMENT_ERROR(self, STREAM, FAILED, ("CDMProxy was not retrieved in time"), (nullptr)); + return GST_FLOW_NOT_SUPPORTED; + } + GST_DEBUG_OBJECT(self, "CDM now available with address %p", priv->cdmProxy.get()); } - GstBuffer* ivBuffer = gst_value_get_buffer(value); + // We are still going to be locked when running this, so no need to lock here. + auto scopeExit = makeScopeExit([buffer, protectionMeta, priv] { + gst_buffer_remove_meta(buffer, reinterpret_cast(protectionMeta)); + priv->decryptionState = DecryptionState::Idle; + }); + + WebKitMediaCommonEncryptionSampleInfo sampleInfo; + if (!webKitMediaCommonEncryptionParseProtectionMeta(GST_ELEMENT_CAST(self), protectionMeta, sampleInfo)) + return GST_FLOW_NOT_SUPPORTED; + + if (!sampleInfo.isEncrypted) + return GST_FLOW_OK; + WebKitMediaCommonEncryptionDecryptClass* klass = WEBKIT_MEDIA_CENC_DECRYPT_GET_CLASS(self); ASSERT(priv->decryptionState == DecryptionState::Idle); @@ -366,7 +382,7 @@ static GstFlowReturn transformInPlace(Gs // in order to avoid creating several scopes with different Locker instances in each one. { DropLockForScope noLockScope { locker }; - result = klass->decrypt(self, ivBuffer, keyIDBuffer, buffer, subSampleCount, subSamplesBuffer); + result = klass->decrypt(self, sampleInfo.ivBuffer, sampleInfo.keyIDBuffer, buffer, sampleInfo.subsampleCount, sampleInfo.subsamplesBuffer); } // Accessing priv members again. @@ -398,9 +414,9 @@ static bool isCDMProxyAvailable(WebKitMe return priv->cdmProxy; } -static CDMProxy* getCDMProxyFromGstContext(WebKitMediaCommonEncryptionDecrypt* self) +CDMProxy* webKitMediaCommonEncryptionCDMProxyFromContext(GstElement* self) { - GRefPtr context = adoptGRef(gst_element_get_context(GST_ELEMENT(self), "drm-cdm-proxy")); + GRefPtr context = adoptGRef(gst_element_get_context(self, "drm-cdm-proxy")); CDMProxy* proxy = nullptr; // According to the GStreamer documentation, if we can't find the context, we should run a downstream query, then an upstream one and then send a bus @@ -439,7 +455,7 @@ static gboolean installCDMProxyIfNotAvai if (!isCDMProxyAvailable(self)) { gboolean result = FALSE; - CDMProxy* proxy = getCDMProxyFromGstContext(self); + CDMProxy* proxy = webKitMediaCommonEncryptionCDMProxyFromContext(GST_ELEMENT_CAST(self)); if (proxy) { attachCDMProxy(self, proxy); result = TRUE; --- a/Source/WebCore/platform/graphics/gstreamer/eme/WebKitCommonEncryptionDecryptorGStreamer.h +++ b/Source/WebCore/platform/graphics/gstreamer/eme/WebKitCommonEncryptionDecryptorGStreamer.h @@ -30,6 +30,7 @@ #include #include #include +#include #include G_BEGIN_DECLS @@ -70,4 +71,18 @@ G_END_DECLS // area. WeakPtr webKitMediaCommonEncryptionDecryptGetCDMProxyDecryptionClient(WebKitMediaCommonEncryptionDecrypt*); +static constexpr Seconds webKitMediaCommonEncryptionMaxSecondsToWaitForCDMProxy = 5_s; + +struct WebKitMediaCommonEncryptionSampleInfo { + GstBuffer* ivBuffer { nullptr }; + GstBuffer* keyIDBuffer { nullptr }; + GstBuffer* subsamplesBuffer { nullptr }; + unsigned subsampleCount { 0 }; + bool isEncrypted { false }; +}; + +WebCore::CDMProxy* webKitMediaCommonEncryptionCDMProxyFromContext(GstElement*); +void webKitMediaCommonEncryptionStripProtection(GstStructure*); +bool webKitMediaCommonEncryptionParseProtectionMeta(GstElement*, GstProtectionMeta*, WebKitMediaCommonEncryptionSampleInfo&); + #endif // ENABLE(ENCRYPTED_MEDIA) && USE(GSTREAMER) --- a/Source/WebCore/platform/graphics/gstreamer/eme/WebKitExternalCDMDecryptorGStreamer.cpp +++ b/Source/WebCore/platform/graphics/gstreamer/eme/WebKitExternalCDMDecryptorGStreamer.cpp @@ -146,45 +146,73 @@ static DecryptionResult decrypt(WebKitMe return DecryptionResult::Failure; } - GstMappedBuffer mappedIVBuffer(ivBuffer, GST_MAP_READ); - if (!mappedIVBuffer) { - GST_ERROR_OBJECT(self, "failed to map IV buffer"); + WebKitMediaCommonEncryptionSampleInfo sampleInfo { ivBuffer, keyIDBuffer, subsamplesBuffer, subsampleCount, true }; + WebKitExternalCDMDecryptInput input; + // The base class strips the protection meta only after decrypt() returns. + if (!input.map(GST_ELEMENT_CAST(self), buffer, GST_MAP_READWRITE, sampleInfo)) return DecryptionResult::Failure; + input.context.cdmProxyDecryptionClient = webKitMediaCommonEncryptionDecryptGetCDMProxyDecryptionClient(decryptor); + + return priv->cdmProxy->cencDecrypt(input.context); +} + +bool WebKitExternalCDMDecryptInput::map(GstElement* element, GstBuffer* sample, GstMapFlags flags, const WebKitMediaCommonEncryptionSampleInfo& sampleInfo) +{ + m_sample.emplace(sample, flags); + if (!*m_sample) { + GST_ERROR_OBJECT(element, "failed to map data buffer"); + return false; } + context.encryptedBuffer = m_sample->data(); + context.encryptedBufferSizeInBytes = m_sample->size(); - GstMappedBuffer mappedKeyIdBuffer(keyIDBuffer, GST_MAP_READ); - if (!mappedKeyIdBuffer) { - GST_ERROR_OBJECT(self, "failed to map key ID buffer"); - return DecryptionResult::Failure; + if (GST_BUFFER_PTS_IS_VALID(sample)) + context.timestampUs = GST_TIME_AS_USECONDS(GST_BUFFER_PTS(sample)); + + if (!sampleInfo.isEncrypted) + return true; + + m_iv.emplace(sampleInfo.ivBuffer, GST_MAP_READ); + if (!*m_iv) { + GST_ERROR_OBJECT(element, "failed to map IV buffer"); + return false; } - GstMappedBuffer mappedBuffer(buffer, GST_MAP_READWRITE); - if (!mappedBuffer) { - GST_ERROR_OBJECT(self, "failed to map data buffer"); - return DecryptionResult::Failure; + m_keyID.emplace(sampleInfo.keyIDBuffer, GST_MAP_READ); + if (!*m_keyID) { + GST_ERROR_OBJECT(element, "failed to map key ID buffer"); + return false; } - std::optional mappedSubsamplesBuffer; - CDMProxyExternalCDM::cencDecryptContext context = { }; - context.keyID = mappedKeyIdBuffer.data(); - context.keyIDSizeInBytes = mappedKeyIdBuffer.size(); - context.iv = mappedIVBuffer.data(); - context.ivSizeInBytes = mappedIVBuffer.size(); - context.encryptedBuffer = mappedBuffer.data(); - context.encryptedBufferSizeInBytes = mappedBuffer.size(); - context.numSubsamples = subsampleCount; - if (subsampleCount) { - mappedSubsamplesBuffer.emplace(subsamplesBuffer, GST_MAP_READ); - if (!*mappedSubsamplesBuffer) { - GST_ERROR_OBJECT(self, "failed to map subsamples buffer"); - return DecryptionResult::Failure; + context.keyID = m_keyID->data(); + context.keyIDSizeInBytes = m_keyID->size(); + context.iv = m_iv->data(); + context.ivSizeInBytes = m_iv->size(); + context.numSubsamples = sampleInfo.subsampleCount; + if (sampleInfo.subsampleCount) { + m_subsamples.emplace(sampleInfo.subsamplesBuffer, GST_MAP_READ); + if (!*m_subsamples) { + GST_ERROR_OBJECT(element, "failed to map subsamples buffer"); + return false; } - context.subsamplesBuffer = mappedSubsamplesBuffer->data(); - context.subsamplesBufferSizeInBytes = mappedSubsamplesBuffer->size(); + context.subsamplesBuffer = m_subsamples->data(); + context.subsamplesBufferSizeInBytes = m_subsamples->size(); } - context.cdmProxyDecryptionClient = webKitMediaCommonEncryptionDecryptGetCDMProxyDecryptionClient(decryptor); - return priv->cdmProxy->cencDecrypt(context); + context.encryptionScheme = cdm::EncryptionScheme::kCenc; + if (auto* protectionMeta = reinterpret_cast(gst_buffer_get_protection_meta(sample))) { + if (const char* cipherMode = gst_structure_get_string(protectionMeta->info, "cipher-mode")) { + if (!g_ascii_strcasecmp(cipherMode, "cbcs")) + context.encryptionScheme = cdm::EncryptionScheme::kCbcs; + } + unsigned cryptByteBlock = 0; + unsigned skipByteBlock = 0; + if (gst_structure_get_uint(protectionMeta->info, "crypt_byte_block", &cryptByteBlock)) + context.cryptByteBlock = cryptByteBlock; + if (gst_structure_get_uint(protectionMeta->info, "skip_byte_block", &skipByteBlock)) + context.skipByteBlock = skipByteBlock; + } + return true; } #undef GST_CAT_DEFAULT --- a/Source/WebCore/platform/graphics/gstreamer/eme/WebKitExternalCDMDecryptorGStreamer.h +++ b/Source/WebCore/platform/graphics/gstreamer/eme/WebKitExternalCDMDecryptorGStreamer.h @@ -23,7 +23,23 @@ #if ENABLE(ENCRYPTED_MEDIA) && ENABLE(EXTERNAL_CDM) && USE(GSTREAMER) +#include "CDMProxyExternalCDM.h" +#include "GStreamerCommon.h" #include "WebKitCommonEncryptionDecryptorGStreamer.h" +#include + +class WebKitExternalCDMDecryptInput { +public: + bool map(GstElement*, GstBuffer* sample, GstMapFlags, const WebKitMediaCommonEncryptionSampleInfo&); + + WebCore::CDMProxyExternalCDM::cencDecryptContext context { }; + +private: + std::optional m_iv; + std::optional m_keyID; + std::optional m_sample; + std::optional m_subsamples; +}; G_BEGIN_DECLS --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/eme/WebKitExternalCDMVideoDecoderGStreamer.cpp @@ -0,0 +1,808 @@ +/* GStreamer video decoder running inside an external CDM + * + * Copyright (C) 2026 Daniel Golle + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "config.h" +#include "WebKitExternalCDMVideoDecoderGStreamer.h" + +#if ENABLE(ENCRYPTED_MEDIA) && ENABLE(EXTERNAL_CDM) && USE(GSTREAMER) + +#include "CDMProxyExternalCDM.h" +#include "GStreamerCommon.h" +#include "GStreamerEMEUtilities.h" +#include "WebKitCommonEncryptionDecryptorGStreamer.h" +#include "WebKitExternalCDMDecryptorGStreamer.h" +#include +#include +#include +#include +#include + +using namespace WebCore; + +GST_DEBUG_CATEGORY_STATIC(webkitExternalCDMVideoDecoderDebugCategory); +#define GST_CAT_DEFAULT webkitExternalCDMVideoDecoderDebugCategory + +static constexpr std::array s_decodableMediaTypes = { "video/x-av1"_s, "video/x-vp9"_s, "video/x-vp8"_s, "video/x-h264"_s }; +static constexpr std::array s_annexBStartCode = { 0, 0, 0, 1 }; + +static GstStaticPadTemplate externalCDMVideoDecoderSrcTemplate = GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS, + GST_STATIC_CAPS(GST_VIDEO_CAPS_MAKE("{ I420, I420_10LE, I420_12LE, I422_10LE, I422_12LE, Y444_10LE, Y444_12LE }"))); + +class ExternalCDMVideoDecoderClient final : public CDMProxyDecryptionClient { + WTF_MAKE_TZONE_ALLOCATED_INLINE(ExternalCDMVideoDecoderClient); + WTF_OVERRIDE_DELETE_FOR_CHECKED_PTR(ExternalCDMVideoDecoderClient); +public: + explicit ExternalCDMVideoDecoderClient(WebKitExternalCDMVideoDecoder* decoder) + : m_decoder(decoder) + { + } + + bool isAborting() final; + +private: + WebKitExternalCDMVideoDecoder* m_decoder; +}; + +struct CdmBufferDestroyer { + void operator()(cdm::Buffer* buffer) const { buffer->Destroy(); } +}; +using CdmBufferPtr = std::unique_ptr; + +struct WebKitExternalCDMVideoDecoderPrivate { + Lock lock; + Condition condition; + RefPtr cdmProxy; + bool isFlushing { false }; + bool isStopped { true }; + std::unique_ptr client; + + std::shared_ptr adapter; + bool isDecoderInitialized { false }; + cdm::VideoDecoderConfig_3 config { }; + cdm::EncryptionScheme encryptionScheme { cdm::EncryptionScheme::kCenc }; + bool isAvc { false }; + Vector h264ParameterSets; + GstVideoCodecState* inputState { nullptr }; + bool canAttachVideoMeta { false }; +}; + +WEBKIT_DEFINE_TYPE(WebKitExternalCDMVideoDecoder, webkit_external_cdm_video_decoder, GST_TYPE_VIDEO_DECODER) + +bool ExternalCDMVideoDecoderClient::isAborting() +{ + auto* priv = m_decoder->priv; + Locker locker { priv->lock }; + return priv->isFlushing || priv->isStopped; +} + +static GRefPtr createSinkPadTemplateCaps() +{ + GRefPtr caps = adoptGRef(gst_caps_new_empty()); + + for (const auto& mediaType : s_decodableMediaTypes) { + gst_caps_append_structure(caps.get(), gst_structure_new("application/x-cenc", "original-media-type", G_TYPE_STRING, mediaType.characters(), + "protection-system", G_TYPE_STRING, GStreamerEMEUtilities::s_WidevineUUID.characters(), nullptr)); + } + + return caps; +} + +static GstVideoFormat videoFormatFromCdm(cdm::VideoFormat format) +{ + switch (format) { + case cdm::kYv12: + case cdm::kI420: + return GST_VIDEO_FORMAT_I420; + case cdm::kYUV420P10: + return GST_VIDEO_FORMAT_I420_10LE; + case cdm::kYUV420P12: + return GST_VIDEO_FORMAT_I420_12LE; + case cdm::kYUV422P10: + return GST_VIDEO_FORMAT_I422_10LE; + case cdm::kYUV422P12: + return GST_VIDEO_FORMAT_I422_12LE; + case cdm::kYUV444P10: + return GST_VIDEO_FORMAT_Y444_10LE; + case cdm::kYUV444P12: + return GST_VIDEO_FORMAT_Y444_12LE; + default: + return GST_VIDEO_FORMAT_UNKNOWN; + } +} + +static cdm::VideoFormat videoFormatFromCaps(const GstStructure* structure) +{ + auto bitDepth = gstStructureGet(structure, "bit-depth-luma"_s).value_or(8); + auto chromaFormat = gstStructureGetString(structure, "chroma-format"_s); + bool is444 = chromaFormat == "4:4:4"_s; + bool is422 = chromaFormat == "4:2:2"_s; + + if (bitDepth == 10) + return is444 ? cdm::kYUV444P10 : is422 ? cdm::kYUV422P10 : cdm::kYUV420P10; + if (bitDepth == 12) + return is444 ? cdm::kYUV444P12 : is422 ? cdm::kYUV422P12 : cdm::kYUV420P12; + return cdm::kI420; +} + +static bool videoCodecFromCaps(const GstStructure* structure, cdm::VideoDecoderConfig_3& config) +{ + auto mediaType = gstStructureGetName(structure); + auto profile = gstStructureGetString(structure, "profile"_s); + + if (mediaType == "video/x-av1"_s) { + config.codec = cdm::kCodecAv1; + config.profile = cdm::kAv1ProfileMain; + if (profile == "high"_s) + config.profile = cdm::kAv1ProfileHigh; + else if (profile == "professional"_s) + config.profile = cdm::kAv1ProfilePro; + return true; + } + + if (mediaType == "video/x-vp9"_s) { + config.codec = cdm::kCodecVp9; + config.profile = cdm::kVP9Profile0; + if (profile == "1"_s) + config.profile = cdm::kVP9Profile1; + else if (profile == "2"_s) + config.profile = cdm::kVP9Profile2; + else if (profile == "3"_s) + config.profile = cdm::kVP9Profile3; + return true; + } + + if (mediaType == "video/x-h264"_s) { + config.codec = cdm::kCodecH264; + config.profile = cdm::kH264ProfileBaseline; + if (profile == "main"_s) + config.profile = cdm::kH264ProfileMain; + else if (profile == "extended"_s) + config.profile = cdm::kH264ProfileExtended; + else if (profile == "high"_s) + config.profile = cdm::kH264ProfileHigh; + else if (profile == "high-10"_s) + config.profile = cdm::kH264ProfileHigh10; + else if (profile == "high-4:2:2"_s) + config.profile = cdm::kH264ProfileHigh422; + else if (profile == "high-4:4:4"_s) + config.profile = cdm::kH264ProfileHigh444Predictive; + return true; + } + + if (mediaType == "video/x-vp8"_s) { + config.codec = cdm::kCodecVp8; + config.profile = cdm::kProfileNotNeeded; + return true; + } + + return false; +} + +// The CDM takes H.264 as Annex B with the parameter sets in band, the form Chromium hands it. +static bool h264ParameterSetsFromCodecData(const GstStructure* structure, Vector& parameterSets) +{ + const GValue* value = gst_structure_get_value(structure, "codec_data"); + GstBuffer* codecData = value ? gst_value_get_buffer(value) : nullptr; + if (!codecData) + return false; + + GstMappedBuffer mappedCodecData(codecData, GST_MAP_READ); + if (!mappedCodecData || mappedCodecData.size() < 6) + return false; + + auto data = unsafeMakeSpan(mappedCodecData.data(), mappedCodecData.size()); + if ((data[4] & 3) != 3) + return false; + + size_t offset = 5; + for (unsigned isPictureParameterSet = 0; isPictureParameterSet < 2; ++isPictureParameterSet) { + if (offset >= data.size()) + return false; + unsigned count = isPictureParameterSet ? data[offset] : data[offset] & 0x1f; + offset++; + for (unsigned i = 0; i < count; ++i) { + if (data.size() - offset < 2) + return false; + size_t length = (data[offset] << 8) | data[offset + 1]; + offset += 2; + if (data.size() - offset < length) + return false; + parameterSets.append(std::span { s_annexBStartCode }); + parameterSets.append(data.subspan(offset, length)); + offset += length; + } + } + return true; +} + +static bool h264SampleToAnnexB(WebKitExternalCDMVideoDecoder* self, bool isKeyFrame, CDMProxyExternalCDM::cencDecryptContext& context, + Vector& sample, Vector& subsamples) +{ + auto* priv = self->priv; + size_t prefixSize = isKeyFrame ? priv->h264ParameterSets.size() : 0; + + // The NAL lengths are rewritten in place, so they have to be in the clear. + if (context.isEncrypted() && !context.isSubsampled()) + return false; + + sample.reserveInitialCapacity(prefixSize + context.encryptedBufferSizeInBytes); + if (prefixSize) + sample.append(priv->h264ParameterSets.span()); + sample.append(unsafeMakeSpan(context.encryptedBuffer, context.encryptedBufferSizeInBytes)); + + for (size_t offset = prefixSize; offset < sample.size();) { + if (sample.size() - offset < s_annexBStartCode.size()) + return false; + size_t length = (static_cast(sample[offset]) << 24) | (sample[offset + 1] << 16) | (sample[offset + 2] << 8) | sample[offset + 3]; + memcpySpan(sample.mutableSpan().subspan(offset, s_annexBStartCode.size()), std::span { s_annexBStartCode }); + offset += s_annexBStartCode.size(); + if (sample.size() - offset < length) + return false; + offset += length; + } + + context.encryptedBuffer = sample.mutableSpan().data(); + context.encryptedBufferSizeInBytes = sample.size(); + if (!prefixSize || !context.isEncrypted()) + return true; + + subsamples.append(unsafeMakeSpan(context.subsamplesBuffer, context.subsamplesBufferSizeInBytes)); + if (subsamples.size() < 2) + return false; + size_t clearBytes = ((subsamples[0] << 8) | subsamples[1]) + prefixSize; + if (clearBytes > std::numeric_limits::max()) + return false; + subsamples[0] = clearBytes >> 8; + subsamples[1] = clearBytes & 0xff; + context.subsamplesBuffer = subsamples.span().data(); + return true; +} + +static void abortWaits(WebKitExternalCDMVideoDecoder* self) +{ + auto* priv = self->priv; + RefPtr cdmProxy; + + { + Locker locker { priv->lock }; + priv->condition.notifyAll(); + cdmProxy = priv->cdmProxy; + } + + if (!cdmProxy) + return; + cdmProxy->abortWaitingForKey(); + cdmProxy->abortWaitingForAdapter(); +} + +static void attachCDMProxyFromContext(WebKitExternalCDMVideoDecoder* self) +{ + auto* priv = self->priv; + CDMProxy* cdmProxy = webKitMediaCommonEncryptionCDMProxyFromContext(GST_ELEMENT_CAST(self)); + if (!cdmProxy) + return; + + Locker locker { priv->lock }; + GST_DEBUG_OBJECT(self, "attaching CDMProxy %p", cdmProxy); + priv->cdmProxy = static_cast(cdmProxy); + priv->condition.notifyAll(); +} + +static RefPtr waitForCDMProxy(WebKitExternalCDMVideoDecoder* self) +{ + auto* priv = self->priv; + + { + Locker locker { priv->lock }; + if (priv->cdmProxy) + return priv->cdmProxy; + } + + attachCDMProxyFromContext(self); + + Locker locker { priv->lock }; + priv->condition.waitFor(priv->lock, webKitMediaCommonEncryptionMaxSecondsToWaitForCDMProxy, [priv] { + return priv->cdmProxy || priv->isFlushing || priv->isStopped; + }); + return priv->cdmProxy; +} + +static void deinitializeDecoder(WebKitExternalCDMVideoDecoder* self) +{ + auto* priv = self->priv; + + if (priv->adapter && priv->isDecoderInitialized) + priv->adapter->DeinitializeDecoder(cdm::kStreamTypeVideo); + priv->isDecoderInitialized = false; +} + +static bool ensureDecoder(WebKitExternalCDMVideoDecoder* self, std::shared_ptr&& adapter) +{ + auto* priv = self->priv; + + if (priv->adapter == adapter && priv->isDecoderInitialized) + return true; + + deinitializeDecoder(self); + priv->adapter = WTF::move(adapter); + + const auto& config = priv->config; + cdm::Status status = priv->adapter->InitializeVideoDecoder(config); + if (status != cdm::kSuccess) { + GST_ERROR_OBJECT(self, "CDM refused codec %u profile %u format %u %dx%d: status %d", static_cast(config.codec), + static_cast(config.profile), static_cast(config.format), config.coded_size.width, config.coded_size.height, + static_cast(status)); + return false; + } + + GST_DEBUG_OBJECT(self, "CDM decoder initialised for codec %u profile %u format %u %dx%d", static_cast(config.codec), + static_cast(config.profile), static_cast(config.format), config.coded_size.width, config.coded_size.height); + priv->isDecoderInitialized = true; + return true; +} + +static bool ensureOutputState(WebKitExternalCDMVideoDecoder* self, GstVideoFormat format, const cdm::Size& size) +{ + auto* decoder = GST_VIDEO_DECODER(self); + auto* priv = self->priv; + + GstVideoCodecState* current = gst_video_decoder_get_output_state(decoder); + bool matches = current && GST_VIDEO_INFO_FORMAT(¤t->info) == format + && GST_VIDEO_INFO_WIDTH(¤t->info) == size.width && GST_VIDEO_INFO_HEIGHT(¤t->info) == size.height; + if (current) + gst_video_codec_state_unref(current); + if (matches) + return true; + + GST_DEBUG_OBJECT(self, "output %s %dx%d", gst_video_format_to_string(format), size.width, size.height); + gst_video_codec_state_unref(gst_video_decoder_set_output_state(decoder, format, size.width, size.height, priv->inputState)); + return gst_video_decoder_negotiate(decoder); +} + +static GstVideoCodecFrame* takePendingFrame(WebKitExternalCDMVideoDecoder* self, int64_t timestampUs) +{ + auto* decoder = GST_VIDEO_DECODER(self); + GstVideoCodecFrame* match = nullptr; + GList* frames = gst_video_decoder_get_frames(decoder); + + for (GList* item = frames; item; item = item->next) { + auto* frame = static_cast(item->data); + if (GST_CLOCK_TIME_IS_VALID(frame->pts) && static_cast(GST_TIME_AS_USECONDS(frame->pts)) == timestampUs) { + match = gst_video_codec_frame_ref(frame); + break; + } + } + + // Pictures come out in presentation order, so a frame presented before the match produced none. + for (GList* item = frames; match && item; item = item->next) { + auto* frame = static_cast(item->data); + if (frame == match || !GST_CLOCK_TIME_IS_VALID(frame->pts) || frame->pts >= match->pts) + continue; + GST_DEBUG_OBJECT(self, "dropping frame %u, the CDM returned no picture for it", frame->system_frame_number); + gst_video_decoder_drop_frame(decoder, gst_video_codec_frame_ref(frame)); + } + + g_list_free_full(frames, reinterpret_cast(gst_video_codec_frame_unref)); + return match; +} + +static GstBuffer* wrapCdmBuffer(CdmBufferPtr&& cdmBuffer, media::CdmVideoFrame& cdmFrame, GstVideoFormat format) +{ + gsize offsets[GST_VIDEO_MAX_PLANES] = { cdmFrame.PlaneOffset(cdm::kYPlane), cdmFrame.PlaneOffset(cdm::kUPlane), cdmFrame.PlaneOffset(cdm::kVPlane), 0 }; + gint strides[GST_VIDEO_MAX_PLANES] = { + static_cast(cdmFrame.Stride(cdm::kYPlane)), static_cast(cdmFrame.Stride(cdm::kUPlane)), static_cast(cdmFrame.Stride(cdm::kVPlane)), 0 + }; + auto size = cdmFrame.Size(); + uint8_t* data = cdmBuffer->Data(); + uint32_t capacity = cdmBuffer->Capacity(); + + GstBuffer* buffer = gst_buffer_new_wrapped_full(static_cast(0), data, capacity, 0, capacity, cdmBuffer.release(), [](gpointer cdmBuffer) { + static_cast(cdmBuffer)->Destroy(); + }); + gst_buffer_add_video_meta_full(buffer, GST_VIDEO_FRAME_FLAG_NONE, format, size.width, size.height, 3, offsets, strides); + return buffer; +} + +static GstFlowReturn copyToOutputBuffer(WebKitExternalCDMVideoDecoder* self, GstVideoCodecFrame* frame, GstBuffer* source) +{ + auto* decoder = GST_VIDEO_DECODER(self); + GstVideoCodecState* state = gst_video_decoder_get_output_state(decoder); + GstFlowReturn ret = gst_video_decoder_allocate_output_frame(decoder, frame); + GstVideoFrame sourceFrame; + GstVideoFrame outputFrame; + + if (ret != GST_FLOW_OK) { + gst_video_codec_state_unref(state); + return ret; + } + + if (!gst_video_frame_map(&sourceFrame, &state->info, source, GST_MAP_READ)) { + gst_video_codec_state_unref(state); + return GST_FLOW_ERROR; + } + + if (!gst_video_frame_map(&outputFrame, &state->info, frame->output_buffer, GST_MAP_WRITE)) { + gst_video_frame_unmap(&sourceFrame); + gst_video_codec_state_unref(state); + return GST_FLOW_ERROR; + } + + bool copied = gst_video_frame_copy(&outputFrame, &sourceFrame); + gst_video_frame_unmap(&outputFrame); + gst_video_frame_unmap(&sourceFrame); + gst_video_codec_state_unref(state); + return copied ? GST_FLOW_OK : GST_FLOW_ERROR; +} + +static GstFlowReturn pushDecodedFrame(WebKitExternalCDMVideoDecoder* self, media::CdmVideoFrame& cdmFrame, CdmBufferPtr&& cdmBuffer) +{ + auto* decoder = GST_VIDEO_DECODER(self); + GstVideoFormat format = videoFormatFromCdm(cdmFrame.Format()); + auto size = cdmFrame.Size(); + + if (!cdmBuffer || format == GST_VIDEO_FORMAT_UNKNOWN || size.width <= 0 || size.height <= 0) { + GST_ELEMENT_ERROR(self, STREAM, DECODE, ("CDM returned an unusable picture"), ("format %u, %dx%d", static_cast(cdmFrame.Format()), size.width, size.height)); + return GST_FLOW_ERROR; + } + + GstVideoCodecFrame* frame = takePendingFrame(self, cdmFrame.Timestamp()); + if (!frame) { + GST_DEBUG_OBJECT(self, "no pending frame for timestamp %" G_GINT64_FORMAT "us", cdmFrame.Timestamp()); + return GST_FLOW_OK; + } + + if (!ensureOutputState(self, format, size)) { + gst_video_decoder_drop_frame(decoder, frame); + return GST_FLOW_NOT_NEGOTIATED; + } + + GstBuffer* buffer = wrapCdmBuffer(WTF::move(cdmBuffer), cdmFrame, format); + if (self->priv->canAttachVideoMeta) { + frame->output_buffer = buffer; + return gst_video_decoder_finish_frame(decoder, frame); + } + + GstFlowReturn ret = copyToOutputBuffer(self, frame, buffer); + gst_buffer_unref(buffer); + if (ret != GST_FLOW_OK) { + gst_video_decoder_drop_frame(decoder, frame); + return ret; + } + return gst_video_decoder_finish_frame(decoder, frame); +} + +static GstFlowReturn decodeFrame(WebKitExternalCDMVideoDecoder* self, const CDMProxyExternalCDM::cencDecryptContext* input) +{ + auto* priv = self->priv; + media::CdmVideoFrame cdmFrame; + + cdm::Status status = CDMProxyExternalCDM::decryptAndDecodeVideoFrame(*priv->adapter, input, cdmFrame); + CdmBufferPtr cdmBuffer(cdmFrame.FrameBuffer()); + GstFlowReturn ret = GST_FLOW_OK; + + switch (status) { + case cdm::kSuccess: + return pushDecodedFrame(self, cdmFrame, WTF::move(cdmBuffer)); + case cdm::kNeedMoreData: + return GST_FLOW_OK; + case cdm::kNoKey: + GST_ELEMENT_ERROR(self, STREAM, DECRYPT_NOKEY, ("No key found, decryption failed"), (nullptr)); + return GST_FLOW_CUSTOM_ERROR; + default: + GST_VIDEO_DECODER_ERROR(self, 1, STREAM, DECODE, ("CDM failed to decrypt and decode a frame"), ("status %d", static_cast(status)), ret); + return ret; + } +} + +static GstFlowReturn drainOneFrame(WebKitExternalCDMVideoDecoder* self, bool& isDrained) +{ + media::CdmVideoFrame cdmFrame; + + cdm::Status status = CDMProxyExternalCDM::decryptAndDecodeVideoFrame(*self->priv->adapter, nullptr, cdmFrame); + CdmBufferPtr cdmBuffer(cdmFrame.FrameBuffer()); + isDrained = status != cdm::kSuccess; + if (isDrained) + return GST_FLOW_OK; + return pushDecodedFrame(self, cdmFrame, WTF::move(cdmBuffer)); +} + +static GstFlowReturn drainFrames(WebKitExternalCDMVideoDecoder* self) +{ + auto* priv = self->priv; + GstFlowReturn ret = GST_FLOW_OK; + bool isDrained = false; + + if (!priv->adapter || !priv->isDecoderInitialized) + return GST_FLOW_OK; + + while (!isDrained && ret == GST_FLOW_OK) + ret = drainOneFrame(self, isDrained); + return ret; +} + +static GstFlowReturn handleFrame(GstVideoDecoder* decoder, GstVideoCodecFrame* frame) +{ + auto* self = WEBKIT_EXTERNAL_CDM_VIDEO_DECODER(decoder); + auto* priv = self->priv; + + RefPtr cdmProxy = waitForCDMProxy(self); + if (!cdmProxy) { + gst_video_decoder_release_frame(decoder, frame); + if (priv->client->isAborting()) + return GST_FLOW_FLUSHING; + GST_ELEMENT_ERROR(self, STREAM, FAILED, ("CDMProxy was not retrieved in time"), (nullptr)); + return GST_FLOW_NOT_SUPPORTED; + } + + WebKitMediaCommonEncryptionSampleInfo sampleInfo; + if (auto* protectionMeta = reinterpret_cast(gst_buffer_get_protection_meta(frame->input_buffer))) { + if (!webKitMediaCommonEncryptionParseProtectionMeta(GST_ELEMENT_CAST(self), protectionMeta, sampleInfo)) { + gst_video_decoder_release_frame(decoder, frame); + return GST_FLOW_NOT_SUPPORTED; + } + } + + WebKitExternalCDMDecryptInput input; + if (!input.map(GST_ELEMENT_CAST(self), frame->input_buffer, GST_MAP_READ, sampleInfo)) { + gst_video_decoder_release_frame(decoder, frame); + GST_ELEMENT_ERROR(self, STREAM, DECODE, ("Failed to map the input frame"), (nullptr)); + return GST_FLOW_ERROR; + } + input.context.cdmProxyDecryptionClient = WeakPtr { *priv->client, EnableWeakPtrThreadingAssertions::No }; + + Vector annexBSample; + Vector annexBSubsamples; + if (priv->isAvc && !h264SampleToAnnexB(self, GST_VIDEO_CODEC_FRAME_IS_SYNC_POINT(frame), input.context, annexBSample, annexBSubsamples)) { + GstFlowReturn ret = GST_FLOW_OK; + gst_video_decoder_drop_frame(decoder, frame); + GST_VIDEO_DECODER_ERROR(self, 1, STREAM, DECODE, ("Malformed H.264 sample"), (nullptr), ret); + return ret; + } + + DecryptionResult result; + auto adapter = cdmProxy->getOrWaitForVideoAdapter(input.context, result); + if (!adapter) { + gst_video_decoder_release_frame(decoder, frame); + if (priv->client->isAborting()) + return GST_FLOW_FLUSHING; + if (result == DecryptionResult::NoKey) { + GST_ELEMENT_ERROR(self, STREAM, DECRYPT_NOKEY, ("No key found, decryption failed"), (nullptr)); + return GST_FLOW_CUSTOM_ERROR; + } + GST_ELEMENT_ERROR(self, STREAM, DECRYPT, ("No CDM to decode the frame with"), (nullptr)); + return GST_FLOW_ERROR; + } + + if (!ensureDecoder(self, WTF::move(adapter))) { + gst_video_decoder_release_frame(decoder, frame); + GST_ELEMENT_ERROR(self, STREAM, CODEC_NOT_FOUND, ("The CDM cannot decode this stream"), (nullptr)); + return GST_FLOW_NOT_NEGOTIATED; + } + + gst_video_codec_frame_unref(frame); + return decodeFrame(self, &input.context); +} + +static gboolean setFormat(GstVideoDecoder* decoder, GstVideoCodecState* state) +{ + auto* self = WEBKIT_EXTERNAL_CDM_VIDEO_DECODER(decoder); + auto* priv = self->priv; + auto* structure = gst_caps_get_structure(state->caps, 0); + cdm::VideoDecoderConfig_3 config { }; + + if (!videoCodecFromCaps(structure, config)) { + GST_WARNING_OBJECT(self, "cannot decode %" GST_PTR_FORMAT, state->caps); + return FALSE; + } + + config.format = videoFormatFromCaps(structure); + config.color_space = { 2, 2, 2, cdm::ColorRange::kInvalid }; + config.coded_size = { gstStructureGet(structure, "width"_s).value_or(0), gstStructureGet(structure, "height"_s).value_or(0) }; + config.encryption_scheme = priv->encryptionScheme; + + Vector h264ParameterSets; + bool isAvc = config.codec == cdm::kCodecH264 && gstStructureGetString(structure, "stream-format"_s) == "avc"_s; + if (isAvc && !h264ParameterSetsFromCodecData(structure, h264ParameterSets)) { + GST_WARNING_OBJECT(self, "unusable avcC in %" GST_PTR_FORMAT, state->caps); + return FALSE; + } + + GST_DEBUG_OBJECT(self, "input %" GST_PTR_FORMAT, state->caps); + + if (priv->isDecoderInitialized) { + drainFrames(self); + deinitializeDecoder(self); + } + + priv->config = config; + priv->isAvc = isAvc; + priv->h264ParameterSets = WTF::move(h264ParameterSets); + if (priv->inputState) + gst_video_codec_state_unref(priv->inputState); + priv->inputState = gst_video_codec_state_ref(state); + return TRUE; +} + +static gboolean decideAllocation(GstVideoDecoder* decoder, GstQuery* query) +{ + auto* self = WEBKIT_EXTERNAL_CDM_VIDEO_DECODER(decoder); + + self->priv->canAttachVideoMeta = gst_query_find_allocation_meta(query, GST_VIDEO_META_API_TYPE, nullptr); + GST_DEBUG_OBJECT(self, "downstream %s video meta", self->priv->canAttachVideoMeta ? "supports" : "does not support"); + return GST_VIDEO_DECODER_CLASS(webkit_external_cdm_video_decoder_parent_class)->decide_allocation(decoder, query); +} + +static gboolean flush(GstVideoDecoder* decoder) +{ + auto* self = WEBKIT_EXTERNAL_CDM_VIDEO_DECODER(decoder); + auto* priv = self->priv; + + if (priv->adapter && priv->isDecoderInitialized) + priv->adapter->ResetDecoder(cdm::kStreamTypeVideo); + return TRUE; +} + +static GstFlowReturn finish(GstVideoDecoder* decoder) +{ + return drainFrames(WEBKIT_EXTERNAL_CDM_VIDEO_DECODER(decoder)); +} + +static gboolean stop(GstVideoDecoder* decoder) +{ + auto* self = WEBKIT_EXTERNAL_CDM_VIDEO_DECODER(decoder); + auto* priv = self->priv; + + deinitializeDecoder(self); + priv->adapter = nullptr; + if (priv->inputState) { + gst_video_codec_state_unref(priv->inputState); + priv->inputState = nullptr; + } + return TRUE; +} + +// GstVideoDecoder only takes video/ or image/ caps, so it gets the media type the encrypted caps carry. +static GstEvent* decodableCapsEvent(WebKitExternalCDMVideoDecoder* self, GstEvent* event) +{ + GstCaps* caps; + gst_event_parse_caps(event, &caps); + + GRefPtr decodableCaps = adoptGRef(gst_caps_copy(caps)); + auto* structure = gst_caps_get_structure(decodableCaps.get(), 0); + auto cipherMode = gstStructureGetString(structure, "cipher-mode"_s); + self->priv->encryptionScheme = cipherMode == "cbcs"_s ? cdm::EncryptionScheme::kCbcs : cdm::EncryptionScheme::kCenc; + webKitMediaCommonEncryptionStripProtection(structure); + + GstEvent* decodableEvent = gst_event_new_caps(decodableCaps.get()); + gst_event_set_seqnum(decodableEvent, gst_event_get_seqnum(event)); + gst_event_unref(event); + return decodableEvent; +} + +static gboolean sinkEvent(GstVideoDecoder* decoder, GstEvent* event) +{ + auto* self = WEBKIT_EXTERNAL_CDM_VIDEO_DECODER(decoder); + auto* priv = self->priv; + + switch (GST_EVENT_TYPE(event)) { + case GST_EVENT_CAPS: + event = decodableCapsEvent(self, event); + break; + case GST_EVENT_CUSTOM_DOWNSTREAM_OOB: + if (gst_event_has_name(event, "attempt-to-decrypt")) { + attachCDMProxyFromContext(self); + gst_event_unref(event); + return TRUE; + } + break; + case GST_EVENT_FLUSH_START: { + { + Locker locker { priv->lock }; + priv->isFlushing = true; + } + abortWaits(self); + break; + } + case GST_EVENT_FLUSH_STOP: { + Locker locker { priv->lock }; + priv->isFlushing = false; + break; + } + default: + break; + } + + return GST_VIDEO_DECODER_CLASS(webkit_external_cdm_video_decoder_parent_class)->sink_event(decoder, event); +} + +static GstStateChangeReturn changeState(GstElement* element, GstStateChange transition) +{ + auto* self = WEBKIT_EXTERNAL_CDM_VIDEO_DECODER(element); + auto* priv = self->priv; + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: { + Locker locker { priv->lock }; + priv->isStopped = false; + break; + } + case GST_STATE_CHANGE_PAUSED_TO_READY: { + { + Locker locker { priv->lock }; + priv->isStopped = true; + } + abortWaits(self); + break; + } + default: + break; + } + + return GST_ELEMENT_CLASS(webkit_external_cdm_video_decoder_parent_class)->change_state(element, transition); +} + +static void setContext(GstElement* element, GstContext* context) +{ + GST_ELEMENT_CLASS(webkit_external_cdm_video_decoder_parent_class)->set_context(element, context); + + if (gst_context_has_context_type(context, "drm-cdm-proxy")) + attachCDMProxyFromContext(WEBKIT_EXTERNAL_CDM_VIDEO_DECODER(element)); +} + +static void constructed(GObject* object) +{ + G_OBJECT_CLASS(webkit_external_cdm_video_decoder_parent_class)->constructed(object); + + auto* self = WEBKIT_EXTERNAL_CDM_VIDEO_DECODER(object); + self->priv->client = makeUnique(self); + gst_video_decoder_set_packetized(GST_VIDEO_DECODER(self), TRUE); + gst_video_decoder_set_needs_format(GST_VIDEO_DECODER(self), TRUE); +} + +static void webkit_external_cdm_video_decoder_class_init(WebKitExternalCDMVideoDecoderClass* klass) +{ + GST_DEBUG_CATEGORY_INIT(webkitExternalCDMVideoDecoderDebugCategory, "webkitexternalcdmvideodec", 0, "external CDM video decoder"); + + G_OBJECT_CLASS(klass)->constructed = constructed; + + GstElementClass* elementClass = GST_ELEMENT_CLASS(klass); + elementClass->change_state = GST_DEBUG_FUNCPTR(changeState); + elementClass->set_context = GST_DEBUG_FUNCPTR(setContext); + + GRefPtr sinkCaps = createSinkPadTemplateCaps(); + gst_element_class_add_pad_template(elementClass, gst_pad_template_new("sink", GST_PAD_SINK, GST_PAD_ALWAYS, sinkCaps.get())); + gst_element_class_add_static_pad_template(elementClass, &externalCDMVideoDecoderSrcTemplate); + gst_element_class_set_static_metadata(elementClass, "Decrypt and decode video in an external CDM", "Codec/Decoder/Video", + "Decrypts and decodes encrypted video inside an external W3C CDM.", "Daniel Golle "); + + GstVideoDecoderClass* decoderClass = GST_VIDEO_DECODER_CLASS(klass); + decoderClass->stop = GST_DEBUG_FUNCPTR(stop); + decoderClass->set_format = GST_DEBUG_FUNCPTR(setFormat); + decoderClass->handle_frame = GST_DEBUG_FUNCPTR(handleFrame); + decoderClass->flush = GST_DEBUG_FUNCPTR(flush); + decoderClass->finish = GST_DEBUG_FUNCPTR(finish); + decoderClass->drain = GST_DEBUG_FUNCPTR(finish); + decoderClass->decide_allocation = GST_DEBUG_FUNCPTR(decideAllocation); + decoderClass->sink_event = GST_DEBUG_FUNCPTR(sinkEvent); +} + +#undef GST_CAT_DEFAULT + +#endif // ENABLE(ENCRYPTED_MEDIA) && ENABLE(EXTERNAL_CDM) && USE(GSTREAMER) --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/eme/WebKitExternalCDMVideoDecoderGStreamer.h @@ -0,0 +1,50 @@ +/* GStreamer video decoder running inside an external CDM + * + * Copyright (C) 2026 Daniel Golle + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#if ENABLE(ENCRYPTED_MEDIA) && ENABLE(EXTERNAL_CDM) && USE(GSTREAMER) + +#include + +G_BEGIN_DECLS + +#define WEBKIT_TYPE_EXTERNAL_CDM_VIDEO_DECODER (webkit_external_cdm_video_decoder_get_type()) +#define WEBKIT_EXTERNAL_CDM_VIDEO_DECODER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), WEBKIT_TYPE_EXTERNAL_CDM_VIDEO_DECODER, WebKitExternalCDMVideoDecoder)) + +typedef struct _WebKitExternalCDMVideoDecoder WebKitExternalCDMVideoDecoder; +typedef struct _WebKitExternalCDMVideoDecoderClass WebKitExternalCDMVideoDecoderClass; +struct WebKitExternalCDMVideoDecoderPrivate; + +GType webkit_external_cdm_video_decoder_get_type(void); + +struct _WebKitExternalCDMVideoDecoder { + GstVideoDecoder parent; + + WebKitExternalCDMVideoDecoderPrivate* priv; +}; + +struct _WebKitExternalCDMVideoDecoderClass { + GstVideoDecoderClass parentClass; +}; + +G_END_DECLS + +#endif // ENABLE(ENCRYPTED_MEDIA) && ENABLE(EXTERNAL_CDM) && USE(GSTREAMER) --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/eme/cdm/base/base_export.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2012 The Chromium Authors. All rights reserved. + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: BSD-3-Clause + * See LICENSES/README.md for more information. + */ + +#ifndef BASE_BASE_EXPORT_H_ +#define BASE_BASE_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(BASE_IMPLEMENTATION) +#define BASE_EXPORT __declspec(dllexport) +#else +#define BASE_EXPORT __declspec(dllimport) +#endif // defined(BASE_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(BASE_IMPLEMENTATION) +#define BASE_EXPORT __attribute__((visibility("default"))) +#else +#define BASE_EXPORT +#endif // defined(BASE_IMPLEMENTATION) +#endif + +#else // defined(COMPONENT_BUILD) +#define BASE_EXPORT +#endif + +#endif // BASE_BASE_EXPORT_H_ --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/eme/cdm/base/basictypes.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2013 The Chromium Authors. All rights reserved. + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: BSD-3-Clause + * See LICENSES/README.md for more information. + */ + +#ifndef BASE_BASICTYPES_H_ +#define BASE_BASICTYPES_H_ + +#include // So we can set the bounds of our types. +#include // For size_t. +#include // For intptr_t. + +#include "macros.h" +#include "../build/build_config.h" + +// DEPRECATED: Use (u)int{8,16,32,64}_t instead (and include ). +// http://crbug.com/138542 +typedef int8_t int8; +typedef uint8_t uint8; +typedef int16_t int16; +typedef uint16_t uint16; +typedef int32_t int32; +typedef uint32_t uint32; +typedef int64_t int64; +typedef uint64_t uint64; + +// DEPRECATED: Use std::numeric_limits (from ) or +// (U)INT{8,16,32,64}_{MIN,MAX} in case of globals (and include ). +// http://crbug.com/138542 +const uint64 kuint64max = 0xFFFFFFFFFFFFFFFFULL; +const int32 kint32max = 0x7FFFFFFF; +const int64 kint64max = 0x7FFFFFFFFFFFFFFFLL; + +#endif // BASE_BASICTYPES_H_ --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/eme/cdm/base/compiler_specific.h @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2012 The Chromium Authors. All rights reserved. + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: BSD-3-Clause + * See LICENSES/README.md for more information. + */ + +#ifndef BASE_COMPILER_SPECIFIC_H_ +#define BASE_COMPILER_SPECIFIC_H_ + +#include "../build/build_config.h" + +#if defined(COMPILER_MSVC) + +// For _Printf_format_string_. +#include + +// Macros for suppressing and disabling warnings on MSVC. +// +// Warning numbers are enumerated at: +// http://msdn.microsoft.com/en-us/library/8x5x43k7(VS.80).aspx +// +// The warning pragma: +// http://msdn.microsoft.com/en-us/library/2c8f766e(VS.80).aspx +// +// Using __pragma instead of #pragma inside macros: +// http://msdn.microsoft.com/en-us/library/d9x1s805.aspx + +// MSVC_SUPPRESS_WARNING disables warning |n| for the remainder of the line and +// for the next line of the source file. +#define MSVC_SUPPRESS_WARNING(n) __pragma(warning(suppress:n)) + +// MSVC_PUSH_DISABLE_WARNING pushes |n| onto a stack of warnings to be disabled. +// The warning remains disabled until popped by MSVC_POP_WARNING. +#define MSVC_PUSH_DISABLE_WARNING(n) __pragma(warning(push)) \ + __pragma(warning(disable:n)) + +// MSVC_PUSH_WARNING_LEVEL pushes |n| as the global warning level. The level +// remains in effect until popped by MSVC_POP_WARNING(). Use 0 to disable all +// warnings. +#define MSVC_PUSH_WARNING_LEVEL(n) __pragma(warning(push, n)) + +// Pop effects of innermost MSVC_PUSH_* macro. +#define MSVC_POP_WARNING() __pragma(warning(pop)) + +#define MSVC_DISABLE_OPTIMIZE() __pragma(optimize("", off)) +#define MSVC_ENABLE_OPTIMIZE() __pragma(optimize("", on)) + +// Allows exporting a class that inherits from a non-exported base class. +// This uses suppress instead of push/pop because the delimiter after the +// declaration (either "," or "{") has to be placed before the pop macro. +// +// Example usage: +// class EXPORT_API Foo : NON_EXPORTED_BASE(public Bar) { +// +// MSVC Compiler warning C4275: +// non dll-interface class 'Bar' used as base for dll-interface class 'Foo'. +// Note that this is intended to be used only when no access to the base class' +// static data is done through derived classes or inline methods. For more info, +// see http://msdn.microsoft.com/en-us/library/3tdb471s(VS.80).aspx +#define NON_EXPORTED_BASE(code) MSVC_SUPPRESS_WARNING(4275) \ + code + +#else // Not MSVC + +#define _Printf_format_string_ +#define MSVC_SUPPRESS_WARNING(n) +#define MSVC_PUSH_DISABLE_WARNING(n) +#define MSVC_PUSH_WARNING_LEVEL(n) +#define MSVC_POP_WARNING() +#define MSVC_DISABLE_OPTIMIZE() +#define MSVC_ENABLE_OPTIMIZE() +#define NON_EXPORTED_BASE(code) code + +#endif // COMPILER_MSVC + + +// Annotate a variable indicating it's ok if the variable is not used. +// (Typically used to silence a compiler warning when the assignment +// is important for some other reason.) +// Use like: +// int x = ...; +// ALLOW_UNUSED_LOCAL(x); +#define ALLOW_UNUSED_LOCAL(x) false ? (void)x : (void)0 + +// Annotate a typedef or function indicating it's ok if it's not used. +// Use like: +// typedef Foo Bar ALLOW_UNUSED_TYPE; +#if defined(COMPILER_GCC) || defined(__clang__) +#define ALLOW_UNUSED_TYPE __attribute__((unused)) +#else +#define ALLOW_UNUSED_TYPE +#endif + +// Annotate a function indicating it should not be inlined. +// Use like: +// NOINLINE void DoStuff() { ... } +#if defined(COMPILER_GCC) +#define NOINLINE __attribute__((noinline)) +#elif defined(COMPILER_MSVC) +#define NOINLINE __declspec(noinline) +#else +#define NOINLINE +#endif + +// Specify memory alignment for structs, classes, etc. +// Use like: +// class ALIGNAS(16) MyClass { ... } +// ALIGNAS(16) int array[4]; +#if defined(COMPILER_MSVC) +#define ALIGNAS(byte_alignment) __declspec(align(byte_alignment)) +#elif defined(COMPILER_GCC) +#define ALIGNAS(byte_alignment) __attribute__((aligned(byte_alignment))) +#endif + +// Return the byte alignment of the given type (available at compile time). +// Use like: +// ALIGNOF(int32) // this would be 4 +#if defined(COMPILER_MSVC) +#define ALIGNOF(type) __alignof(type) +#elif defined(COMPILER_GCC) +#define ALIGNOF(type) __alignof__(type) +#endif + +// Annotate a function indicating the caller must examine the return value. +// Use like: +// int foo() WARN_UNUSED_RESULT; +// To explicitly ignore a result, see |ignore_result()| in base/macros.h. +// TODO(dcheng): Update //third_party/webrtc's macro definition to match. +#undef WARN_UNUSED_RESULT +#if defined(COMPILER_GCC) || defined(__clang__) +#define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +#define WARN_UNUSED_RESULT +#endif + +// Tell the compiler a function is using a printf-style format string. +// |format_param| is the one-based index of the format string parameter; +// |dots_param| is the one-based index of the "..." parameter. +// For v*printf functions (which take a va_list), pass 0 for dots_param. +// (This is undocumented but matches what the system C headers do.) +#if defined(COMPILER_GCC) +#define PRINTF_FORMAT(format_param, dots_param) \ + __attribute__((format(printf, format_param, dots_param))) +#else +#define PRINTF_FORMAT(format_param, dots_param) +#endif + +// WPRINTF_FORMAT is the same, but for wide format strings. +// This doesn't appear to yet be implemented in any compiler. +// See http://gcc.gnu.org/bugzilla/show_bug.cgi?id=38308 . +#define WPRINTF_FORMAT(format_param, dots_param) +// If available, it would look like: +// __attribute__((format(wprintf, format_param, dots_param))) + +// MemorySanitizer annotations. +#if defined(MEMORY_SANITIZER) && !defined(OS_NACL) +#include + +// Mark a memory region fully initialized. +// Use this to annotate code that deliberately reads uninitialized data, for +// example a GC scavenging root set pointers from the stack. +#define MSAN_UNPOISON(p, size) __msan_unpoison(p, size) + +// Check a memory region for initializedness, as if it was being used here. +// If any bits are uninitialized, crash with an MSan report. +// Use this to sanitize data which MSan won't be able to track, e.g. before +// passing data to another process via shared memory. +#define MSAN_CHECK_MEM_IS_INITIALIZED(p, size) \ + __msan_check_mem_is_initialized(p, size) +#else // MEMORY_SANITIZER +#define MSAN_UNPOISON(p, size) +#define MSAN_CHECK_MEM_IS_INITIALIZED(p, size) +#endif // MEMORY_SANITIZER + +// Macro useful for writing cross-platform function pointers. +#if !defined(CDECL) +#if defined(OS_WIN) +#define CDECL __cdecl +#else // defined(OS_WIN) +#define CDECL +#endif // defined(OS_WIN) +#endif // !defined(CDECL) + +// Macro for hinting that an expression is likely to be false. +#if !defined(UNLIKELY) +#if defined(COMPILER_GCC) +#define UNLIKELY(x) __builtin_expect(!!(x), 0) +#else +#define UNLIKELY(x) (x) +#endif // defined(COMPILER_GCC) +#endif // !defined(UNLIKELY) + +#endif // BASE_COMPILER_SPECIFIC_H_ --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/eme/cdm/base/macros.h @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2014 The Chromium Authors. All rights reserved. + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: BSD-3-Clause + * See LICENSES/README.md for more information. + */ + +// This file contains macros and macro-like constructs (e.g., templates) that +// are commonly used throughout Chromium source. (It may also contain things +// that are closely related to things that are commonly used that belong in this +// file.) + +#ifndef BASE_MACROS_H_ +#define BASE_MACROS_H_ + +#include // For size_t. +#include // For memcpy. + +// INITIALIZE_CDM_MODULE is a macro in api/content_decryption_module.h. +// However, we need to pass it as a string to GetFunctionPointer() once it +// is expanded. +#define STRINGIFY(X) #X +#define MAKE_STRING(X) STRINGIFY(X) + +// Put this in the declarations for a class to be uncopyable. +#define DISALLOW_COPY(TypeName) \ + TypeName(const TypeName&) = delete + +// Put this in the declarations for a class to be unassignable. +#define DISALLOW_ASSIGN(TypeName) \ + void operator=(const TypeName&) = delete + +// A macro to disallow the copy constructor and operator= functions +// This should be used in the private: declarations for a class +#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&); \ + void operator=(const TypeName&) + +// A macro to disallow all the implicit constructors, namely the +// default constructor, copy constructor and operator= functions. +// +// This should be used in the private: declarations for a class +// that wants to prevent anyone from instantiating it. This is +// especially useful for classes containing only static methods. +#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ + TypeName() = delete; \ + DISALLOW_COPY_AND_ASSIGN(TypeName) + +// The arraysize(arr) macro returns the # of elements in an array arr. +// The expression is a compile-time constant, and therefore can be +// used in defining new arrays, for example. If you use arraysize on +// a pointer by mistake, you will get a compile-time error. + +// This template function declaration is used in defining arraysize. +// Note that the function doesn't need an implementation, as we only +// use its type. +template char (&ArraySizeHelper(T (&array)[N]))[N]; +#define arraysize(array) (sizeof(ArraySizeHelper(array))) + +// bit_cast is a template function that implements the +// equivalent of "*reinterpret_cast(&source)". We need this in +// very low-level functions like the protobuf library and fast math +// support. +// +// float f = 3.14159265358979; +// int i = bit_cast(f); +// // i = 0x40490fdb +// +// The classical address-casting method is: +// +// // WRONG +// float f = 3.14159265358979; // WRONG +// int i = * reinterpret_cast(&f); // WRONG +// +// The address-casting method actually produces undefined behavior +// according to ISO C++ specification section 3.10 -15 -. Roughly, this +// section says: if an object in memory has one type, and a program +// accesses it with a different type, then the result is undefined +// behavior for most values of "different type". +// +// This is true for any cast syntax, either *(int*)&f or +// *reinterpret_cast(&f). And it is particularly true for +// conversions between integral lvalues and floating-point lvalues. +// +// The purpose of 3.10 -15- is to allow optimizing compilers to assume +// that expressions with different types refer to different memory. gcc +// 4.0.1 has an optimizer that takes advantage of this. So a +// non-conforming program quietly produces wildly incorrect output. +// +// The problem is not the use of reinterpret_cast. The problem is type +// punning: holding an object in memory of one type and reading its bits +// back using a different type. +// +// The C++ standard is more subtle and complex than this, but that +// is the basic idea. +// +// Anyways ... +// +// bit_cast<> calls memcpy() which is blessed by the standard, +// especially by the example in section 3.9 . Also, of course, +// bit_cast<> wraps up the nasty logic in one place. +// +// Fortunately memcpy() is very fast. In optimized mode, with a +// constant size, gcc 2.95.3, gcc 4.0.1, and msvc 7.1 produce inline +// code with the minimal amount of data movement. On a 32-bit system, +// memcpy(d,s,4) compiles to one load and one store, and memcpy(d,s,8) +// compiles to two loads and two stores. +// +// I tested this code with gcc 2.95.3, gcc 4.0.1, icc 8.1, and msvc 7.1. +// +// WARNING: if Dest or Source is a non-POD type, the result of the memcpy +// is likely to surprise you. + +template +inline Dest bit_cast(const Source& source) { + static_assert(sizeof(Dest) == sizeof(Source), + "bit_cast requires source and destination to be the same size"); + + Dest dest; + memcpy(&dest, &source, sizeof(dest)); + return dest; +} + +// Used to explicitly mark the return value of a function as unused. If you are +// really sure you don't want to do anything with the return value of a function +// that has been marked WARN_UNUSED_RESULT, wrap it with this. Example: +// +// scoped_ptr my_var = ...; +// if (TakeOwnership(my_var.get()) == SUCCESS) +// ignore_result(my_var.release()); +// +template +inline void ignore_result(const T&) { +} + +// The following enum should be used only as a constructor argument to indicate +// that the variable has static storage class, and that the constructor should +// do nothing to its state. It indicates to the reader that it is legal to +// declare a static instance of the class, provided the constructor is given +// the base::LINKER_INITIALIZED argument. Normally, it is unsafe to declare a +// static variable that has a constructor or a destructor because invocation +// order is undefined. However, IF the type can be initialized by filling with +// zeroes (which the loader does for static variables), AND the destructor also +// does nothing to the storage, AND there are no virtual methods, then a +// constructor declared as +// explicit MyClass(base::LinkerInitialized x) {} +// and invoked as +// static MyClass my_variable_name(base::LINKER_INITIALIZED); +namespace base { +enum LinkerInitialized { LINKER_INITIALIZED }; + +// Use these to declare and define a static local variable (static T;) so that +// it is leaked so that its destructors are not called at exit. If you need +// thread-safe initialization, use base/lazy_instance.h instead. +#define CR_DEFINE_STATIC_LOCAL(type, name, arguments) \ + static type& name = *new type arguments + +} // base + +#endif // BASE_MACROS_H_ --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/eme/cdm/base/native_library.cc @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2015 The Chromium Authors. All rights reserved. + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: BSD-3-Clause + * See LICENSES/README.md for more information. + */ + +#include "native_library.h" + +namespace base { + + const char *int2char(int value, char* buffer) + { + const char *result = buffer; + unsigned int buffer_pos = 0; + + if (value < 0) { + buffer[buffer_pos++] = '-'; + value = -value; + } + + int number_of_digits = 0; + int t = value; + do { + ++number_of_digits; + } while (t /= 10); + + buffer_pos += number_of_digits; + + do { + int last_digit = value % 10; + buffer[--buffer_pos] = '0' + last_digit; + value /= 10; + } while (value); + return result; + } +} //namespace --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/eme/cdm/base/native_library.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2011 The Chromium Authors. All rights reserved. + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: BSD-3-Clause + * See LICENSES/README.md for more information. + */ + +#ifndef BASE_NATIVE_LIBRARY_H_ +#define BASE_NATIVE_LIBRARY_H_ + +// This file defines a cross-platform "NativeLibrary" type which represents +// a loadable module. + +#include + +#include "../base/base_export.h" +#include "../base/compiler_specific.h" +//#include "base/strings/string16.h" +#include "../build/build_config.h" + +#if defined(OS_WIN) +#include +// Windows "ERROR" define can conflicts with definitions of projects that use this name, so remove it +#undef ERROR +#elif defined(OS_MACOSX) +#import +#endif // OS_* + +namespace base { + +const char *int2char(int value, char* buffer); + +//class FilePath; + +#if defined(OS_WIN) +typedef HMODULE NativeLibrary; +#elif defined(OS_MACOSX) +enum NativeLibraryType { + BUNDLE, + DYNAMIC_LIB +}; +enum NativeLibraryObjCStatus { + OBJC_UNKNOWN, + OBJC_PRESENT, + OBJC_NOT_PRESENT, +}; +struct NativeLibraryStruct { + NativeLibraryType type; + NativeLibraryObjCStatus objc_status; + union { + CFBundleRef bundle; + void* dylib; + }; +}; +typedef NativeLibraryStruct* NativeLibrary; +#elif defined(OS_POSIX) +typedef void* NativeLibrary; +#endif // OS_* + +struct BASE_EXPORT NativeLibraryLoadError { +#if defined(OS_WIN) + NativeLibraryLoadError() : code(0) {} +#endif // OS_WIN + + // Returns a string representation of the load error. + std::string ToString() const; + +#if defined(OS_WIN) + DWORD code; +#else + std::string message; +#endif // OS_WIN +}; + +// Loads a native library from disk. Release it with UnloadNativeLibrary when +// you're done. Returns NULL on failure. +// If |error| is not NULL, it may be filled in on load error. +BASE_EXPORT NativeLibrary LoadNativeLibrary(const std::string& library_path, + NativeLibraryLoadError* error); + +#if defined(OS_WIN) +// Loads a native library from disk. Release it with UnloadNativeLibrary when +// you're done. +// This function retrieves the LoadLibrary function exported from kernel32.dll +// and calls it instead of directly calling the LoadLibrary function via the +// import table. +BASE_EXPORT NativeLibrary LoadNativeLibraryDynamically( + const std::string& library_path); +#endif // OS_WIN + +// Unloads a native library. +BASE_EXPORT void UnloadNativeLibrary(NativeLibrary library); + +// Gets a function pointer from a native library. +BASE_EXPORT void* GetFunctionPointerFromNativeLibrary(NativeLibrary library, + const char* name); + +// Returns the full platform specific name for a native library. +// For example: +// "mylib" returns "mylib.dll" on Windows, "libmylib.so" on Linux, +// "mylib.dylib" on Mac. +//BASE_EXPORT string16 GetNativeLibraryName(const string16& name); + +} // namespace base + +#endif // BASE_NATIVE_LIBRARY_H_ --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/eme/cdm/base/native_library_posix.cc @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2011 The Chromium Authors. All rights reserved. + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: BSD-3-Clause + * See LICENSES/README.md for more information. + */ + +#include "native_library.h" + +#include + +namespace base { + +std::string NativeLibraryLoadError::ToString() const { + return message; +} + +// static +NativeLibrary LoadNativeLibrary(const std::string& library_path, + NativeLibraryLoadError* error) { + // dlopen() opens the file off disk. + //base::ThreadRestrictions::AssertIOAllowed(); + + // We deliberately do not use RTLD_DEEPBIND. For the history why, please + // refer to the bug tracker. Some useful bug reports to read include: + // http://crbug.com/17943, http://crbug.com/17557, http://crbug.com/36892, + // and http://crbug.com/40794. +#ifdef ANDROID + void* dl = dlopen(library_path.c_str(), RTLD_LAZY); +#else + void* dl = dlopen(library_path.c_str(), RTLD_LAZY); +#endif + + if (!dl && error) + error->message = dlerror(); + + return dl; +} + +// static +void UnloadNativeLibrary(NativeLibrary library) { + if (library) + { + int ret = 0;//dlclose(library); + if (ret < 0) { + //DLOG(ERROR) << "dlclose failed: " << dlerror(); + //NOTREACHED(); + } + } +} + +// static +void* GetFunctionPointerFromNativeLibrary(NativeLibrary library, + const char* name) { + return dlsym(library, name); +} + +// static +//string16 GetNativeLibraryName(const string16& name) { +// return ASCIIToUTF16("lib") + name + ASCIIToUTF16(".so"); +//} + +} // namespace base --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/eme/cdm/build/build_config.h @@ -0,0 +1,203 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file adds defines about the platform we're currently building on. +// Operating System: +// OS_WIN / OS_MACOSX / OS_LINUX / OS_POSIX (MACOSX or LINUX) / +// OS_NACL (NACL_SFI or NACL_NONSFI) / OS_NACL_SFI / OS_NACL_NONSFI +// Compiler: +// COMPILER_MSVC / COMPILER_GCC +// Processor: +// ARCH_CPU_X86 / ARCH_CPU_X86_64 / ARCH_CPU_X86_FAMILY (X86 or X86_64) +// ARCH_CPU_32_BITS / ARCH_CPU_64_BITS + +#ifndef BUILD_BUILD_CONFIG_H_ +#define BUILD_BUILD_CONFIG_H_ + +// A set of macros to use for platform detection. +#if defined(__native_client__) +// __native_client__ must be first, so that other OS_ defines are not set. +#define OS_NACL 1 +// OS_NACL comes in two sandboxing technology flavors, SFI or Non-SFI. +// PNaCl toolchain defines __native_client_nonsfi__ macro in Non-SFI build +// mode, while it does not in SFI build mode. +#if defined(__native_client_nonsfi__) +#define OS_NACL_NONSFI +#else +#define OS_NACL_SFI +#endif +#elif defined(ANDROID) +#define OS_ANDROID 1 +#elif defined(__APPLE__) +// only include TargetConditions after testing ANDROID as some android builds +// on mac don't have this header available and it's not needed unless the target +// is really mac/ios. +#include +#define OS_MACOSX 1 +#if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE +#define OS_IOS 1 +#endif // defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE +#elif defined(__linux__) +#define OS_LINUX 1 +// include a system header to pull in features.h for glibc/uclibc macros. +#include +#if defined(__GLIBC__) && !defined(__UCLIBC__) +// we really are using glibc, not uClibc pretending to be glibc +#define LIBC_GLIBC 1 +#endif +#elif defined(_WIN32) +#define OS_WIN 1 +#define TOOLKIT_VIEWS 1 +#elif defined(__FreeBSD__) +#define OS_FREEBSD 1 +#elif defined(__OpenBSD__) +#define OS_OPENBSD 1 +#elif defined(__sun) +#define OS_SOLARIS 1 +#elif defined(__QNXNTO__) +#define OS_QNX 1 +#else +#error Please add support for your platform in build/build_config.h +#endif + +#if defined(USE_OPENSSL_CERTS) && defined(USE_NSS_CERTS) +#error Cannot use both OpenSSL and NSS for certificates +#endif + +// For access to standard BSD features, use OS_BSD instead of a +// more specific macro. +#if defined(OS_FREEBSD) || defined(OS_OPENBSD) +#define OS_BSD 1 +#endif + +// For access to standard POSIXish features, use OS_POSIX instead of a +// more specific macro. +#if defined(OS_MACOSX) || defined(OS_LINUX) || defined(OS_FREEBSD) || \ + defined(OS_OPENBSD) || defined(OS_SOLARIS) || defined(OS_ANDROID) || \ + defined(OS_NACL) || defined(OS_QNX) +#define OS_POSIX 1 +#endif + +// Use tcmalloc +#if (defined(OS_WIN) || defined(OS_LINUX) || defined(OS_ANDROID)) && \ + !defined(NO_TCMALLOC) +#define USE_TCMALLOC 1 +#endif + +// Compiler detection. +#if defined(__GNUC__) +#define COMPILER_GCC 1 +#elif defined(_MSC_VER) +#define COMPILER_MSVC 1 +#else +#error Please add support for your compiler in build/build_config.h +#endif + +// Processor architecture detection. For more info on what's defined, see: +// http://msdn.microsoft.com/en-us/library/b0084kay.aspx +// http://www.agner.org/optimize/calling_conventions.pdf +// or with gcc, run: "echo | gcc -E -dM -" +#if defined(_M_ARM) +#define ARCH_CPU_ARM_FAMILY 1 +#define ARCH_CPU_ARMEL 1 +#define ARCH_CPU_32_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 +#elif defined(_M_X64) || defined(__x86_64__) +#define ARCH_CPU_X86_FAMILY 1 +#define ARCH_CPU_X86_64 1 +#define ARCH_CPU_64_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 +#elif defined(_M_IX86) || defined(__i386__) +#define ARCH_CPU_X86_FAMILY 1 +#define ARCH_CPU_X86 1 +#define ARCH_CPU_32_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 +#elif defined(__ARMEL__) +#define ARCH_CPU_ARM_FAMILY 1 +#define ARCH_CPU_ARMEL 1 +#define ARCH_CPU_32_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 +#elif defined(__aarch64__) || defined(_M_ARM64) +#define ARCH_CPU_ARM_FAMILY 1 +#define ARCH_CPU_ARM64 1 +#define ARCH_CPU_64_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 +#elif defined(__pnacl__) +#define ARCH_CPU_32_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 +#elif defined(__MIPSEL__) +#if defined(__LP64__) +#define ARCH_CPU_MIPS64_FAMILY 1 +#define ARCH_CPU_MIPS64EL 1 +#define ARCH_CPU_64_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 +#else +#define ARCH_CPU_MIPS_FAMILY 1 +#define ARCH_CPU_MIPSEL 1 +#define ARCH_CPU_32_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 +#endif +#elif (defined(__PPC64__) || defined(__PPC__)) && defined(__BIG_ENDIAN__) +#define ARCH_CPU_PPC64_FAMILY 1 +#define ARCH_CPU_PPC64 1 +#define ARCH_CPU_64_BITS 1 +#define ARCH_CPU_BIG_ENDIAN 1 +#elif defined(__PPC64__) +#define ARCH_CPU_PPC64_FAMILY 1 +#define ARCH_CPU_PPC64 1 +#define ARCH_CPU_64_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 +#elif defined(__riscv) || defined(__riscv__) +#define ARCH_CPU_RISCV_FAMILY 1 +#define ARCH_CPU_RISCV 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 +#if defined(__riscv_xlen) +# if (__riscv_xlen == 64) +# define ARCH_CPU_64_BITS 1 +# else +# define ARCH_CPU_32_BITS 1 +# endif +#endif +#elif defined(__loongarch__) +#define ARCH_CPU_LOONGARCH_FAMILY 1 +#define ARCH_CPU_LOONGARCH 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 +#if (__loongarch_grlen == 64) +#define ARCH_CPU_64_BITS 1 +#else +#define ARCH_CPU_32_BITS 1 +#endif +# else +#error Please add support for your architecture in build/build_config. +#endif + +// Type detection for wchar_t. +#if defined(OS_WIN) +#define WCHAR_T_IS_UTF16 +#elif defined(OS_POSIX) && defined(COMPILER_GCC) && \ + defined(__WCHAR_MAX__) && \ + (__WCHAR_MAX__ == 0x7fffffff || __WCHAR_MAX__ == 0xffffffff) +#define WCHAR_T_IS_UTF32 +#elif defined(OS_POSIX) && defined(COMPILER_GCC) && \ + defined(__WCHAR_MAX__) && \ + (__WCHAR_MAX__ == 0x7fff || __WCHAR_MAX__ == 0xffff) +// On Posix, we'll detect short wchar_t, but projects aren't guaranteed to +// compile in this mode (in particular, Chrome doesn't). This is intended for +// other projects using base who manage their own dependencies and make sure +// short wchar works for them. +#define WCHAR_T_IS_UTF16 +#else +#error Please add support for your compiler in build/build_config.h +#endif + +#if defined(OS_ANDROID) +// The compiler thinks std::string::const_iterator and "const char*" are +// equivalent types. +#define STD_STRING_ITERATOR_IS_CHAR_POINTER +// The compiler thinks base::string16::const_iterator and "char16*" are +// equivalent types. +#define BASE_STRING16_ITERATOR_IS_CHAR16_POINTER +#endif + +#endif // BUILD_BUILD_CONFIG_H_ --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/eme/cdm/debug.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2025 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "debug.h" + +#include +#include + +#ifdef _MSC_VER +#define snprintf _snprintf +#endif + +struct dbgContext +{ + const char* name; + void (*msgCallback)(const CDM_DBG::LogLevel level, const char* msg); +}; + +dbgContext debugContext = {"WV-CDM-Library", nullptr}; + +void CDM_DBG::Log(const CDM_DBG::LogLevel level, const char* format, ...) +{ + if (!debugContext.msgCallback) + return; + + char msg[2048]; + const int len = snprintf(msg, sizeof(msg), "[%s] ", debugContext.name); + if (len < 0 || len >= sizeof(msg)) + { + debugContext.msgCallback(CDM_DBG::LogLevel::ERROR, + "Cannot print log string: Context name too long"); + return; + } + + va_list ap; + va_start(ap, format); + const int formattedLen = vsnprintf(msg + len, sizeof(msg) - len, format, ap); + va_end(ap); + if (formattedLen < 0 || formattedLen >= (sizeof(msg) - len)) + { + debugContext.msgCallback(CDM_DBG::LogLevel::ERROR, + "Cannot print log string: Text content too long"); + return; + } + + debugContext.msgCallback(level, msg); +} + +void CDM_DBG::SetDBGMsgCallback(void (*msgcb)(const CDM_DBG::LogLevel level, const char* msg)) +{ + debugContext.msgCallback = msgcb; +} --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/eme/cdm/debug.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2025 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +namespace CDM_DBG +{ +enum class LogLevel +{ + DEBUG, + INFO, + WARNING, + ERROR, + FATAL +}; + +void Log(const LogLevel level, const char* format, ...); + +void SetDBGMsgCallback(void (*msgcb)(const LogLevel level, const char* msg)); + +} // namespace cdm --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/eme/cdm/media/base/cdm_config.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2015 The Chromium Authors. All rights reserved. + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: BSD-3-Clause + * See LICENSES/README.md for more information. + */ + +#ifndef MEDIA_BASE_CDM_CONFIG_H_ +#define MEDIA_BASE_CDM_CONFIG_H_ + +namespace media { + +// The runtime configuration for new CDM instances as computed by +// |requestMediaKeySystemAccess|. This is in some sense the Chromium-side +// counterpart of Blink's WebMediaKeySystemConfiguration. +struct CdmConfig { + CdmConfig(bool distinctive_identifier = false, + bool persistent_state = false, + bool hw_secure_codecs = false) + : allow_distinctive_identifier(distinctive_identifier), + allow_persistent_state(persistent_state), + use_hw_secure_codecs(hw_secure_codecs) {}; + + // Allow access to a distinctive identifier. + bool allow_distinctive_identifier; + + // Allow access to persistent state. + bool allow_persistent_state; + + // Use hardware-secure codecs. This flag is only used on Android, it should + // always be false on other platforms. + bool use_hw_secure_codecs; +}; + +} // namespace media + +#endif // MEDIA_BASE_CDM_CONFIG_H_ --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/eme/cdm/media/base/limits.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2011 The Chromium Authors. All rights reserved. + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: BSD-3-Clause + * See LICENSES/README.md for more information. + */ + +// Contains limit definition constants for the media subsystem. + +#ifndef MEDIA_BASE_LIMITS_H_ +#define MEDIA_BASE_LIMITS_H_ + +#include "../../base/basictypes.h" + +namespace media { + +namespace limits { + +enum { + // Maximum possible dimension (width or height) for any video. + kMaxDimension = (1 << 15) - 1, // 32767 + + // Maximum possible canvas size (width multiplied by height) for any video. + kMaxCanvas = (1 << (14 * 2)), // 16384 x 16384 + + // Total number of video frames which are populating in the pipeline. + kMaxVideoFrames = 4, + + // The following limits are used by AudioParameters::IsValid(). + // + // A few notes on sample rates of common formats: + // - AAC files are limited to 96 kHz. + // - MP3 files are limited to 48 kHz. + // - Vorbis used to be limited to 96 KHz, but no longer has that + // restriction. + // - Most PC audio hardware is limited to 192 KHz. + kMaxSampleRate = 192000, + kMinSampleRate = 3000, + kMaxChannels = 32, + kMaxBytesPerSample = 4, + kMaxBitsPerSample = kMaxBytesPerSample * 8, + kMaxSamplesPerPacket = kMaxSampleRate, + kMaxPacketSizeInBytes = + kMaxBytesPerSample * kMaxChannels * kMaxSamplesPerPacket, + + // This limit is used by ParamTraits. + kMaxFramesPerSecond = 1000, + + // Maximum lengths for various EME API parameters. These are checks to + // prevent unnecessarily large parameters from being passed around, and the + // lengths are somewhat arbitrary as the EME spec doesn't specify any limits. + kMinCertificateLength = 128, + kMaxCertificateLength = 16 * 1024, + kMaxSessionIdLength = 512, + kMinKeyIdLength = 1, + kMaxKeyIdLength = 512, + kMaxKeyIds = 128, + kMaxInitDataLength = 64 * 1024, // 64 KB + kMaxSessionResponseLength = 64 * 1024, // 64 KB + kMaxKeySystemLength = 256, +}; + +} // namespace limits + +} // namespace media + +#endif // MEDIA_BASE_LIMITS_H_ --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/eme/cdm/media/cdm/api/content_decryption_module.h @@ -0,0 +1,1793 @@ +/* + * Copyright (C) 2012 The Chromium Authors. All rights reserved. + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: BSD-3-Clause + * See LICENSES/README.md for more information. + */ + +#ifndef CDM_CONTENT_DECRYPTION_MODULE_H_ +#define CDM_CONTENT_DECRYPTION_MODULE_H_ + +#include + +#include "content_decryption_module_export.h" + +#if defined(_MSC_VER) +typedef unsigned char uint8_t; +typedef unsigned int uint32_t; +typedef int int32_t; +typedef __int64 int64_t; +#else +#include +#endif + +// The version number must be rolled when the exported functions are updated! +// If the CDM and the adapter use different versions of these functions, the +// adapter will fail to load or crash! +#ifdef TARGET_WEBOS + #define CDM_MODULE_VERSION 10 +#else + #define CDM_MODULE_VERSION 4 +#endif + +// Build the versioned entrypoint name. +// The extra macros are necessary to expand version to an actual value. +#define INITIALIZE_CDM_MODULE \ + BUILD_ENTRYPOINT(InitializeCdmModule, CDM_MODULE_VERSION) +#define BUILD_ENTRYPOINT(name, version) \ + BUILD_ENTRYPOINT_NO_EXPANSION(name, version) +#define BUILD_ENTRYPOINT_NO_EXPANSION(name, version) name##_##version + +// Macro to check that |type| does the following: +// 1. is a standard layout. +// 2. is trivial. +// 3. sizeof(type) matches the expected size in bytes. As some types contain +// pointers, the size is specified for both 32 and 64 bit. +#define CHECK_TYPE(type, size_32, size_64) \ + static_assert(std::is_standard_layout(), \ + #type " not standard_layout"); \ + static_assert(std::is_trivial(), #type " not trivial"); \ + static_assert((sizeof(void*) == 4 && sizeof(type) == size_32) || \ + (sizeof(void*) == 8 && sizeof(type) == size_64), \ + #type " size mismatch") + +extern "C" { + +CDM_API void INITIALIZE_CDM_MODULE(); + +CDM_API void DeinitializeCdmModule(); + +// Returns a pointer to the requested CDM Host interface upon success. +// Returns NULL if the requested CDM Host interface is not supported. +// The caller should cast the returned pointer to the type matching +// |host_interface_version|. +typedef void* (*GetCdmHostFunc)(int host_interface_version, void* user_data); + +// Returns a pointer to the requested CDM upon success. +// Returns NULL if an error occurs or the requested |cdm_interface_version| or +// |key_system| is not supported or another error occurs. +// The caller should cast the returned pointer to the type matching +// |cdm_interface_version|. +// Caller retains ownership of arguments and must call Destroy() on the returned +// object. +CDM_API void* CreateCdmInstance(int cdm_interface_version, + const char* key_system, + uint32_t key_system_size, + GetCdmHostFunc get_cdm_host_func, + void* user_data); + +CDM_API const char* GetCdmVersion(); + +} // extern "C" + +namespace cdm { + +enum Status : uint32_t { + kSuccess = 0, + kNeedMoreData, // Decoder needs more data to produce a decoded frame/sample. + kNoKey, // The required decryption key is not available. + kInitializationError, // Initialization error. + kDecryptError, // Decryption failed. + kDecodeError, // Error decoding audio or video. + kDeferredInitialization // Decoder is not ready for initialization. +}; +CHECK_TYPE(Status, 4, 4); + +// Exceptions used by the CDM to reject promises. +// https://w3c.github.io/encrypted-media/#exceptions +enum Exception : uint32_t { + kExceptionTypeError, + kExceptionNotSupportedError, + kExceptionInvalidStateError, + kExceptionQuotaExceededError +}; +CHECK_TYPE(Exception, 4, 4); + +// The encryption scheme. The definitions are from ISO/IEC 23001-7:2016. +enum class EncryptionScheme : uint32_t { + kUnencrypted = 0, + kCenc, // 'cenc' subsample encryption using AES-CTR mode. + kCbcs // 'cbcs' pattern encryption using AES-CBC mode. +}; +CHECK_TYPE(EncryptionScheme, 4, 4); + +// The pattern used for pattern encryption. Note that ISO/IEC 23001-7:2016 +// defines each block to be 16-bytes. +struct Pattern { + uint32_t crypt_byte_block; // Count of the encrypted blocks. + uint32_t skip_byte_block; // Count of the unencrypted blocks. +}; +CHECK_TYPE(Pattern, 8, 8); + +enum class ColorRange : uint8_t { + kInvalid, + kLimited, // 709 color range with RGB values ranging from 16 to 235. + kFull, // Full RGB color range with RGB values from 0 to 255. + kDerived // Range is defined by |transfer_id| and |matrix_id|. +}; +CHECK_TYPE(ColorRange, 1, 1); + +// Described in ISO 23001-8:2016, section 7. All the IDs are in the range +// [0, 255] so 8-bit integer is sufficient. An unspecified ColorSpace should be +// {2, 2, 2, ColorRange::kInvalid}, where value 2 means "Unspecified" for all +// the IDs, as defined by the spec. +struct ColorSpace { + uint8_t primary_id; // 7.1 colour primaries, table 2 + uint8_t transfer_id; // 7.2 transfer characteristics, table 3 + uint8_t matrix_id; // 7.3 matrix coefficients, table 4 + ColorRange range; +}; +CHECK_TYPE(ColorSpace, 4, 4); + +// Time is defined as the number of seconds since the Epoch +// (00:00:00 UTC, January 1, 1970), not including any added leap second. +// Also see Time definition in spec: https://w3c.github.io/encrypted-media/#time +// Note that Time is defined in millisecond accuracy in the spec but in second +// accuracy here. +typedef double Time; + +// An input buffer can be split into several continuous subsamples. +// A SubsampleEntry specifies the number of clear and cipher bytes in each +// subsample. For example, the following buffer has three subsamples: +// +// |<----- subsample1 ----->|<----- subsample2 ----->|<----- subsample3 ----->| +// | clear1 | cipher1 | clear2 | cipher2 | clear3 | cipher3 | +// +// For decryption, all of the cipher bytes in a buffer should be concatenated +// (in the subsample order) into a single logical stream. The clear bytes should +// not be considered as part of decryption. +// +// Stream to decrypt: | cipher1 | cipher2 | cipher3 | +// Decrypted stream: | decrypted1| decrypted2 | decrypted3 | +// +// After decryption, the decrypted bytes should be copied over the position +// of the corresponding cipher bytes in the original buffer to form the output +// buffer. Following the above example, the decrypted buffer should be: +// +// |<----- subsample1 ----->|<----- subsample2 ----->|<----- subsample3 ----->| +// | clear1 | decrypted1| clear2 | decrypted2 | clear3 | decrypted3 | +// +struct SubsampleEntry { + uint32_t clear_bytes; + uint32_t cipher_bytes; +}; +CHECK_TYPE(SubsampleEntry, 8, 8); + +// Represents an input buffer to be decrypted (and possibly decoded). It does +// not own any pointers in this struct. If |encryption_scheme| = kUnencrypted, +// the data is unencrypted. +// Note that this struct is organized so that sizeof(InputBuffer_2) +// equals the sum of sizeof() all members in both 32-bit and 64-bit compiles. +// Padding has been added to keep the fields aligned. +struct InputBuffer_2 { + const uint8_t* data; // Pointer to the beginning of the input data. + uint32_t data_size; // Size (in bytes) of |data|. + + EncryptionScheme encryption_scheme; + + const uint8_t* key_id; // Key ID to identify the decryption key. + uint32_t key_id_size; // Size (in bytes) of |key_id|. + uint32_t : 32; // Padding. + + const uint8_t* iv; // Initialization vector. + uint32_t iv_size; // Size (in bytes) of |iv|. + uint32_t : 32; // Padding. + + const struct SubsampleEntry* subsamples; + uint32_t num_subsamples; // Number of subsamples in |subsamples|. + uint32_t : 32; // Padding. + + // |pattern| is required if |encryption_scheme| specifies pattern encryption. + Pattern pattern; + + int64_t timestamp; // Presentation timestamp in microseconds. +}; +CHECK_TYPE(InputBuffer_2, 64, 80); + +enum AudioCodec : uint32_t { kUnknownAudioCodec = 0, kCodecVorbis, kCodecAac }; +CHECK_TYPE(AudioCodec, 4, 4); + +struct AudioDecoderConfig_2 { + AudioCodec codec; + int32_t channel_count; + int32_t bits_per_channel; + int32_t samples_per_second; + + // Optional byte data required to initialize audio decoders, such as the + // vorbis setup header. + uint8_t* extra_data; + uint32_t extra_data_size; + + // Encryption scheme. + EncryptionScheme encryption_scheme; +}; +CHECK_TYPE(AudioDecoderConfig_2, 28, 32); + +// Supported sample formats for AudioFrames. +enum AudioFormat : uint32_t { + kUnknownAudioFormat = 0, // Unknown format value. Used for error reporting. + kAudioFormatU8, // Interleaved unsigned 8-bit w/ bias of 128. + kAudioFormatS16, // Interleaved signed 16-bit. + kAudioFormatS32, // Interleaved signed 32-bit. + kAudioFormatF32, // Interleaved float 32-bit. + kAudioFormatPlanarS16, // Signed 16-bit planar. + kAudioFormatPlanarF32, // Float 32-bit planar. +}; +CHECK_TYPE(AudioFormat, 4, 4); + +// Surface formats based on FOURCC labels, see: http://www.fourcc.org/yuv.php +// Values are chosen to be consistent with Chromium's VideoPixelFormat values. +enum VideoFormat : uint32_t { + kUnknownVideoFormat = 0, // Unknown format value. Used for error reporting. + kYv12 = 1, // 12bpp YVU planar 1x1 Y, 2x2 VU samples. + kI420 = 2, // 12bpp YUV planar 1x1 Y, 2x2 UV samples. + + // In the following formats, each sample uses 16-bit in storage, while the + // sample value is stored in the least significant N bits where N is + // specified by the number after "P". For example, for YUV420P9, each Y, U, + // and V sample is stored in the least significant 9 bits in a 2-byte block. + kYUV420P9 = 16, + kYUV420P10 = 17, + kYUV422P9 = 18, + kYUV422P10 = 19, + kYUV444P9 = 20, + kYUV444P10 = 21, + kYUV420P12 = 22, + kYUV422P12 = 23, + kYUV444P12 = 24, +}; +CHECK_TYPE(VideoFormat, 4, 4); + +struct Size { + int32_t width; + int32_t height; +}; +CHECK_TYPE(Size, 8, 8); + +enum VideoCodec : uint32_t { + kUnknownVideoCodec = 0, + kCodecVp8, + kCodecH264, + kCodecVp9, + kCodecAv1 +}; +CHECK_TYPE(VideoCodec, 4, 4); + +enum VideoCodecProfile : uint32_t { + kUnknownVideoCodecProfile = 0, + kProfileNotNeeded, + kH264ProfileBaseline, + kH264ProfileMain, + kH264ProfileExtended, + kH264ProfileHigh, + kH264ProfileHigh10, + kH264ProfileHigh422, + kH264ProfileHigh444Predictive, + kVP9Profile0, + kVP9Profile1, + kVP9Profile2, + kVP9Profile3, + kAv1ProfileMain, + kAv1ProfileHigh, + kAv1ProfilePro +}; +CHECK_TYPE(VideoCodecProfile, 4, 4); + +// Deprecated: New CDM implementations should use VideoDecoderConfig_3. +// Note that this struct is organized so that sizeof(VideoDecoderConfig_2) +// equals the sum of sizeof() all members in both 32-bit and 64-bit compiles. +// Padding has been added to keep the fields aligned. +struct VideoDecoderConfig_2 { + VideoCodec codec; + VideoCodecProfile profile; + VideoFormat format; + uint32_t : 32; // Padding. + + // Width and height of video frame immediately post-decode. Not all pixels + // in this region are valid. + Size coded_size; + + // Optional byte data required to initialize video decoders, such as H.264 + // AAVC data. + uint8_t* extra_data; + uint32_t extra_data_size; + + // Encryption scheme. + EncryptionScheme encryption_scheme; +}; +CHECK_TYPE(VideoDecoderConfig_2, 36, 40); + +struct VideoDecoderConfig_3 { + VideoCodec codec; + VideoCodecProfile profile; + VideoFormat format; + ColorSpace color_space; + + // Width and height of video frame immediately post-decode. Not all pixels + // in this region are valid. + Size coded_size; + + // Optional byte data required to initialize video decoders, such as H.264 + // AAVC data. + uint8_t* extra_data; + uint32_t extra_data_size; + + EncryptionScheme encryption_scheme; +}; +CHECK_TYPE(VideoDecoderConfig_3, 36, 40); + +enum StreamType : uint32_t { kStreamTypeAudio = 0, kStreamTypeVideo = 1 }; +CHECK_TYPE(StreamType, 4, 4); + +// Structure provided to ContentDecryptionModule::OnPlatformChallengeResponse() +// after a platform challenge was initiated via Host::SendPlatformChallenge(). +// All values will be NULL / zero in the event of a challenge failure. +struct PlatformChallengeResponse { + // |challenge| provided during Host::SendPlatformChallenge() combined with + // nonce data and signed with the platform's private key. + const uint8_t* signed_data; + uint32_t signed_data_length; + + // RSASSA-PKCS1-v1_5-SHA256 signature of the |signed_data| block. + const uint8_t* signed_data_signature; + uint32_t signed_data_signature_length; + + // X.509 device specific certificate for the |service_id| requested. + const uint8_t* platform_key_certificate; + uint32_t platform_key_certificate_length; +}; +CHECK_TYPE(PlatformChallengeResponse, 24, 48); + +// The current status of the associated key. The valid types are defined in the +// spec: https://w3c.github.io/encrypted-media/#dom-mediakeystatus +enum KeyStatus : uint32_t { + kUsable = 0, + kInternalError = 1, + kExpired = 2, + kOutputRestricted = 3, + kOutputDownscaled = 4, + kStatusPending = 5, + kReleased = 6 +}; +CHECK_TYPE(KeyStatus, 4, 4); + +// The current status of the associated key. The valid types are defined in the +// spec: https://w3c.github.io/encrypted-media/#dom-mediakeystatus +// Note: For forward compatibility, Host implementations must gracefully handle +// unexpected (new) enum values, e.g. no-op. This is used by the CDM Interfaces +// starting from CDM_12. +enum class KeyStatus_2 : uint32_t { + kUsable = 0, + kInternalError = 1, + kExpired = 2, + kOutputRestricted = 3, + kOutputDownscaled = 4, + kStatusPending = 5, + kReleased = 6, + kUsableInFuture = 7 +}; +CHECK_TYPE(KeyStatus_2, 4, 4); + +// Used when passing arrays of key information. Does not own the referenced +// data. |system_code| is an additional error code for unusable keys and +// should be 0 when |status| == kUsable. +struct KeyInformation { + const uint8_t* key_id; + uint32_t key_id_size; + KeyStatus status; + uint32_t system_code; +}; +CHECK_TYPE(KeyInformation, 16, 24); + +// Used when passing arrays of key information. Does not own the referenced +// data. |system_code| is an additional error code for unusable keys and +// should be 0 when |status| == kUsable. Used by CDM12 and beyond. +struct KeyInformation_2 { + const uint8_t* key_id; + uint32_t key_id_size; + KeyStatus_2 status; + uint32_t system_code; +}; +CHECK_TYPE(KeyInformation_2, 16, 24); + +// Supported output protection methods for use with EnableOutputProtection() and +// returned by OnQueryOutputProtectionStatus(). +enum OutputProtectionMethods : uint32_t { + kProtectionNone = 0, + kProtectionHDCP = 1 << 0 +}; +CHECK_TYPE(OutputProtectionMethods, 4, 4); + +// Connected output link types returned by OnQueryOutputProtectionStatus(). +enum OutputLinkTypes : uint32_t { + kLinkTypeNone = 0, + kLinkTypeUnknown = 1 << 0, + kLinkTypeInternal = 1 << 1, + kLinkTypeVGA = 1 << 2, + kLinkTypeHDMI = 1 << 3, + kLinkTypeDVI = 1 << 4, + kLinkTypeDisplayPort = 1 << 5, + kLinkTypeNetwork = 1 << 6 +}; +CHECK_TYPE(OutputLinkTypes, 4, 4); + +// Result of the QueryOutputProtectionStatus() call. +enum QueryResult : uint32_t { kQuerySucceeded = 0, kQueryFailed }; +CHECK_TYPE(QueryResult, 4, 4); + +// The Initialization Data Type. The valid types are defined in the spec: +// https://w3c.github.io/encrypted-media/format-registry/initdata/index.html#registry +enum InitDataType : uint32_t { kCenc = 0, kKeyIds = 1, kWebM = 2 }; +CHECK_TYPE(InitDataType, 4, 4); + +// The type of session to create. The valid types are defined in the spec: +// https://w3c.github.io/encrypted-media/#dom-mediakeysessiontype +enum SessionType : uint32_t { + kTemporary = 0, + kPersistentLicense = 1 +}; +CHECK_TYPE(SessionType, 4, 4); + +// The type of the message event. The valid types are defined in the spec: +// https://w3c.github.io/encrypted-media/#dom-mediakeymessagetype +enum MessageType : uint32_t { + kLicenseRequest = 0, + kLicenseRenewal = 1, + kLicenseRelease = 2, + kIndividualizationRequest = 3 +}; +CHECK_TYPE(MessageType, 4, 4); + +enum HdcpVersion : uint32_t { + kHdcpVersionNone, + kHdcpVersion1_0, + kHdcpVersion1_1, + kHdcpVersion1_2, + kHdcpVersion1_3, + kHdcpVersion1_4, + kHdcpVersion2_0, + kHdcpVersion2_1, + kHdcpVersion2_2, + kHdcpVersion2_3 +}; +CHECK_TYPE(HdcpVersion, 4, 4); + +struct Policy { + HdcpVersion min_hdcp_version; +}; +CHECK_TYPE(Policy, 4, 4); + +// Represents a buffer created by Allocator implementations. +class CDM_CLASS_API Buffer { + public: + // Destroys the buffer in the same context as it was created. + virtual void Destroy() = 0; + + virtual uint32_t Capacity() const = 0; + virtual uint8_t* Data() = 0; + virtual void SetSize(uint32_t size) = 0; + virtual uint32_t Size() const = 0; + + protected: + Buffer() {} + virtual ~Buffer() {} + + private: + Buffer(const Buffer&); + void operator=(const Buffer&); +}; + +// Represents a decrypted block that has not been decoded. +class CDM_CLASS_API DecryptedBlock { + public: + virtual void SetDecryptedBuffer(Buffer* buffer) = 0; + virtual Buffer* DecryptedBuffer() = 0; + + // TODO(tomfinegan): Figure out if timestamp is really needed. If it is not, + // we can just pass Buffer pointers around. + virtual void SetTimestamp(int64_t timestamp) = 0; + virtual int64_t Timestamp() const = 0; + + protected: + DecryptedBlock() {} + virtual ~DecryptedBlock() {} +}; + +// This intentionally avoids using an enum, since it will be used to do math +// with other enums, which is deprecated in C++20. +using VideoPlane = uint32_t; +constexpr VideoPlane kYPlane = 0; +constexpr VideoPlane kUPlane = 1; +constexpr VideoPlane kVPlane = 2; +constexpr VideoPlane kMaxPlanes = 3; +CHECK_TYPE(VideoPlane, 4, 4); + +class CDM_CLASS_API VideoFrame { + public: + virtual void SetFormat(VideoFormat format) = 0; + virtual VideoFormat Format() const = 0; + + virtual void SetSize(cdm::Size size) = 0; + virtual cdm::Size Size() const = 0; + + virtual void SetFrameBuffer(Buffer* frame_buffer) = 0; + virtual Buffer* FrameBuffer() = 0; + + virtual void SetPlaneOffset(VideoPlane plane, uint32_t offset) = 0; + virtual uint32_t PlaneOffset(VideoPlane plane) = 0; + + virtual void SetStride(VideoPlane plane, uint32_t stride) = 0; + virtual uint32_t Stride(VideoPlane plane) = 0; + + // Sets and gets the presentation timestamp which is in microseconds. + virtual void SetTimestamp(int64_t timestamp) = 0; + virtual int64_t Timestamp() const = 0; + + protected: + VideoFrame() {} + virtual ~VideoFrame() {} +}; + +// Represents a decoded video frame. The CDM should call the interface methods +// to set the frame attributes. See DecryptAndDecodeFrame(). +class CDM_CLASS_API VideoFrame_2 { + public: + virtual void SetFormat(VideoFormat format) = 0; + virtual void SetSize(cdm::Size size) = 0; + virtual void SetFrameBuffer(Buffer* frame_buffer) = 0; + virtual void SetPlaneOffset(VideoPlane plane, uint32_t offset) = 0; + virtual void SetStride(VideoPlane plane, uint32_t stride) = 0; + // Sets the presentation timestamp which is in microseconds. + virtual void SetTimestamp(int64_t timestamp) = 0; + virtual void SetColorSpace(ColorSpace color_space) = 0; + + protected: + VideoFrame_2() {} + virtual ~VideoFrame_2() {} +}; + +// Represents decrypted and decoded audio frames. AudioFrames can contain +// multiple audio output buffers, which are serialized into this format: +// +// |<------------------- serialized audio buffer ------------------->| +// | int64_t timestamp | int64_t length | length bytes of audio data | +// +// For example, with three audio output buffers, the AudioFrames will look +// like this: +// +// |<----------------- AudioFrames ------------------>| +// | audio buffer 0 | audio buffer 1 | audio buffer 2 | +class CDM_CLASS_API AudioFrames { + public: + virtual void SetFrameBuffer(Buffer* buffer) = 0; + virtual Buffer* FrameBuffer() = 0; + + // The CDM must call this method, providing a valid format, when providing + // frame buffers. Planar data should be stored end to end; e.g., + // |ch1 sample1||ch1 sample2|....|ch1 sample_last||ch2 sample1|... + virtual void SetFormat(AudioFormat format) = 0; + virtual AudioFormat Format() const = 0; + + protected: + AudioFrames() {} + virtual ~AudioFrames() {} +}; + +// FileIO interface provides a way for the CDM to store data in a file in +// persistent storage. This interface aims only at providing basic read/write +// capabilities and should not be used as a full fledged file IO API. +// Each CDM and origin (e.g. HTTPS, "foo.example.com", 443) combination has +// its own persistent storage. All instances of a given CDM associated with a +// given origin share the same persistent storage. +// Note to implementors of this interface: +// Per-origin storage and the ability for users to clear it are important. +// See http://www.w3.org/TR/encrypted-media/#privacy-storedinfo. +class CDM_CLASS_API FileIO { + public: + // Opens the file with |file_name| for read and write. + // FileIOClient::OnOpenComplete() will be called after the opening + // operation finishes. + // - When the file is opened by a CDM instance, it will be classified as "in + // use". In this case other CDM instances in the same domain may receive + // kInUse status when trying to open it. + // - |file_name| must only contain letters (A-Za-z), digits(0-9), or "._-". + // It must not start with an underscore ('_'), and must be at least 1 + // character and no more than 256 characters long. + virtual void Open(const char* file_name, uint32_t file_name_size) = 0; + + // Reads the contents of the file. FileIOClient::OnReadComplete() will be + // called with the read status. Read() should not be called if a previous + // Read() or Write() call is still pending; otherwise OnReadComplete() will + // be called with kInUse. + virtual void Read() = 0; + + // Writes |data_size| bytes of |data| into the file. + // FileIOClient::OnWriteComplete() will be called with the write status. + // All existing contents in the file will be overwritten. Calling Write() with + // NULL |data| will clear all contents in the file. Write() should not be + // called if a previous Write() or Read() call is still pending; otherwise + // OnWriteComplete() will be called with kInUse. + virtual void Write(const uint8_t* data, uint32_t data_size) = 0; + + // Closes the file if opened, destroys this FileIO object and releases any + // resources allocated. The CDM must call this method when it finished using + // this object. A FileIO object must not be used after Close() is called. + virtual void Close() = 0; + + protected: + FileIO() {} + virtual ~FileIO() {} +}; + +// Responses to FileIO calls. All responses will be called asynchronously. +// When kError is returned, the FileIO object could be in an error state. All +// following calls (other than Close()) could return kError. The CDM should +// still call Close() to destroy the FileIO object. +class CDM_CLASS_API FileIOClient { + public: + enum class Status : uint32_t { kSuccess = 0, kInUse, kError }; + + // Response to a FileIO::Open() call with the open |status|. + virtual void OnOpenComplete(Status status) = 0; + + // Response to a FileIO::Read() call to provide |data_size| bytes of |data| + // read from the file. + // - kSuccess indicates that all contents of the file has been successfully + // read. In this case, 0 |data_size| means that the file is empty. + // - kInUse indicates that there are other read/write operations pending. + // - kError indicates read failure, e.g. the storage is not open or cannot be + // fully read. + virtual void OnReadComplete(Status status, + const uint8_t* data, + uint32_t data_size) = 0; + + // Response to a FileIO::Write() call. + // - kSuccess indicates that all the data has been written into the file + // successfully. + // - kInUse indicates that there are other read/write operations pending. + // - kError indicates write failure, e.g. the storage is not open or cannot be + // fully written. Upon write failure, the contents of the file should be + // regarded as corrupt and should not used. + virtual void OnWriteComplete(Status status) = 0; + + protected: + FileIOClient() {} + virtual ~FileIOClient() {} +}; + +// Metrics that will be reported from the CDM through the ReportMetrics() +// function. To add a new metric, please add it to the end of this enum list +// without changing any existing enum values. +// Note: For forward compatibility, Host implementations must gracefully handle +// unexpected (new) enum values, e.g. no-op. +enum MetricName : uint32_t { + kSdkVersion, + kCertificateSerialNumber, + kDecoderBypassBlockCount, +}; +CHECK_TYPE(MetricName, 4, 4); + +class CDM_CLASS_API Host_10; +class CDM_CLASS_API Host_11; +class CDM_CLASS_API Host_12; + +// ContentDecryptionModule interface that all CDMs need to implement. +// The interface is versioned for backward compatibility. +// Note: ContentDecryptionModule implementations must use the allocator +// provided in CreateCdmInstance() to allocate any Buffer that needs to +// be passed back to the caller. Implementations must call Buffer::Destroy() +// when a Buffer is created that will never be returned to the caller. +class CDM_CLASS_API ContentDecryptionModule_10 { + public: + static const int kVersion = 10; + static const bool kIsStable = true; + typedef Host_10 Host; + + // Initializes the CDM instance, providing information about permitted + // functionalities. The CDM must respond by calling Host::OnInitialized() + // with whether the initialization succeeded. No other calls will be made by + // the host before Host::OnInitialized() returns. + // If |allow_distinctive_identifier| is false, messages from the CDM, + // such as message events, must not contain a Distinctive Identifier, + // even in an encrypted form. + // If |allow_persistent_state| is false, the CDM must not attempt to + // persist state. Calls to CreateFileIO() will fail. + // If |use_hw_secure_codecs| is true, the CDM must ensure the decryption key + // and video buffers (compressed and uncompressed) are securely protected by + // hardware. + virtual void Initialize(bool allow_distinctive_identifier, + bool allow_persistent_state, + bool use_hw_secure_codecs) = 0; + + // Gets the key status if the CDM has a hypothetical key with the |policy|. + // The CDM must respond by calling either Host::OnResolveKeyStatusPromise() + // with the result key status or Host::OnRejectPromise() if an unexpected + // error happened or this method is not supported. + virtual void GetStatusForPolicy(uint32_t promise_id, + const Policy& policy) = 0; + + // SetServerCertificate(), CreateSessionAndGenerateRequest(), LoadSession(), + // UpdateSession(), CloseSession(), and RemoveSession() all accept a + // |promise_id|, which must be passed to the completion Host method + // (e.g. Host::OnResolveNewSessionPromise()). + + // Provides a server certificate to be used to encrypt messages to the + // license server. The CDM must respond by calling either + // Host::OnResolvePromise() or Host::OnRejectPromise(). + // If the CDM does not support server certificates, the promise should be + // rejected with kExceptionNotSupportedError. If |server_certificate_data| + // is empty, reject with kExceptionTypeError. Any other error should be + // rejected with kExceptionInvalidStateError or kExceptionQuotaExceededError. + // TODO(crbug.com/796417): Add support for the promise to return true or + // false, rather than using kExceptionNotSupportedError to mean false. + virtual void SetServerCertificate(uint32_t promise_id, + const uint8_t* server_certificate_data, + uint32_t server_certificate_data_size) = 0; + + // Creates a session given |session_type|, |init_data_type|, and |init_data|. + // The CDM must respond by calling either Host::OnResolveNewSessionPromise() + // or Host::OnRejectPromise(). + virtual void CreateSessionAndGenerateRequest(uint32_t promise_id, + SessionType session_type, + InitDataType init_data_type, + const uint8_t* init_data, + uint32_t init_data_size) = 0; + + // Loads the session of type |session_type| specified by |session_id|. + // The CDM must respond by calling either Host::OnResolveNewSessionPromise() + // or Host::OnRejectPromise(). If the session is not found, call + // Host::OnResolveNewSessionPromise() with session_id = NULL. + virtual void LoadSession(uint32_t promise_id, + SessionType session_type, + const char* session_id, + uint32_t session_id_size) = 0; + + // Updates the session with |response|. The CDM must respond by calling + // either Host::OnResolvePromise() or Host::OnRejectPromise(). + virtual void UpdateSession(uint32_t promise_id, + const char* session_id, + uint32_t session_id_size, + const uint8_t* response, + uint32_t response_size) = 0; + + // Requests that the CDM close the session. The CDM must respond by calling + // either Host::OnResolvePromise() or Host::OnRejectPromise() when the request + // has been processed. This may be before the session is closed. Once the + // session is closed, Host::OnSessionClosed() must also be called. + virtual void CloseSession(uint32_t promise_id, + const char* session_id, + uint32_t session_id_size) = 0; + + // Removes any stored session data associated with this session. Will only be + // called for persistent sessions. The CDM must respond by calling either + // Host::OnResolvePromise() or Host::OnRejectPromise() when the request has + // been processed. + virtual void RemoveSession(uint32_t promise_id, + const char* session_id, + uint32_t session_id_size) = 0; + + // Performs scheduled operation with |context| when the timer fires. + virtual void TimerExpired(void* context) = 0; + + // Decrypts the |encrypted_buffer|. + // + // Returns kSuccess if decryption succeeded, in which case the callee + // should have filled the |decrypted_buffer| and passed the ownership of + // |data| in |decrypted_buffer| to the caller. + // Returns kNoKey if the CDM did not have the necessary decryption key + // to decrypt. + // Returns kDecryptError if any other error happened. + // If the return value is not kSuccess, |decrypted_buffer| should be ignored + // by the caller. +#ifdef TARGET_WEBOS + virtual Status Decrypt(const InputBuffer_2& encrypted_buffer, + DecryptedBlock* decrypted_buffer, + StreamType stream_type) = 0; +#else + virtual Status Decrypt(const InputBuffer_2& encrypted_buffer, + DecryptedBlock* decrypted_buffer) = 0; +#endif + + // Initializes the CDM audio decoder with |audio_decoder_config|. This + // function must be called before DecryptAndDecodeSamples() is called. + // + // Returns kSuccess if the |audio_decoder_config| is supported and the CDM + // audio decoder is successfully initialized. + // Returns kInitializationError if |audio_decoder_config| is not supported. + // The CDM may still be able to do Decrypt(). + // Returns kDeferredInitialization if the CDM is not ready to initialize the + // decoder at this time. Must call Host::OnDeferredInitializationDone() once + // initialization is complete. + virtual Status InitializeAudioDecoder( + const AudioDecoderConfig_2& audio_decoder_config) = 0; + + // Initializes the CDM video decoder with |video_decoder_config|. This + // function must be called before DecryptAndDecodeFrame() is called. + // + // Returns kSuccess if the |video_decoder_config| is supported and the CDM + // video decoder is successfully initialized. + // Returns kInitializationError if |video_decoder_config| is not supported. + // The CDM may still be able to do Decrypt(). + // Returns kDeferredInitialization if the CDM is not ready to initialize the + // decoder at this time. Must call Host::OnDeferredInitializationDone() once + // initialization is complete. + virtual Status InitializeVideoDecoder( + const VideoDecoderConfig_2& video_decoder_config) = 0; + + // De-initializes the CDM decoder and sets it to an uninitialized state. The + // caller can initialize the decoder again after this call to re-initialize + // it. This can be used to reconfigure the decoder if the configuration + // changes. + virtual void DeinitializeDecoder(StreamType decoder_type) = 0; + + // Resets the CDM decoder to an initialized clean state. All internal buffers + // MUST be flushed. + virtual void ResetDecoder(StreamType decoder_type) = 0; + + // Decrypts the |encrypted_buffer| and decodes the decrypted buffer into a + // |video_frame|. Upon end-of-stream, the caller should call this function + // repeatedly with empty |encrypted_buffer| (|data| == NULL) until + // kNeedMoreData is returned. + // + // Returns kSuccess if decryption and decoding both succeeded, in which case + // the callee will have filled the |video_frame| and passed the ownership of + // |frame_buffer| in |video_frame| to the caller. + // Returns kNoKey if the CDM did not have the necessary decryption key + // to decrypt. + // Returns kNeedMoreData if more data was needed by the decoder to generate + // a decoded frame (e.g. during initialization and end-of-stream). + // Returns kDecryptError if any decryption error happened. + // Returns kDecodeError if any decoding error happened. + // If the return value is not kSuccess, |video_frame| should be ignored by + // the caller. + virtual Status DecryptAndDecodeFrame(const InputBuffer_2& encrypted_buffer, + VideoFrame* video_frame) = 0; + + // Decrypts the |encrypted_buffer| and decodes the decrypted buffer into + // |audio_frames|. Upon end-of-stream, the caller should call this function + // repeatedly with empty |encrypted_buffer| (|data| == NULL) until only empty + // |audio_frames| is produced. + // + // Returns kSuccess if decryption and decoding both succeeded, in which case + // the callee will have filled |audio_frames| and passed the ownership of + // |data| in |audio_frames| to the caller. + // Returns kNoKey if the CDM did not have the necessary decryption key + // to decrypt. + // Returns kNeedMoreData if more data was needed by the decoder to generate + // audio samples (e.g. during initialization and end-of-stream). + // Returns kDecryptError if any decryption error happened. + // Returns kDecodeError if any decoding error happened. + // If the return value is not kSuccess, |audio_frames| should be ignored by + // the caller. + virtual Status DecryptAndDecodeSamples(const InputBuffer_2& encrypted_buffer, + AudioFrames* audio_frames) = 0; + + // Called by the host after a platform challenge was initiated via + // Host::SendPlatformChallenge(). + virtual void OnPlatformChallengeResponse( + const PlatformChallengeResponse& response) = 0; + + // Called by the host after a call to Host::QueryOutputProtectionStatus(). The + // |link_mask| is a bit mask of OutputLinkTypes and |output_protection_mask| + // is a bit mask of OutputProtectionMethods. If |result| is kQueryFailed, + // then |link_mask| and |output_protection_mask| are undefined and should + // be ignored. + virtual void OnQueryOutputProtectionStatus( + QueryResult result, + uint32_t link_mask, + uint32_t output_protection_mask) = 0; + + // Called by the host after a call to Host::RequestStorageId(). If the + // version of the storage ID requested is available, |storage_id| and + // |storage_id_size| are set appropriately. |version| will be the same as + // what was requested, unless 0 (latest) was requested, in which case + // |version| will be the actual version number for the |storage_id| returned. + // If the requested version is not available, null/zero will be provided as + // |storage_id| and |storage_id_size|, respectively, and |version| should be + // ignored. + virtual void OnStorageId(uint32_t version, + const uint8_t* storage_id, + uint32_t storage_id_size) = 0; + + // Destroys the object in the same context as it was created. + virtual void Destroy() = 0; + + protected: + ContentDecryptionModule_10() {} + virtual ~ContentDecryptionModule_10() {} +}; + +// ContentDecryptionModule interface that all CDMs need to implement. +// The interface is versioned for backward compatibility. +// Note: ContentDecryptionModule implementations must use the allocator +// provided in CreateCdmInstance() to allocate any Buffer that needs to +// be passed back to the caller. Implementations must call Buffer::Destroy() +// when a Buffer is created that will never be returned to the caller. +class CDM_CLASS_API ContentDecryptionModule_11 { + public: + static const int kVersion = 11; + static const bool kIsStable = true; + typedef Host_11 Host; + + // Initializes the CDM instance, providing information about permitted + // functionalities. The CDM must respond by calling Host::OnInitialized() + // with whether the initialization succeeded. No other calls will be made by + // the host before Host::OnInitialized() returns. + // If |allow_distinctive_identifier| is false, messages from the CDM, + // such as message events, must not contain a Distinctive Identifier, + // even in an encrypted form. + // If |allow_persistent_state| is false, the CDM must not attempt to + // persist state. Calls to CreateFileIO() will fail. + // If |use_hw_secure_codecs| is true, the CDM must ensure the decryption key + // and video buffers (compressed and uncompressed) are securely protected by + // hardware. + virtual void Initialize(bool allow_distinctive_identifier, + bool allow_persistent_state, + bool use_hw_secure_codecs) = 0; + + // Gets the key status if the CDM has a hypothetical key with the |policy|. + // The CDM must respond by calling either Host::OnResolveKeyStatusPromise() + // with the result key status or Host::OnRejectPromise() if an unexpected + // error happened or this method is not supported. + virtual void GetStatusForPolicy(uint32_t promise_id, + const Policy& policy) = 0; + + // SetServerCertificate(), CreateSessionAndGenerateRequest(), LoadSession(), + // UpdateSession(), CloseSession(), and RemoveSession() all accept a + // |promise_id|, which must be passed to the completion Host method + // (e.g. Host::OnResolveNewSessionPromise()). + + // Provides a server certificate to be used to encrypt messages to the + // license server. The CDM must respond by calling either + // Host::OnResolvePromise() or Host::OnRejectPromise(). + // If the CDM does not support server certificates, the promise should be + // rejected with kExceptionNotSupportedError. If |server_certificate_data| + // is empty, reject with kExceptionTypeError. Any other error should be + // rejected with kExceptionInvalidStateError or kExceptionQuotaExceededError. + // TODO(crbug.com/796417): Add support for the promise to return true or + // false, rather than using kExceptionNotSupportedError to mean false. + virtual void SetServerCertificate(uint32_t promise_id, + const uint8_t* server_certificate_data, + uint32_t server_certificate_data_size) = 0; + + // Creates a session given |session_type|, |init_data_type|, and |init_data|. + // The CDM must respond by calling either Host::OnResolveNewSessionPromise() + // or Host::OnRejectPromise(). + virtual void CreateSessionAndGenerateRequest(uint32_t promise_id, + SessionType session_type, + InitDataType init_data_type, + const uint8_t* init_data, + uint32_t init_data_size) = 0; + + // Loads the session of type |session_type| specified by |session_id|. + // The CDM must respond by calling either Host::OnResolveNewSessionPromise() + // or Host::OnRejectPromise(). If the session is not found, call + // Host::OnResolveNewSessionPromise() with session_id = NULL. + virtual void LoadSession(uint32_t promise_id, + SessionType session_type, + const char* session_id, + uint32_t session_id_size) = 0; + + // Updates the session with |response|. The CDM must respond by calling + // either Host::OnResolvePromise() or Host::OnRejectPromise(). + virtual void UpdateSession(uint32_t promise_id, + const char* session_id, + uint32_t session_id_size, + const uint8_t* response, + uint32_t response_size) = 0; + + // Requests that the CDM close the session. The CDM must respond by calling + // either Host::OnResolvePromise() or Host::OnRejectPromise() when the request + // has been processed. This may be before the session is closed. Once the + // session is closed, Host::OnSessionClosed() must also be called. + virtual void CloseSession(uint32_t promise_id, + const char* session_id, + uint32_t session_id_size) = 0; + + // Removes any stored session data associated with this session. Will only be + // called for persistent sessions. The CDM must respond by calling either + // Host::OnResolvePromise() or Host::OnRejectPromise() when the request has + // been processed. + virtual void RemoveSession(uint32_t promise_id, + const char* session_id, + uint32_t session_id_size) = 0; + + // Performs scheduled operation with |context| when the timer fires. + virtual void TimerExpired(void* context) = 0; + + // Decrypts the |encrypted_buffer|. + // + // Returns kSuccess if decryption succeeded, in which case the callee + // should have filled the |decrypted_buffer| and passed the ownership of + // |data| in |decrypted_buffer| to the caller. + // Returns kNoKey if the CDM did not have the necessary decryption key + // to decrypt. + // Returns kDecryptError if any other error happened. + // If the return value is not kSuccess, |decrypted_buffer| should be ignored + // by the caller. +#ifdef TARGET_WEBOS + virtual Status Decrypt(const InputBuffer_2& encrypted_buffer, + DecryptedBlock* decrypted_buffer, + StreamType stream_type) = 0; +#else + virtual Status Decrypt(const InputBuffer_2& encrypted_buffer, + DecryptedBlock* decrypted_buffer) = 0; +#endif + + // Initializes the CDM audio decoder with |audio_decoder_config|. This + // function must be called before DecryptAndDecodeSamples() is called. + // + // Returns kSuccess if the |audio_decoder_config| is supported and the CDM + // audio decoder is successfully initialized. + // Returns kInitializationError if |audio_decoder_config| is not supported. + // The CDM may still be able to do Decrypt(). + // Returns kDeferredInitialization if the CDM is not ready to initialize the + // decoder at this time. Must call Host::OnDeferredInitializationDone() once + // initialization is complete. + virtual Status InitializeAudioDecoder( + const AudioDecoderConfig_2& audio_decoder_config) = 0; + + // Initializes the CDM video decoder with |video_decoder_config|. This + // function must be called before DecryptAndDecodeFrame() is called. + // + // Returns kSuccess if the |video_decoder_config| is supported and the CDM + // video decoder is successfully initialized. + // Returns kInitializationError if |video_decoder_config| is not supported. + // The CDM may still be able to do Decrypt(). + // Returns kDeferredInitialization if the CDM is not ready to initialize the + // decoder at this time. Must call Host::OnDeferredInitializationDone() once + // initialization is complete. + virtual Status InitializeVideoDecoder( + const VideoDecoderConfig_2& video_decoder_config) = 0; + + // De-initializes the CDM decoder and sets it to an uninitialized state. The + // caller can initialize the decoder again after this call to re-initialize + // it. This can be used to reconfigure the decoder if the configuration + // changes. + virtual void DeinitializeDecoder(StreamType decoder_type) = 0; + + // Resets the CDM decoder to an initialized clean state. All internal buffers + // MUST be flushed. + virtual void ResetDecoder(StreamType decoder_type) = 0; + + // Decrypts the |encrypted_buffer| and decodes the decrypted buffer into a + // |video_frame|. Upon end-of-stream, the caller should call this function + // repeatedly with empty |encrypted_buffer| (|data| == NULL) until + // kNeedMoreData is returned. + // + // Returns kSuccess if decryption and decoding both succeeded, in which case + // the callee will have filled the |video_frame| and passed the ownership of + // |frame_buffer| in |video_frame| to the caller. + // Returns kNoKey if the CDM did not have the necessary decryption key + // to decrypt. + // Returns kNeedMoreData if more data was needed by the decoder to generate + // a decoded frame (e.g. during initialization and end-of-stream). + // Returns kDecryptError if any decryption error happened. + // Returns kDecodeError if any decoding error happened. + // If the return value is not kSuccess, |video_frame| should be ignored by + // the caller. + virtual Status DecryptAndDecodeFrame(const InputBuffer_2& encrypted_buffer, + VideoFrame* video_frame) = 0; + + // Decrypts the |encrypted_buffer| and decodes the decrypted buffer into + // |audio_frames|. Upon end-of-stream, the caller should call this function + // repeatedly with empty |encrypted_buffer| (|data| == NULL) until only empty + // |audio_frames| is produced. + // + // Returns kSuccess if decryption and decoding both succeeded, in which case + // the callee will have filled |audio_frames| and passed the ownership of + // |data| in |audio_frames| to the caller. + // Returns kNoKey if the CDM did not have the necessary decryption key + // to decrypt. + // Returns kNeedMoreData if more data was needed by the decoder to generate + // audio samples (e.g. during initialization and end-of-stream). + // Returns kDecryptError if any decryption error happened. + // Returns kDecodeError if any decoding error happened. + // If the return value is not kSuccess, |audio_frames| should be ignored by + // the caller. + virtual Status DecryptAndDecodeSamples(const InputBuffer_2& encrypted_buffer, + AudioFrames* audio_frames) = 0; + + // Called by the host after a platform challenge was initiated via + // Host::SendPlatformChallenge(). + virtual void OnPlatformChallengeResponse( + const PlatformChallengeResponse& response) = 0; + + // Called by the host after a call to Host::QueryOutputProtectionStatus(). The + // |link_mask| is a bit mask of OutputLinkTypes and |output_protection_mask| + // is a bit mask of OutputProtectionMethods. If |result| is kQueryFailed, + // then |link_mask| and |output_protection_mask| are undefined and should + // be ignored. + virtual void OnQueryOutputProtectionStatus( + QueryResult result, + uint32_t link_mask, + uint32_t output_protection_mask) = 0; + + // Called by the host after a call to Host::RequestStorageId(). If the + // version of the storage ID requested is available, |storage_id| and + // |storage_id_size| are set appropriately. |version| will be the same as + // what was requested, unless 0 (latest) was requested, in which case + // |version| will be the actual version number for the |storage_id| returned. + // If the requested version is not available, null/zero will be provided as + // |storage_id| and |storage_id_size|, respectively, and |version| should be + // ignored. + virtual void OnStorageId(uint32_t version, + const uint8_t* storage_id, + uint32_t storage_id_size) = 0; + + // Destroys the object in the same context as it was created. + virtual void Destroy() = 0; + + protected: + ContentDecryptionModule_11() {} + virtual ~ContentDecryptionModule_11() {} +}; + +// ----- Note: CDM interface(s) below still in development and not stable! ----- + +// ContentDecryptionModule interface that all CDMs need to implement. +// The interface is versioned for backward compatibility. +// Note: ContentDecryptionModule implementations must use the allocator +// provided in CreateCdmInstance() to allocate any Buffer that needs to +// be passed back to the caller. Implementations must call Buffer::Destroy() +// when a Buffer is created that will never be returned to the caller. +class CDM_CLASS_API ContentDecryptionModule_12 { + public: + static const int kVersion = 12; + static const bool kIsStable = false; + typedef Host_12 Host; + + // Initializes the CDM instance, providing information about permitted + // functionalities. The CDM must respond by calling Host::OnInitialized() + // with whether the initialization succeeded. No other calls will be made by + // the host before Host::OnInitialized() returns. + // If |allow_distinctive_identifier| is false, messages from the CDM, + // such as message events, must not contain a Distinctive Identifier, + // even in an encrypted form. + // If |allow_persistent_state| is false, the CDM must not attempt to + // persist state. Calls to CreateFileIO() will fail. + // If |use_hw_secure_codecs| is true, the CDM must ensure the decryption key + // and video buffers (compressed and uncompressed) are securely protected by + // hardware. + virtual void Initialize(bool allow_distinctive_identifier, + bool allow_persistent_state, + bool use_hw_secure_codecs) = 0; + + // Gets the key status if the CDM has a hypothetical key with the |policy|. + // The CDM must respond by calling either Host::OnResolveKeyStatusPromise() + // with the result key status or Host::OnRejectPromise() if an unexpected + // error happened or this method is not supported. + virtual void GetStatusForPolicy(uint32_t promise_id, + const Policy& policy) = 0; + + // SetServerCertificate(), CreateSessionAndGenerateRequest(), LoadSession(), + // UpdateSession(), CloseSession(), and RemoveSession() all accept a + // |promise_id|, which must be passed to the completion Host method + // (e.g. Host::OnResolveNewSessionPromise()). + + // Provides a server certificate to be used to encrypt messages to the + // license server. The CDM must respond by calling either + // Host::OnResolvePromise() or Host::OnRejectPromise(). + // If the CDM does not support server certificates, the promise should be + // rejected with kExceptionNotSupportedError. If |server_certificate_data| + // is empty, reject with kExceptionTypeError. Any other error should be + // rejected with kExceptionInvalidStateError or kExceptionQuotaExceededError. + // TODO(crbug.com/796417): Add support for the promise to return true or + // false, rather than using kExceptionNotSupportedError to mean false. + virtual void SetServerCertificate(uint32_t promise_id, + const uint8_t* server_certificate_data, + uint32_t server_certificate_data_size) = 0; + + // Creates a session given |session_type|, |init_data_type|, and |init_data|. + // The CDM must respond by calling either Host::OnResolveNewSessionPromise() + // or Host::OnRejectPromise(). + virtual void CreateSessionAndGenerateRequest(uint32_t promise_id, + SessionType session_type, + InitDataType init_data_type, + const uint8_t* init_data, + uint32_t init_data_size) = 0; + + // Loads the session of type |session_type| specified by |session_id|. + // The CDM must respond by calling either Host::OnResolveNewSessionPromise() + // or Host::OnRejectPromise(). If the session is not found, call + // Host::OnResolveNewSessionPromise() with session_id = NULL. + virtual void LoadSession(uint32_t promise_id, + SessionType session_type, + const char* session_id, + uint32_t session_id_size) = 0; + + // Updates the session with |response|. The CDM must respond by calling + // either Host::OnResolvePromise() or Host::OnRejectPromise(). + virtual void UpdateSession(uint32_t promise_id, + const char* session_id, + uint32_t session_id_size, + const uint8_t* response, + uint32_t response_size) = 0; + + // Requests that the CDM close the session. The CDM must respond by calling + // either Host::OnResolvePromise() or Host::OnRejectPromise() when the request + // has been processed. This may be before the session is closed. Once the + // session is closed, Host::OnSessionClosed() must also be called. + virtual void CloseSession(uint32_t promise_id, + const char* session_id, + uint32_t session_id_size) = 0; + + // Removes any stored session data associated with this session. Removes all + // license(s) and key(s) associated with the session, whether they are in + // memory, persistent store, or both. For persistent session types, other + // session data (e.g. record of license destruction) will be cleared as + // defined for each session type once a release message acknowledgment is + // processed by UpdateSession(). The CDM must respond by calling either + // Host::OnResolvePromise() or Host::OnRejectPromise() when the request has + // been processed. + virtual void RemoveSession(uint32_t promise_id, + const char* session_id, + uint32_t session_id_size) = 0; + + // Performs scheduled operation with |context| when the timer fires. + virtual void TimerExpired(void* context) = 0; + + // Decrypts the |encrypted_buffer|. + // + // Returns kSuccess if decryption succeeded, in which case the callee + // should have filled the |decrypted_buffer| and passed the ownership of + // |data| in |decrypted_buffer| to the caller. + // Returns kNoKey if the CDM did not have the necessary decryption key + // to decrypt. + // Returns kDecryptError if any other error happened. + // If the return value is not kSuccess, |decrypted_buffer| should be ignored + // by the caller. + virtual Status Decrypt(const InputBuffer_2& encrypted_buffer, + DecryptedBlock* decrypted_buffer) = 0; + + // Initializes the CDM audio decoder with |audio_decoder_config|. This + // function must be called before DecryptAndDecodeSamples() is called. + // + // Returns kSuccess if the |audio_decoder_config| is supported and the CDM + // audio decoder is successfully initialized. + // Returns kInitializationError if |audio_decoder_config| is not supported. + // The CDM may still be able to do Decrypt(). + // Returns kDeferredInitialization if the CDM is not ready to initialize the + // decoder at this time. Must call Host::OnDeferredInitializationDone() once + // initialization is complete. + virtual Status InitializeAudioDecoder( + const AudioDecoderConfig_2& audio_decoder_config) = 0; + + // Initializes the CDM video decoder with |video_decoder_config|. This + // function must be called before DecryptAndDecodeFrame() is called. + // + // Returns kSuccess if the |video_decoder_config| is supported and the CDM + // video decoder is successfully initialized. + // Returns kInitializationError if |video_decoder_config| is not supported. + // The CDM may still be able to do Decrypt(). + // Returns kDeferredInitialization if the CDM is not ready to initialize the + // decoder at this time. Must call Host::OnDeferredInitializationDone() once + // initialization is complete. + virtual Status InitializeVideoDecoder( + const VideoDecoderConfig_3& video_decoder_config) = 0; + + // De-initializes the CDM decoder and sets it to an uninitialized state. The + // caller can initialize the decoder again after this call to re-initialize + // it. This can be used to reconfigure the decoder if the configuration + // changes. + virtual void DeinitializeDecoder(StreamType decoder_type) = 0; + + // Resets the CDM decoder to an initialized clean state. All internal buffers + // MUST be flushed. + virtual void ResetDecoder(StreamType decoder_type) = 0; + + // Decrypts the |encrypted_buffer| and decodes the decrypted buffer into a + // |video_frame|. Upon end-of-stream, the caller should call this function + // repeatedly with empty |encrypted_buffer| (|data| == NULL) until + // kNeedMoreData is returned. + // + // Returns kSuccess if decryption and decoding both succeeded, in which case + // the callee will have filled the |video_frame| and passed the ownership of + // |frame_buffer| in |video_frame| to the caller. + // Returns kNoKey if the CDM did not have the necessary decryption key + // to decrypt. + // Returns kNeedMoreData if more data was needed by the decoder to generate + // a decoded frame (e.g. during initialization and end-of-stream). + // Returns kDecryptError if any decryption error happened. + // Returns kDecodeError if any decoding error happened. + // If the return value is not kSuccess, |video_frame| should be ignored by + // the caller. + virtual Status DecryptAndDecodeFrame(const InputBuffer_2& encrypted_buffer, + VideoFrame_2* video_frame) = 0; + + // Decrypts the |encrypted_buffer| and decodes the decrypted buffer into + // |audio_frames|. Upon end-of-stream, the caller should call this function + // repeatedly with empty |encrypted_buffer| (|data| == NULL) until only empty + // |audio_frames| is produced. + // + // Returns kSuccess if decryption and decoding both succeeded, in which case + // the callee will have filled |audio_frames| and passed the ownership of + // |data| in |audio_frames| to the caller. + // Returns kNoKey if the CDM did not have the necessary decryption key + // to decrypt. + // Returns kNeedMoreData if more data was needed by the decoder to generate + // audio samples (e.g. during initialization and end-of-stream). + // Returns kDecryptError if any decryption error happened. + // Returns kDecodeError if any decoding error happened. + // If the return value is not kSuccess, |audio_frames| should be ignored by + // the caller. + virtual Status DecryptAndDecodeSamples(const InputBuffer_2& encrypted_buffer, + AudioFrames* audio_frames) = 0; + + // Called by the host after a platform challenge was initiated via + // Host::SendPlatformChallenge(). + virtual void OnPlatformChallengeResponse( + const PlatformChallengeResponse& response) = 0; + + // Called by the host after a call to Host::QueryOutputProtectionStatus(). The + // |link_mask| is a bit mask of OutputLinkTypes and |output_protection_mask| + // is a bit mask of OutputProtectionMethods. If |result| is kQueryFailed, + // then |link_mask| and |output_protection_mask| are undefined and should + // be ignored. + virtual void OnQueryOutputProtectionStatus( + QueryResult result, + uint32_t link_mask, + uint32_t output_protection_mask) = 0; + + // Called by the host after a call to Host::RequestStorageId(). If the + // version of the storage ID requested is available, |storage_id| and + // |storage_id_size| are set appropriately. |version| will be the same as + // what was requested, unless 0 (latest) was requested, in which case + // |version| will be the actual version number for the |storage_id| returned. + // If the requested version is not available, null/zero will be provided as + // |storage_id| and |storage_id_size|, respectively, and |version| should be + // ignored. + virtual void OnStorageId(uint32_t version, + const uint8_t* storage_id, + uint32_t storage_id_size) = 0; + + // Destroys the object in the same context as it was created. + virtual void Destroy() = 0; + + protected: + ContentDecryptionModule_12() {} + virtual ~ContentDecryptionModule_12() {} +}; + +class CDM_CLASS_API Host_10 { + public: + static const int kVersion = 10; + + // Returns a Buffer* containing non-zero members upon success, or NULL on + // failure. The caller owns the Buffer* after this call. The buffer is not + // guaranteed to be zero initialized. The capacity of the allocated Buffer + // is guaranteed to be not less than |capacity|. + virtual Buffer* Allocate(uint32_t capacity) = 0; + + // Requests the host to call ContentDecryptionModule::TimerFired() |delay_ms| + // from now with |context|. + virtual void SetTimer(int64_t delay_ms, void* context) = 0; + + // Returns the current wall time. + virtual Time GetCurrentWallTime() = 0; + + // Called by the CDM with the result after the CDM instance was initialized. + virtual void OnInitialized(bool success) = 0; + + // Called by the CDM when a key status is available in response to + // GetStatusForPolicy(). + virtual void OnResolveKeyStatusPromise(uint32_t promise_id, + KeyStatus key_status) = 0; + + // Called by the CDM when a session is created or loaded and the value for the + // MediaKeySession's sessionId attribute is available (|session_id|). + // This must be called before OnSessionMessage() or + // OnSessionKeysChange() is called for the same session. |session_id_size| + // should not include null termination. + // When called in response to LoadSession(), the |session_id| must be the + // same as the |session_id| passed in LoadSession(), or NULL if the + // session could not be loaded. + virtual void OnResolveNewSessionPromise(uint32_t promise_id, + const char* session_id, + uint32_t session_id_size) = 0; + + // Called by the CDM when a session is updated or released. + virtual void OnResolvePromise(uint32_t promise_id) = 0; + + // Called by the CDM when an error occurs as a result of one of the + // ContentDecryptionModule calls that accept a |promise_id|. + // |exception| must be specified. |error_message| and |system_code| + // are optional. |error_message_size| should not include null termination. + virtual void OnRejectPromise(uint32_t promise_id, + Exception exception, + uint32_t system_code, + const char* error_message, + uint32_t error_message_size) = 0; + + // Called by the CDM when it has a message for session |session_id|. + // Size parameters should not include null termination. + virtual void OnSessionMessage(const char* session_id, + uint32_t session_id_size, + MessageType message_type, + const char* message, + uint32_t message_size) = 0; + + // Called by the CDM when there has been a change in keys or their status for + // session |session_id|. |has_additional_usable_key| should be set if a + // key is newly usable (e.g. new key available, previously expired key has + // been renewed, etc.) and the browser should attempt to resume playback. + // |keys_info| is the list of key IDs for this session along with their + // current status. |keys_info_count| is the number of entries in |keys_info|. + // Size parameter for |session_id| should not include null termination. + virtual void OnSessionKeysChange(const char* session_id, + uint32_t session_id_size, + bool has_additional_usable_key, + const KeyInformation* keys_info, + uint32_t keys_info_count) = 0; + + // Called by the CDM when there has been a change in the expiration time for + // session |session_id|. This can happen as the result of an Update() call + // or some other event. If this happens as a result of a call to Update(), + // it must be called before resolving the Update() promise. |new_expiry_time| + // represents the time after which the key(s) in the session will no longer + // be usable for decryption. It can be 0 if no such time exists or if the + // license explicitly never expires. Size parameter should not include null + // termination. + virtual void OnExpirationChange(const char* session_id, + uint32_t session_id_size, + Time new_expiry_time) = 0; + + // Called by the CDM when session |session_id| is closed. Size + // parameter should not include null termination. + virtual void OnSessionClosed(const char* session_id, + uint32_t session_id_size) = 0; + + // The following are optional methods that may not be implemented on all + // platforms. + + // Sends a platform challenge for the given |service_id|. |challenge| is at + // most 256 bits of data to be signed. Once the challenge has been completed, + // the host will call ContentDecryptionModule::OnPlatformChallengeResponse() + // with the signed challenge response and platform certificate. Size + // parameters should not include null termination. + virtual void SendPlatformChallenge(const char* service_id, + uint32_t service_id_size, + const char* challenge, + uint32_t challenge_size) = 0; + + // Attempts to enable output protection (e.g. HDCP) on the display link. The + // |desired_protection_mask| is a bit mask of OutputProtectionMethods. No + // status callback is issued, the CDM must call QueryOutputProtectionStatus() + // periodically to ensure the desired protections are applied. + virtual void EnableOutputProtection(uint32_t desired_protection_mask) = 0; + + // Requests the current output protection status. Once the host has the status + // it will call ContentDecryptionModule::OnQueryOutputProtectionStatus(). + virtual void QueryOutputProtectionStatus() = 0; + + // Must be called by the CDM if it returned kDeferredInitialization during + // InitializeAudioDecoder() or InitializeVideoDecoder(). + virtual void OnDeferredInitializationDone(StreamType stream_type, + Status decoder_status) = 0; + + // Creates a FileIO object from the host to do file IO operation. Returns NULL + // if a FileIO object cannot be obtained. Once a valid FileIO object is + // returned, |client| must be valid until FileIO::Close() is called. The + // CDM can call this method multiple times to operate on different files. + virtual FileIO* CreateFileIO(FileIOClient* client) = 0; + + // Requests a specific version of the storage ID. A storage ID is a stable, + // device specific ID used by the CDM to securely store persistent data. The + // ID will be returned by the host via ContentDecryptionModule::OnStorageId(). + // If |version| is 0, the latest version will be returned. All |version|s + // that are greater than or equal to 0x80000000 are reserved for the CDM and + // should not be supported or returned by the host. The CDM must not expose + // the ID outside the client device, even in encrypted form. + virtual void RequestStorageId(uint32_t version) = 0; + + protected: + Host_10() {} + virtual ~Host_10() {} +}; + +class CDM_CLASS_API Host_11 { + public: + static const int kVersion = 11; + + // Returns a Buffer* containing non-zero members upon success, or NULL on + // failure. The caller owns the Buffer* after this call. The buffer is not + // guaranteed to be zero initialized. The capacity of the allocated Buffer + // is guaranteed to be not less than |capacity|. + virtual Buffer* Allocate(uint32_t capacity) = 0; + + // Requests the host to call ContentDecryptionModule::TimerFired() |delay_ms| + // from now with |context|. + virtual void SetTimer(int64_t delay_ms, void* context) = 0; + + // Returns the current wall time. + virtual Time GetCurrentWallTime() = 0; + + // Called by the CDM with the result after the CDM instance was initialized. + virtual void OnInitialized(bool success) = 0; + + // Called by the CDM when a key status is available in response to + // GetStatusForPolicy(). + virtual void OnResolveKeyStatusPromise(uint32_t promise_id, + KeyStatus key_status) = 0; + + // Called by the CDM when a session is created or loaded and the value for the + // MediaKeySession's sessionId attribute is available (|session_id|). + // This must be called before OnSessionMessage() or + // OnSessionKeysChange() is called for the same session. |session_id_size| + // should not include null termination. + // When called in response to LoadSession(), the |session_id| must be the + // same as the |session_id| passed in LoadSession(), or NULL if the + // session could not be loaded. + virtual void OnResolveNewSessionPromise(uint32_t promise_id, + const char* session_id, + uint32_t session_id_size) = 0; + + // Called by the CDM when a session is updated or released. + virtual void OnResolvePromise(uint32_t promise_id) = 0; + + // Called by the CDM when an error occurs as a result of one of the + // ContentDecryptionModule calls that accept a |promise_id|. + // |exception| must be specified. |error_message| and |system_code| + // are optional. |error_message_size| should not include null termination. + virtual void OnRejectPromise(uint32_t promise_id, + Exception exception, + uint32_t system_code, + const char* error_message, + uint32_t error_message_size) = 0; + + // Called by the CDM when it has a message for session |session_id|. + // Size parameters should not include null termination. + virtual void OnSessionMessage(const char* session_id, + uint32_t session_id_size, + MessageType message_type, + const char* message, + uint32_t message_size) = 0; + + // Called by the CDM when there has been a change in keys or their status for + // session |session_id|. |has_additional_usable_key| should be set if a + // key is newly usable (e.g. new key available, previously expired key has + // been renewed, etc.) and the browser should attempt to resume playback. + // |keys_info| is the list of key IDs for this session along with their + // current status. |keys_info_count| is the number of entries in |keys_info|. + // Size parameter for |session_id| should not include null termination. + virtual void OnSessionKeysChange(const char* session_id, + uint32_t session_id_size, + bool has_additional_usable_key, + const KeyInformation* keys_info, + uint32_t keys_info_count) = 0; + + // Called by the CDM when there has been a change in the expiration time for + // session |session_id|. This can happen as the result of an Update() call + // or some other event. If this happens as a result of a call to Update(), + // it must be called before resolving the Update() promise. |new_expiry_time| + // represents the time after which the key(s) in the session will no longer + // be usable for decryption. It can be 0 if no such time exists or if the + // license explicitly never expires. Size parameter should not include null + // termination. + virtual void OnExpirationChange(const char* session_id, + uint32_t session_id_size, + Time new_expiry_time) = 0; + + // Called by the CDM when session |session_id| is closed. Size + // parameter should not include null termination. + virtual void OnSessionClosed(const char* session_id, + uint32_t session_id_size) = 0; + + // The following are optional methods that may not be implemented on all + // platforms. + + // Sends a platform challenge for the given |service_id|. |challenge| is at + // most 256 bits of data to be signed. Once the challenge has been completed, + // the host will call ContentDecryptionModule::OnPlatformChallengeResponse() + // with the signed challenge response and platform certificate. Size + // parameters should not include null termination. + virtual void SendPlatformChallenge(const char* service_id, + uint32_t service_id_size, + const char* challenge, + uint32_t challenge_size) = 0; + + // Attempts to enable output protection (e.g. HDCP) on the display link. The + // |desired_protection_mask| is a bit mask of OutputProtectionMethods. No + // status callback is issued, the CDM must call QueryOutputProtectionStatus() + // periodically to ensure the desired protections are applied. + virtual void EnableOutputProtection(uint32_t desired_protection_mask) = 0; + + // Requests the current output protection status. Once the host has the status + // it will call ContentDecryptionModule::OnQueryOutputProtectionStatus(). + virtual void QueryOutputProtectionStatus() = 0; + + // Must be called by the CDM if it returned kDeferredInitialization during + // InitializeAudioDecoder() or InitializeVideoDecoder(). + virtual void OnDeferredInitializationDone(StreamType stream_type, + Status decoder_status) = 0; + + // Creates a FileIO object from the host to do file IO operation. Returns NULL + // if a FileIO object cannot be obtained. Once a valid FileIO object is + // returned, |client| must be valid until FileIO::Close() is called. The + // CDM can call this method multiple times to operate on different files. + virtual FileIO* CreateFileIO(FileIOClient* client) = 0; + + // Requests a specific version of the storage ID. A storage ID is a stable, + // device specific ID used by the CDM to securely store persistent data. The + // ID will be returned by the host via ContentDecryptionModule::OnStorageId(). + // If |version| is 0, the latest version will be returned. All |version|s + // that are greater than or equal to 0x80000000 are reserved for the CDM and + // should not be supported or returned by the host. The CDM must not expose + // the ID outside the client device, even in encrypted form. + virtual void RequestStorageId(uint32_t version) = 0; + + // Reports the metric |metric_name| with value |value| to the host. Can be + // called by the CDM at any time. May report the same metric multiple times + // during the lifetime of the CDM. + virtual void ReportMetrics(MetricName metric_name, uint64_t value) = 0; + + protected: + Host_11() {} + virtual ~Host_11() {} +}; + +class CDM_CLASS_API Host_12 { + public: + static const int kVersion = 12; + + // Returns a Buffer* containing non-zero members upon success, or NULL on + // failure. The caller owns the Buffer* after this call. The buffer is not + // guaranteed to be zero initialized. The capacity of the allocated Buffer + // is guaranteed to be not less than |capacity|. + virtual Buffer* Allocate(uint32_t capacity) = 0; + + // Requests the host to call ContentDecryptionModule::TimerFired() |delay_ms| + // from now with |context|. + virtual void SetTimer(int64_t delay_ms, void* context) = 0; + + // Returns the current wall time. + virtual Time GetCurrentWallTime() = 0; + + // Called by the CDM with the result after the CDM instance was initialized. + virtual void OnInitialized(bool success) = 0; + + // Called by the CDM when a key status is available in response to + // GetStatusForPolicy(). + virtual void OnResolveKeyStatusPromise(uint32_t promise_id, + KeyStatus_2 key_status) = 0; + + // Called by the CDM when a session is created or loaded and the value for the + // MediaKeySession's sessionId attribute is available (|session_id|). + // This must be called before OnSessionMessage() or + // OnSessionKeysChange() is called for the same session. |session_id_size| + // should not include null termination. + // When called in response to LoadSession(), the |session_id| must be the + // same as the |session_id| passed in LoadSession(), or NULL if the + // session could not be loaded. + virtual void OnResolveNewSessionPromise(uint32_t promise_id, + const char* session_id, + uint32_t session_id_size) = 0; + + // Called by the CDM when a session is updated or released. + virtual void OnResolvePromise(uint32_t promise_id) = 0; + + // Called by the CDM when an error occurs as a result of one of the + // ContentDecryptionModule calls that accept a |promise_id|. + // |exception| must be specified. |error_message| and |system_code| + // are optional. |error_message_size| should not include null termination. + virtual void OnRejectPromise(uint32_t promise_id, + Exception exception, + uint32_t system_code, + const char* error_message, + uint32_t error_message_size) = 0; + + // Called by the CDM when it has a message for session |session_id|. + // Size parameters should not include null termination. + virtual void OnSessionMessage(const char* session_id, + uint32_t session_id_size, + MessageType message_type, + const char* message, + uint32_t message_size) = 0; + + // Called by the CDM when there has been a change in keys or their status for + // session |session_id|. |has_additional_usable_key| should be set if a + // key is newly usable (e.g. new key available, previously expired key has + // been renewed, etc.) and the browser should attempt to resume playback. + // |keys_info| is the list of key IDs for this session along with their + // current status. |keys_info_count| is the number of entries in |keys_info|. + // Size parameter for |session_id| should not include null termination. + virtual void OnSessionKeysChange(const char* session_id, + uint32_t session_id_size, + bool has_additional_usable_key, + const KeyInformation_2* keys_info, + uint32_t keys_info_count) = 0; + + // Called by the CDM when there has been a change in the expiration time for + // session |session_id|. This can happen as the result of an Update() call + // or some other event. If this happens as a result of a call to Update(), + // it must be called before resolving the Update() promise. |new_expiry_time| + // represents the time after which the key(s) in the session will no longer + // be usable for decryption. It can be 0 if no such time exists or if the + // license explicitly never expires. Size parameter should not include null + // termination. + virtual void OnExpirationChange(const char* session_id, + uint32_t session_id_size, + Time new_expiry_time) = 0; + + // Called by the CDM when session |session_id| is closed. Size + // parameter should not include null termination. + virtual void OnSessionClosed(const char* session_id, + uint32_t session_id_size) = 0; + + // The following are optional methods that may not be implemented on all + // platforms. + + // Sends a platform challenge for the given |service_id|. |challenge| is at + // most 256 bits of data to be signed. Once the challenge has been completed, + // the host will call ContentDecryptionModule::OnPlatformChallengeResponse() + // with the signed challenge response and platform certificate. Size + // parameters should not include null termination. + virtual void SendPlatformChallenge(const char* service_id, + uint32_t service_id_size, + const char* challenge, + uint32_t challenge_size) = 0; + + // Attempts to enable output protection (e.g. HDCP) on the display link. The + // |desired_protection_mask| is a bit mask of OutputProtectionMethods. No + // status callback is issued, the CDM must call QueryOutputProtectionStatus() + // periodically to ensure the desired protections are applied. + virtual void EnableOutputProtection(uint32_t desired_protection_mask) = 0; + + // Requests the current output protection status. Once the host has the status + // it will call ContentDecryptionModule::OnQueryOutputProtectionStatus(). + virtual void QueryOutputProtectionStatus() = 0; + + // Must be called by the CDM if it returned kDeferredInitialization during + // InitializeAudioDecoder() or InitializeVideoDecoder(). + virtual void OnDeferredInitializationDone(StreamType stream_type, + Status decoder_status) = 0; + + // Creates a FileIO object from the host to do file IO operation. Returns NULL + // if a FileIO object cannot be obtained. Once a valid FileIO object is + // returned, |client| must be valid until FileIO::Close() is called. The + // CDM can call this method multiple times to operate on different files. + virtual FileIO* CreateFileIO(FileIOClient* client) = 0; + + // Requests a specific version of the storage ID. A storage ID is a stable, + // device specific ID used by the CDM to securely store persistent data. The + // ID will be returned by the host via ContentDecryptionModule::OnStorageId(). + // If |version| is 0, the latest version will be returned. All |version|s + // that are greater than or equal to 0x80000000 are reserved for the CDM and + // should not be supported or returned by the host. The CDM must not expose + // the ID outside the client device, even in encrypted form. + virtual void RequestStorageId(uint32_t version) = 0; + + // Reports the metric |metric_name| with value |value| to the host. Can be + // called by the CDM at any time. May report the same metric multiple times + // during the lifetime of the CDM. + virtual void ReportMetrics(MetricName metric_name, uint64_t value) = 0; + + protected: + Host_12() {} + virtual ~Host_12() {} +}; + +} // namespace cdm + +#endif // CDM_CONTENT_DECRYPTION_MODULE_H_ --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/eme/cdm/media/cdm/api/content_decryption_module_export.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2017 The Chromium Authors. All rights reserved. + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: BSD-3-Clause + * See LICENSES/README.md for more information. + */ + +#ifndef CDM_CONTENT_DECRYPTION_MODULE_EXPORT_H_ +#define CDM_CONTENT_DECRYPTION_MODULE_EXPORT_H_ + +// Define CDM_API so that functionality implemented by the CDM module +// can be exported to consumers. +#if defined(_WIN32) + +#if defined(CDM_IMPLEMENTATION) +#define CDM_API __declspec(dllexport) +#else +#define CDM_API __declspec(dllimport) +#endif // defined(CDM_IMPLEMENTATION) + +#else // defined(_WIN32) +#define CDM_API __attribute__((visibility("default"))) +#endif // defined(_WIN32) + +// Define CDM_CLASS_API to export class types. We have to add visibility +// attributes to make sure virtual tables in CDM consumer and CDM implementation +// are the same. Generally, it was always a good idea, as there're no guarantees +// about that for the internal symbols, but it has only become a practical issue +// after introduction of LTO devirtualization. See more details on +// https://crbug.com/609564#c35 +#if defined(_WIN32) +#if defined(__clang__) +#define CDM_CLASS_API [[clang::lto_visibility_public]] +#else +#define CDM_CLASS_API +#endif +#else // defined(_WIN32) +#define CDM_CLASS_API __attribute__((visibility("default"))) +#endif // defined(_WIN32) + +#endif // CDM_CONTENT_DECRYPTION_MODULE_EXPORT_H_ --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/eme/cdm/media/cdm/cdm_adapter.cc @@ -0,0 +1,1051 @@ +/* + * Copyright (C) 2015 The Chromium Authors. All rights reserved. + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: BSD-3-Clause + * See LICENSES/README.md for more information. + */ + +#include "cdm_adapter.h" + +#include "../../debug.h" +#include "../base/limits.h" + +#include +#include +#include + +#include + +#ifdef _WIN32 +#include +#endif + +#define DCHECK(condition) assert(condition) + +#ifdef __APPLE__ +#include +//clock_gettime is not implemented on OSX +int clock_gettime(int clk_id, struct timespec* t) { + struct timeval now; + int rv = gettimeofday(&now, NULL); + if (rv) return rv; + t->tv_sec = now.tv_sec; + t->tv_nsec = now.tv_usec * 1000; + return 0; +} +#ifndef CLOCK_REALTIME +#define CLOCK_REALTIME 1 +#endif +#endif + +using namespace CDM_DBG; + +namespace media { + +uint64_t gtc() +{ +#ifdef OS_WIN + return GetTickCount64(); +#else + struct timespec tp; + clock_gettime(CLOCK_REALTIME, &tp); + return tp.tv_sec * 1000 + tp.tv_nsec / 1000000; +#endif +} + +namespace { +#ifdef _WIN32 +const char PATH_SEPARATOR = '\\'; +#else +const char PATH_SEPARATOR = '/'; +#endif + +constexpr std::chrono::seconds INIT_FUTURE_TIMEOUT_SEC{3}; + +void* GetCdmHost(int host_interface_version, void* user_data) +{ + if (!user_data) + return nullptr; + + CdmAdapter *adapter = static_cast(user_data); + + switch (host_interface_version) + { + case cdm::Host_10::kVersion: + return static_cast(adapter); + case cdm::Host_11::kVersion: + return static_cast(adapter); + case cdm::Host_12::kVersion: + return static_cast(adapter); + default: + return nullptr; + } +} + +bool ExistsDir(const char* path) +{ + struct stat info; + if (stat(path, &info) != 0) + return false; + else if (info.st_mode & S_IFDIR) + return true; + return false; +} + +// \brief Create a full directory path +bool CreateDirs(const char* path) +{ + const char* p; + bool ret = true; + // Skip Windows drive letter. +#ifdef _WIN32 + p = std::strchr(path, ':'); + if (p != NULL) + p++; + else + { +#endif + p = path; +#ifdef _WIN32 + } +#endif + // Ignore the first separator + if (*p == PATH_SEPARATOR) + p++; + + // Iterate each folder in the path + while ((p = std::strchr(p, PATH_SEPARATOR)) != NULL) + { + // Skip empty elements. Could be a Windows UNC path or just multiple separators + if (p != path && *(p - 1) == PATH_SEPARATOR) + { + p++; + continue; + } + std::string currPath(path, p - path); + p++; +#ifdef _WIN32 + std::wstring currPathW(currPath.begin(), currPath.end()); + if (CreateDirectory(currPathW.c_str(), NULL) == FALSE) + { + if (GetLastError() != ERROR_ALREADY_EXISTS) + { + ret = false; + break; + } + } +#else + if (mkdir(currPath.c_str(), 0774) != 0) + { + if (errno != EEXIST) + { + ret = false; + break; + } + } +#endif + } + return ret; +} + +} // namespace + +cdm::VideoDecoderConfig_2 ToVideoDecoderConfig_2( + const cdm::VideoDecoderConfig_3& config) { + return{ config.codec, + config.profile, + config.format, + config.coded_size, + config.extra_data, + config.extra_data_size, + config.encryption_scheme }; +} + + +/******************************* CdmAdapter ****************************************/ + + +CdmAdapter::CdmAdapter( + const std::string& key_system, + const std::string& cdm_path, + const std::string& base_path, + const CdmConfig& cdm_config, + CdmAdapterClient *client) +: cdm_path_(cdm_path) +, cdm_base_path_(base_path) +, client_(client) +, key_system_(key_system) +, cdm_config_(cdm_config) +{ + //DCHECK(!key_system_.empty()); +} + +CdmAdapter::~CdmAdapter() +{ + UnloadCDM(); +} + +bool CdmAdapter::LoadCDM() +{ + UnloadCDM(); + + base::NativeLibraryLoadError error; + library_ = base::LoadNativeLibrary(cdm_path_, &error); + + if (!library_) + { + Log(LogLevel::ERROR, "Failed to load library: %s", error.ToString().c_str()); + return false; + } + + init_cdm_func = reinterpret_cast( + base::GetFunctionPointerFromNativeLibrary(library_, MAKE_STRING(INITIALIZE_CDM_MODULE))); + deinit_cdm_func = reinterpret_cast( + base::GetFunctionPointerFromNativeLibrary(library_, "DeinitializeCdmModule")); + create_cdm_func = reinterpret_cast( + base::GetFunctionPointerFromNativeLibrary(library_, "CreateCdmInstance")); + get_cdm_verion_func = reinterpret_cast( + base::GetFunctionPointerFromNativeLibrary(library_, "GetCdmVersion")); + + if (!init_cdm_func || !create_cdm_func || !get_cdm_verion_func || !deinit_cdm_func) + { + Log(LogLevel::ERROR, "Failed to binding CDM library functions"); + return false; + } + return true; +} + +bool CdmAdapter::Initialize() +{ + m_initPromise = std::promise{}; + m_initFuture = m_initPromise.get_future(); + m_provisioningCompleteOrStarted = false; + m_isClosingSession = false; + + Log(LogLevel::DEBUG, "CDM version: %s", GetVersion().c_str()); + +#if defined(OS_WIN) + // Load DXVA before sandbox lockdown to give CDM access to Output Protection + // Manager (OPM). + base::NativeLibraryLoadError error; + base::LoadNativeLibrary("dxva2.dll", &error); +#endif // defined(OS_WIN) + + init_cdm_func(); + +#ifndef TARGET_WEBOS + cdm12_ = static_cast(create_cdm_func( + 12, key_system_.data(), static_cast(key_system_.size()), GetCdmHost, this)); +#endif + + if (!cdm12_) + { + cdm11_ = static_cast(create_cdm_func( + 11, key_system_.data(), static_cast(key_system_.size()), GetCdmHost, this)); + + if (!cdm11_) + { + cdm10_ = static_cast(create_cdm_func( + 10, key_system_.data(), static_cast(key_system_.size()), GetCdmHost, this)); + } + } + + if (cdm12_ || cdm11_ || cdm10_) + { + if (cdm12_) + { + cdm12_->Initialize(cdm_config_.allow_distinctive_identifier, + cdm_config_.allow_persistent_state, cdm_config_.use_hw_secure_codecs); + } + else if (cdm11_) + { + cdm11_->Initialize(cdm_config_.allow_distinctive_identifier, + cdm_config_.allow_persistent_state, cdm_config_.use_hw_secure_codecs); + } + else if (cdm10_) + { + cdm10_->Initialize(cdm_config_.allow_distinctive_identifier, + cdm_config_.allow_persistent_state, cdm_config_.use_hw_secure_codecs); + } + + // Wait for the CDM to be initialized + // Add a maximum timeout in case we never hear back! + if (m_initFuture.valid() && + m_initFuture.wait_for(INIT_FUTURE_TIMEOUT_SEC) != std::future_status::ready) + { + Log(LogLevel::ERROR, "CDM initialization timed out"); + return false; + } + + if (!m_provisioningCompleteOrStarted) + { + Log(LogLevel::ERROR, "CDM initialization failed or not started"); + return false; + } + + return true; + } + + Log(LogLevel::ERROR, "Cannot get the ContentDecryptionModule interface"); + return false; +} + +std::string CdmAdapter::GetVersion() const +{ + if (get_cdm_verion_func) + return get_cdm_verion_func(); + + return ""; +} + +void CdmAdapter::UnloadCDM() +{ + if (cdm12_) + { + cdm12_->Destroy(); + cdm12_ = nullptr; + } + else if (cdm11_) + { + cdm11_->Destroy(); + cdm11_ = nullptr; + } + else if (cdm10_) + { + cdm10_->Destroy(); + cdm10_ = nullptr; + } + + init_cdm_func = nullptr; + create_cdm_func = nullptr; + get_cdm_verion_func = nullptr; + + if (deinit_cdm_func) + { + deinit_cdm_func(); + deinit_cdm_func = nullptr; + } + + if (library_) + { + base::UnloadNativeLibrary(library_); + library_ = nullptr; + } +} + +void CdmAdapter::SendClientMessage(const char* session, uint32_t session_size, CdmAdapterClient::CDMADPMSG msg, const uint8_t *data, size_t data_size, uint32_t status) +{ + std::lock_guard guard(client_mutex_); + if (client_) + client_->OnCDMMessage(session, session_size, msg, data, data_size, status); +} + +void CdmAdapter::SendOnKeyStatusChange(const std::string& sessionId, + const std::vector& keysInfo) +{ + std::lock_guard guard(client_mutex_); + if (client_) + client_->OnKeyStatusChange(sessionId, keysInfo); +} + +void CdmAdapter::RemoveClient() +{ + std::lock_guard guard(client_mutex_); + client_ = nullptr; +} + +void CdmAdapter::SetServerCertificate(uint32_t promise_id, + const uint8_t* server_certificate_data, + uint32_t server_certificate_data_size) +{ + std::lock_guard lock(decrypt_mutex_); + if (server_certificate_data_size < limits::kMinCertificateLength || + server_certificate_data_size > limits::kMaxCertificateLength) { + static const char message[] = "server certificate size out of range"; + OnRejectPromise(promise_id, cdm::kExceptionTypeError, 0, message, sizeof(message) - 1); + return; + } + + if (cdm12_) + cdm12_->SetServerCertificate(promise_id, server_certificate_data, server_certificate_data_size); + else if (cdm11_) + cdm11_->SetServerCertificate(promise_id, server_certificate_data, server_certificate_data_size); + else if (cdm10_) + cdm10_->SetServerCertificate(promise_id, server_certificate_data, server_certificate_data_size); +} + +void CdmAdapter::CreateSessionAndGenerateRequest(uint32_t promise_id, + cdm::SessionType session_type, + cdm::InitDataType init_data_type, + const uint8_t* init_data, + uint32_t init_data_size) +{ + std::lock_guard lock(decrypt_mutex_); + if (cdm12_) + cdm12_->CreateSessionAndGenerateRequest(promise_id, session_type, init_data_type, init_data, + init_data_size); + else if (cdm11_) + cdm11_->CreateSessionAndGenerateRequest(promise_id, session_type, init_data_type, init_data, + init_data_size); + else if (cdm10_) + cdm10_->CreateSessionAndGenerateRequest(promise_id, session_type, init_data_type, init_data, + init_data_size); +} + +void CdmAdapter::LoadSession(uint32_t promise_id, + cdm::SessionType session_type, + const char* session_id, + uint32_t session_id_size) +{ + std::lock_guard lock(decrypt_mutex_); + if (cdm12_) + cdm12_->LoadSession(promise_id, session_type, session_id, session_id_size); + else if (cdm11_) + cdm11_->LoadSession(promise_id, session_type, session_id, session_id_size); + else if (cdm10_) + cdm10_->LoadSession(promise_id, session_type, session_id, session_id_size); +} + +void CdmAdapter::UpdateSession(uint32_t promise_id, + const char* session_id, + uint32_t session_id_size, + const uint8_t* response, + uint32_t response_size) +{ + std::lock_guard lock(decrypt_mutex_); + if (cdm12_) + cdm12_->UpdateSession(promise_id, session_id, session_id_size, response, response_size); + else if (cdm11_) + cdm11_->UpdateSession(promise_id, session_id, session_id_size, response, response_size); + else if (cdm10_) + cdm10_->UpdateSession(promise_id, session_id, session_id_size, response, response_size); +} + +void CdmAdapter::CloseSession(uint32_t promise_id, + const char* session_id, + uint32_t session_id_size) +{ + std::vector> closingTimers; + { + std::lock_guard lock(m_closeSessionMutex); + m_isClosingSession = true; + // Take the pending timers so they can be released below without a CDM lock + // held: a timer already in TimerExpired waits for decrypt_mutex_, so + // blocking on its future while holding that lock would deadlock. + closingTimers.swap(m_asyncTimerTasks); + } + m_sessionClosingCond.notify_all(); + + { + std::lock_guard lock(decrypt_mutex_); + if (cdm12_) + cdm12_->CloseSession(promise_id, session_id, session_id_size); + else if (cdm11_) + cdm11_->CloseSession(promise_id, session_id, session_id_size); + else if (cdm10_) + cdm10_->CloseSession(promise_id, session_id, session_id_size); + } + + // Release the taken timers with no CDM lock held. The closing flag is still + // set, so a timer still in its wait drops at once; one already in + // TimerExpired can take decrypt_mutex_ and finish, since we hold nothing. + closingTimers.clear(); + + // The flag only cancels timers queued across this close; leaving it set would + // drop every later timer, so the session that reuses these MediaKeys stalls. + { + std::lock_guard lock(m_closeSessionMutex); + m_isClosingSession = false; + } +} + +void CdmAdapter::RemoveSession(uint32_t promise_id, + const char* session_id, + uint32_t session_id_size) +{ + std::lock_guard lock(decrypt_mutex_); + if (cdm12_) + cdm12_->RemoveSession(promise_id, session_id, session_id_size); + else if (cdm11_) + cdm11_->RemoveSession(promise_id, session_id, session_id_size); + else if (cdm10_) + cdm10_->RemoveSession(promise_id, session_id, session_id_size); +} + +void CdmAdapter::TimerExpired(void* context) +{ + std::lock_guard lock(decrypt_mutex_); + if (cdm12_) + cdm12_->TimerExpired(context); + else if (cdm11_) + cdm11_->TimerExpired(context); + else if (cdm10_) + cdm10_->TimerExpired(context); +} + +cdm::Status CdmAdapter::Decrypt(const cdm::InputBuffer_2& encrypted_buffer, + cdm::DecryptedBlock* decrypted_buffer, + cdm::StreamType streamType) +{ + //We need this wait here for fast systems, during buffering + //widewine stopps if some seconds (5??) are fetched too fast + //std::this_thread::sleep_for(std::chrono::milliseconds(5)); + + std::lock_guard lock(decrypt_mutex_); + + active_buffer_ = decrypted_buffer->DecryptedBuffer(); + cdm::Status ret{cdm::Status::kDecryptError}; + + if (cdm12_) + ret = cdm12_->Decrypt(encrypted_buffer, decrypted_buffer); + else if (cdm11_) +#ifdef TARGET_WEBOS + ret = cdm11_->Decrypt(encrypted_buffer, decrypted_buffer, streamType); +#else + ret = cdm11_->Decrypt(encrypted_buffer, decrypted_buffer); +#endif + else if (cdm10_) +#ifdef TARGET_WEBOS + ret = cdm10_->Decrypt(encrypted_buffer, decrypted_buffer, streamType); +#else + ret = cdm10_->Decrypt(encrypted_buffer, decrypted_buffer); +#endif + + active_buffer_ = 0; + return ret; +} + +cdm::Status CdmAdapter::InitializeAudioDecoder( + const cdm::AudioDecoderConfig_2& audio_decoder_config) +{ + std::lock_guard lock(decrypt_mutex_); + if (cdm12_) + return cdm12_->InitializeAudioDecoder(audio_decoder_config); + else if (cdm11_) + return cdm11_->InitializeAudioDecoder(audio_decoder_config); + else if (cdm10_) + return cdm10_->InitializeAudioDecoder(audio_decoder_config); + + return cdm::kDeferredInitialization; +} + +cdm::Status CdmAdapter::InitializeVideoDecoder( + const cdm::VideoDecoderConfig_3& video_decoder_config) +{ + std::lock_guard lock(decrypt_mutex_); + if (cdm12_) + return cdm12_->InitializeVideoDecoder(video_decoder_config); + else if (cdm11_) + return cdm11_->InitializeVideoDecoder(ToVideoDecoderConfig_2(video_decoder_config)); + else if (cdm10_) + return cdm10_->InitializeVideoDecoder(ToVideoDecoderConfig_2(video_decoder_config)); + + return cdm::kDeferredInitialization; +} + +void CdmAdapter::DeinitializeDecoder(cdm::StreamType decoder_type) +{ + std::lock_guard lock(decrypt_mutex_); + if (cdm12_) + cdm12_->DeinitializeDecoder(decoder_type); + else if (cdm11_) + cdm11_->DeinitializeDecoder(decoder_type); + else if (cdm10_) + cdm10_->DeinitializeDecoder(decoder_type); +} + +void CdmAdapter::ResetDecoder(cdm::StreamType decoder_type) +{ + std::lock_guard lock(decrypt_mutex_); + if (cdm12_) + cdm12_->ResetDecoder(decoder_type); + else if (cdm11_) + cdm11_->ResetDecoder(decoder_type); + else if (cdm10_) + cdm10_->ResetDecoder(decoder_type); +} + +cdm::Status CdmAdapter::DecryptAndDecodeFrame(const cdm::InputBuffer_2& encrypted_buffer, + CdmVideoFrame* video_frame) +{ + std::lock_guard lock(decrypt_mutex_); + cdm::Status ret(cdm::kDeferredInitialization); + + if (cdm12_) + ret = cdm12_->DecryptAndDecodeFrame(encrypted_buffer, video_frame); + else if (cdm11_) + ret = cdm11_->DecryptAndDecodeFrame(encrypted_buffer, video_frame); + else if (cdm10_) + ret = cdm10_->DecryptAndDecodeFrame(encrypted_buffer, video_frame); + + active_buffer_ = 0; + return ret; +} + +cdm::Status CdmAdapter::DecryptAndDecodeSamples(const cdm::InputBuffer_2& encrypted_buffer, + cdm::AudioFrames* audio_frames) +{ + std::lock_guard lock(decrypt_mutex_); + if (cdm12_) + return cdm12_->DecryptAndDecodeSamples(encrypted_buffer, audio_frames); + else if (cdm11_) + return cdm11_->DecryptAndDecodeSamples(encrypted_buffer, audio_frames); + else if (cdm10_) + return cdm10_->DecryptAndDecodeSamples(encrypted_buffer, audio_frames); + return cdm::kDeferredInitialization; +} + +void CdmAdapter::OnPlatformChallengeResponse( + const cdm::PlatformChallengeResponse& response) +{ + if (cdm12_) + cdm12_->OnPlatformChallengeResponse(response); + else if (cdm11_) + cdm11_->OnPlatformChallengeResponse(response); + else if (cdm10_) + cdm10_->OnPlatformChallengeResponse(response); +} + +void CdmAdapter::OnQueryOutputProtectionStatus(cdm::QueryResult result, + uint32_t link_mask, + uint32_t output_protection_mask) +{ + if (cdm12_) + cdm12_->OnQueryOutputProtectionStatus(result, link_mask, output_protection_mask); + else if (cdm11_) + cdm11_->OnQueryOutputProtectionStatus(result, link_mask, output_protection_mask); + else if (cdm10_) + cdm10_->OnQueryOutputProtectionStatus(result, link_mask, output_protection_mask); +} + +/******************************** HOST *****************************************/ + +cdm::Buffer* CdmAdapter::Allocate(uint32_t capacity) +{ + if (active_buffer_) + return active_buffer_; + + std::lock_guard guard(client_mutex_); + return client_ ? client_->AllocateBuffer(capacity) : nullptr; +} + +void CdmAdapter::timerfunc(CdmAdapter* adp, int64_t delay, void* context) +{ + { + std::unique_lock lock(m_closeSessionMutex); + const bool isClosing = m_sessionClosingCond.wait_for( + lock, std::chrono::milliseconds(delay), [this] { return m_isClosingSession.load(); }); + if (isClosing) + return; + } + adp->TimerExpired(context); +} + +void CdmAdapter::SetTimer(int64_t delay_ms, void* context) +{ + //LICENSERENEWAL + std::future future = std::async(std::launch::async, &CdmAdapter::timerfunc, + shared_from_this(), this, delay_ms, context); + // Prune finished tasks and record the new one under the one lock that also + // guards the close path, so SetTimer (called by the CDM while a session op + // holds decrypt_mutex_) and CloseSession never race on the task list. + std::lock_guard lock(m_closeSessionMutex); + for (auto itFuture = m_asyncTimerTasks.begin(); itFuture != m_asyncTimerTasks.end();) + { + if (!itFuture->valid() || + itFuture->wait_for(std::chrono::milliseconds(0)) == std::future_status::ready) + { + itFuture = m_asyncTimerTasks.erase(itFuture); + } + else + itFuture++; + } + m_asyncTimerTasks.push_back(std::move(future)); +} + +cdm::Time CdmAdapter::GetCurrentWallTime() +{ + cdm::Time res = static_cast(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()); + return res / 1000.0; +} + +void CdmAdapter::OnResolvePromise(uint32_t promise_id) +{ + std::lock_guard guard(client_mutex_); + if (client_) + client_->OnPromiseResolved(promise_id); +} + +std::future CdmAdapter::PrepareSessionFuture(uint32_t promiseId) +{ + std::lock_guard lock(m_sessionMx); + std::promise promise; + auto future = promise.get_future(); + m_sessionPromises[promiseId] = std::move(promise); + return future; +} + +void CdmAdapter::OnResolveNewSessionPromise(uint32_t promise_id, + const char* session_id, + uint32_t session_id_size) +{ + std::string sessionStr(session_id, session_id_size); + + std::lock_guard lock(m_sessionMx); + auto it = m_sessionPromises.find(promise_id); + if (it != m_sessionPromises.end()) + { + it->second.set_value(sessionStr); + m_sessionPromises.erase(it); + } + else + { + Log(LogLevel::ERROR, "Promise ID %u not found", promise_id); + } +} + +void CdmAdapter::OnSessionKeysChange(const char* session_id, + uint32_t session_id_size, + bool has_additional_usable_key, + const cdm::KeyInformation* keys_info, + uint32_t keys_info_count) +{ + const std::string sessionId{session_id, session_id_size}; + std::vector keysInfo; + + for (uint32_t i(0); i < keys_info_count; ++i) + { + const cdm::KeyInformation cdmKeyInfo = keys_info[i]; + + if (cdmKeyInfo.system_code != 0) + { + char buffer[128]; + char* bufferPtr{buffer}; + for (uint32_t j{0}; j < keys_info[i].key_id_size; ++j) + bufferPtr += std::snprintf(bufferPtr, 3, "%02X", (int)keys_info[i].key_id[j]); + Log(LogLevel::WARNING, "OnSessionKeysChange: KID %s with status %d has error system code: %u", + buffer, cdmKeyInfo.status, cdmKeyInfo.system_code); + } + + CdmAdapterClient::KeyInfo keyInfo; + keyInfo.kid.assign(cdmKeyInfo.key_id, cdmKeyInfo.key_id + cdmKeyInfo.key_id_size); + + switch (cdmKeyInfo.status) + { + case cdm::KeyStatus::kUsable: + keyInfo.status = CdmAdapterClient::CDMKeyStatus::kUsable; + break; + case cdm::KeyStatus::kInternalError: + keyInfo.status = CdmAdapterClient::CDMKeyStatus::kInternalError; + break; + case cdm::KeyStatus::kExpired: + keyInfo.status = CdmAdapterClient::CDMKeyStatus::kExpired; + break; + case cdm::KeyStatus::kOutputRestricted: + keyInfo.status = CdmAdapterClient::CDMKeyStatus::kOutputRestricted; + break; + case cdm::KeyStatus::kOutputDownscaled: + keyInfo.status = CdmAdapterClient::CDMKeyStatus::kOutputDownscaled; + break; + case cdm::KeyStatus::kStatusPending: + keyInfo.status = CdmAdapterClient::CDMKeyStatus::kStatusPending; + break; + case cdm::KeyStatus::kReleased: + keyInfo.status = CdmAdapterClient::CDMKeyStatus::kReleased; + break; + default: + Log(LogLevel::ERROR, "OnSessionKeysChange: Unhandled KeyStatus %i, KID ignored", + cdmKeyInfo.status); + continue; + } + + keysInfo.emplace_back(keyInfo); + } + + SendOnKeyStatusChange(sessionId, keysInfo); +} + +void CdmAdapter::OnSessionKeysChange(const char* session_id, + uint32_t session_id_size, + bool has_additional_usable_key, + const cdm::KeyInformation_2* keys_info, + uint32_t keys_info_count) +{ + const std::string sessionId{session_id, session_id_size}; + std::vector keysInfo; + + for (uint32_t i(0); i < keys_info_count; ++i) + { + const cdm::KeyInformation_2 cdmKeyInfo = keys_info[i]; + + if (cdmKeyInfo.system_code != 0) + { + char buffer[128]; + char* bufferPtr{buffer}; + for (uint32_t j{0}; j < keys_info[i].key_id_size; ++j) + bufferPtr += std::snprintf(bufferPtr, 3, "%02X", (int)keys_info[i].key_id[j]); + Log(LogLevel::WARNING, "OnSessionKeysChange: KID %s with status %d has error system code: %u", + buffer, cdmKeyInfo.status, cdmKeyInfo.system_code); + } + + CdmAdapterClient::KeyInfo keyInfo; + keyInfo.kid.assign(cdmKeyInfo.key_id, cdmKeyInfo.key_id + cdmKeyInfo.key_id_size); + + switch (cdmKeyInfo.status) + { + case cdm::KeyStatus_2::kUsable: + keyInfo.status = CdmAdapterClient::CDMKeyStatus::kUsable; + break; + case cdm::KeyStatus_2::kInternalError: + keyInfo.status = CdmAdapterClient::CDMKeyStatus::kInternalError; + break; + case cdm::KeyStatus_2::kExpired: + keyInfo.status = CdmAdapterClient::CDMKeyStatus::kExpired; + break; + case cdm::KeyStatus_2::kOutputRestricted: + keyInfo.status = CdmAdapterClient::CDMKeyStatus::kOutputRestricted; + break; + case cdm::KeyStatus_2::kOutputDownscaled: + keyInfo.status = CdmAdapterClient::CDMKeyStatus::kOutputDownscaled; + break; + case cdm::KeyStatus_2::kStatusPending: + keyInfo.status = CdmAdapterClient::CDMKeyStatus::kStatusPending; + break; + case cdm::KeyStatus_2::kReleased: + keyInfo.status = CdmAdapterClient::CDMKeyStatus::kReleased; + break; + case cdm::KeyStatus_2::kUsableInFuture: + keyInfo.status = CdmAdapterClient::CDMKeyStatus::kUsableInFuture; + break; + default: + Log(LogLevel::ERROR, "OnSessionKeysChange: Unhandled KeyStatus_2 %i, KID ignored", + cdmKeyInfo.status); + continue; + } + + keysInfo.emplace_back(keyInfo); + } + + SendOnKeyStatusChange(sessionId, keysInfo); +} + +void CdmAdapter::OnExpirationChange(const char* session_id, + uint32_t session_id_size, + cdm::Time new_expiry_time) +{ + SendClientMessage(session_id, session_id_size, CdmAdapterClient::kSessionExpired, nullptr, 0, 0); +} + +void CdmAdapter::OnSessionClosed(const char* session_id, + uint32_t session_id_size) +{ + SendClientMessage(session_id, session_id_size, CdmAdapterClient::kSessionClosed, nullptr, 0, 0); +} + +void CdmAdapter::SendPlatformChallenge(const char* service_id, + uint32_t service_id_size, + const char* challenge, + uint32_t challenge_size) +{ +} + +void CdmAdapter::EnableOutputProtection(uint32_t desired_protection_mask) +{ + QueryOutputProtectionStatus(); +} + +void CdmAdapter::QueryOutputProtectionStatus() +{ + OnQueryOutputProtectionStatus(cdm::kQuerySucceeded, cdm::kLinkTypeInternal, cdm::kProtectionHDCP); +} + +void CdmAdapter::OnDeferredInitializationDone(cdm::StreamType stream_type, + cdm::Status decoder_status) +{ +} + +// The CDM owns the returned object and must call FileIO::Close() to release it. +cdm::FileIO* CdmAdapter::CreateFileIO(cdm::FileIOClient* client) +{ + return new CdmFileIoImpl(cdm_base_path_, client); +} + +void CdmAdapter::OnResolveKeyStatusPromise(uint32_t promise_id, cdm::KeyStatus key_status) +{ +} + +void CdmAdapter::OnResolveKeyStatusPromise(uint32_t promise_id, cdm::KeyStatus_2 key_status) +{ +} + +void CdmAdapter::OnRejectPromise(uint32_t promise_id, + cdm::Exception exception, + uint32_t system_code, + const char* error_message, + uint32_t error_message_size) +{ + std::string msg; + if (error_message && error_message_size) + { + msg.assign(error_message, error_message_size); + } + else + { + msg = "CDM reject: " + std::to_string(static_cast(exception)) + + " sys=" + std::to_string(system_code); + } + + { + std::lock_guard lk(m_sessionMx); + auto it = m_sessionPromises.find(promise_id); + if (it != m_sessionPromises.end()) + { + try + { + it->second.set_exception(std::make_exception_ptr(std::runtime_error(msg))); + } + catch (...) + { + } + m_sessionPromises.erase(it); + return; + } + } + + std::lock_guard guard(client_mutex_); + if (client_) + client_->OnPromiseRejected(promise_id, system_code, msg); +} + +void CdmAdapter::OnSessionMessage(const char* session_id, uint32_t session_id_size, + cdm::MessageType message_type, const char* message, uint32_t message_size) +{ + SendClientMessage(session_id, session_id_size, CdmAdapterClient::kSessionMessage, reinterpret_cast(message), message_size, 0); +} + +void CdmAdapter::RequestStorageId(uint32_t version) +{ + if (cdm12_) + cdm12_->OnStorageId(version, nullptr, 0); + else if (cdm11_) + cdm11_->OnStorageId(version, nullptr, 0); + else if (cdm10_) + cdm10_->OnStorageId(version, nullptr, 0); +} + +void CdmAdapter::ReportMetrics(cdm::MetricName metric_name, uint64_t value) +{ +} + +void CdmAdapter::OnInitialized(bool success) +{ + // Notify the client that the CDM is initialized + if (success) + { + m_provisioningCompleteOrStarted = true; + } + + // Set the promise value so that execution can continue + m_initPromise.set_value(); + + Log(LogLevel::DEBUG, "CDM is initialized: %s", success ? "true" : "false"); +} + + +/******************************* CdmFileIoImpl ****************************************/ + +CdmFileIoImpl::CdmFileIoImpl(std::string base_path, cdm::FileIOClient* client) + : base_path_(base_path) + , client_(client) + , file_descriptor_(0) + , data_buffer_(0) + , opened_(false) +{ +} + +void CdmFileIoImpl::Open(const char* file_name, uint32_t file_name_size) +{ + if (!opened_) + { + opened_ = true; + m_filepath.assign(file_name, file_name_size); + m_filepath = base_path_ + m_filepath; + client_->OnOpenComplete(cdm::FileIOClient::Status::kSuccess); + } + else + client_->OnOpenComplete(cdm::FileIOClient::Status::kInUse); +} + +void CdmFileIoImpl::Read() +{ + cdm::FileIOClient::Status status(cdm::FileIOClient::Status::kError); + size_t sz(0); + + free(reinterpret_cast(data_buffer_)); + data_buffer_ = nullptr; + + file_descriptor_ = fopen(m_filepath.c_str(), "rb"); + + if (file_descriptor_) + { + status = cdm::FileIOClient::Status::kSuccess; + fseek(file_descriptor_, 0, SEEK_END); + sz = ftell(file_descriptor_); + if (sz) + { + fseek(file_descriptor_, 0, SEEK_SET); + if ((data_buffer_ = reinterpret_cast(malloc(sz))) == nullptr || fread(data_buffer_, 1, sz, file_descriptor_) != sz) + status = cdm::FileIOClient::Status::kError; + } + } else + status = cdm::FileIOClient::Status::kSuccess; + + if (file_descriptor_) + { + fclose(file_descriptor_); + file_descriptor_ = 0; + } + client_->OnReadComplete(status, data_buffer_, static_cast(sz)); +} + +void CdmFileIoImpl::Write(const uint8_t* data, uint32_t data_size) +{ + if (!ExistsDir(base_path_.c_str()) && !CreateDirs(base_path_.c_str())) + { + Log(LogLevel::ERROR, "Cannot create directory: %s", base_path_.c_str()); + client_->OnWriteComplete(cdm::FileIOClient::Status::kError); + return; + } + + cdm::FileIOClient::Status status = cdm::FileIOClient::Status::kError; + + file_descriptor_ = fopen(m_filepath.c_str(), "wb"); + + if (file_descriptor_) + { + if (fwrite(data, 1, data_size, file_descriptor_) == data_size) + { + status = cdm::FileIOClient::Status::kSuccess; + } + fclose(file_descriptor_); + file_descriptor_ = 0; + } + + client_->OnWriteComplete(status); +} + +void CdmFileIoImpl::Close() +{ + if (file_descriptor_) + { + fclose(file_descriptor_); + file_descriptor_ = 0; + } + client_ = 0; + free(reinterpret_cast(data_buffer_)); + data_buffer_ = 0; + delete this; +} + +} // namespace media --- /dev/null +++ b/Source/WebCore/platform/graphics/gstreamer/eme/cdm/media/cdm/cdm_adapter.h @@ -0,0 +1,330 @@ +/* + * Copyright (C) 2015 The Chromium Authors. All rights reserved. + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: BSD-3-Clause + * See LICENSES/README.md for more information. + */ + +#ifndef MEDIA_CDM_CDM_ADAPTER_H_ +#define MEDIA_CDM_CDM_ADAPTER_H_ + +#include "../../base/compiler_specific.h" +#include "../../base/macros.h" +#include "../../base/native_library.h" +#include "../base/cdm_config.h" +#include "api/content_decryption_module.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace media { + +uint64_t gtc(); + +class CdmAdapterClient +{ +public: + enum CDMADPMSG + { + kError, + kSessionMessage, + kSessionExpired, + kSessionKeysChange, + kSessionClosed, + kLegacySessionError + }; + + // \brief Common Key status enum values (depending on the CDM version there are different enums) + enum class CDMKeyStatus + { + kUsable, + kInternalError, + kExpired, + kOutputRestricted, + kOutputDownscaled, + kStatusPending, + kReleased, + kUsableInFuture, + }; + + struct KeyInfo + { + std::vector kid; + CDMKeyStatus status; + }; + + virtual void OnCDMMessage(const char* session, uint32_t session_size, CDMADPMSG msg, const uint8_t *data, size_t data_size, uint32_t status) = 0; + virtual void OnKeyStatusChange(const std::string& sessionId, + const std::vector& keysInfo) = 0; + virtual cdm::Buffer *AllocateBuffer(size_t sz) = 0; + virtual void OnPromiseResolved(uint32_t promise_id) = 0; + virtual void OnPromiseRejected(uint32_t promise_id, uint32_t system_code, const std::string& message) = 0; +}; + +class CdmVideoFrame : public cdm::VideoFrame, public cdm::VideoFrame_2 { +public: + CdmVideoFrame() = default; + + void SetFormat(cdm::VideoFormat format) override { m_format = format; } + cdm::VideoFormat Format() const override { return m_format; } + void SetSize(cdm::Size size) override { m_size = size; } + cdm::Size Size() const override { return m_size; } + void SetFrameBuffer(cdm::Buffer* frame_buffer) override { m_buffer = frame_buffer; } + cdm::Buffer* FrameBuffer() override { return m_buffer; } + void SetPlaneOffset(cdm::VideoPlane plane, uint32_t offset) override { m_planeOffsets[plane] = offset; } + void SetColorSpace(cdm::ColorSpace color_space) override { m_colorSpace = color_space; } + + virtual uint32_t PlaneOffset(cdm::VideoPlane plane) override { return m_planeOffsets[plane]; } + virtual void SetStride(cdm::VideoPlane plane, uint32_t stride) override { m_stride[plane] = stride; } + virtual uint32_t Stride(cdm::VideoPlane plane) override { return m_stride[plane]; } + virtual void SetTimestamp(int64_t timestamp) override { m_pts = timestamp; } + + virtual int64_t Timestamp() const override { return m_pts; } +private: + cdm::VideoFormat m_format; + cdm::Buffer *m_buffer = nullptr; + cdm::Size m_size; + cdm::ColorSpace m_colorSpace; + + uint32_t m_planeOffsets[cdm::kMaxPlanes]; + uint32_t m_stride[cdm::kMaxPlanes]; + + uint64_t m_pts; +}; + +class CdmAdapter : public std::enable_shared_from_this, + public cdm::Host_10, + public cdm::Host_11, + public cdm::Host_12 +{ + public: + void timerfunc(CdmAdapter* adp, int64_t delay, void* context); + CdmAdapter(const std::string& key_system, + const std::string& cdm_path, + const std::string& base_path, + const CdmConfig& cdm_config, + CdmAdapterClient *client); + + void RemoveClient(); + + void SetServerCertificate(uint32_t promise_id, + const uint8_t* server_certificate_data, + uint32_t server_certificate_data_size); + + void CreateSessionAndGenerateRequest(uint32_t promise_id, + cdm::SessionType session_type, + cdm::InitDataType init_data_type, + const uint8_t* init_data, + uint32_t init_data_size); + + void LoadSession(uint32_t promise_id, + cdm::SessionType session_type, + const char* session_id, + uint32_t session_id_size); + + void UpdateSession(uint32_t promise_id, + const char* session_id, + uint32_t session_id_size, + const uint8_t* response, + uint32_t response_size); + + void CloseSession(uint32_t promise_id, + const char* session_id, + uint32_t session_id_size); + + void RemoveSession(uint32_t promise_id, + const char* session_id, + uint32_t session_id_size); + + void TimerExpired(void* context); + + cdm::Status Decrypt(const cdm::InputBuffer_2& encrypted_buffer, + cdm::DecryptedBlock* decrypted_buffer, + cdm::StreamType streamType = cdm::StreamType::kStreamTypeVideo); + + cdm::Status InitializeAudioDecoder( + const cdm::AudioDecoderConfig_2& audio_decoder_config); + + cdm::Status InitializeVideoDecoder( + const cdm::VideoDecoderConfig_3& video_decoder_config); + + void DeinitializeDecoder(cdm::StreamType decoder_type); + + void ResetDecoder(cdm::StreamType decoder_type); + + cdm::Status DecryptAndDecodeFrame(const cdm::InputBuffer_2& encrypted_buffer, + CdmVideoFrame* video_frame); + + cdm::Status DecryptAndDecodeSamples(const cdm::InputBuffer_2& encrypted_buffer, + cdm::AudioFrames* audio_frames); + + void OnPlatformChallengeResponse( + const cdm::PlatformChallengeResponse& response); + + void OnQueryOutputProtectionStatus(cdm::QueryResult result, + uint32_t link_mask, + uint32_t output_protection_mask); + + // cdm::Host implementation. + + cdm::Buffer* Allocate(uint32_t capacity) override; + + void SetTimer(int64_t delay_ms, void* context) override; + + cdm::Time GetCurrentWallTime() override; + + void OnResolveKeyStatusPromise(uint32_t promise_id, + cdm::KeyStatus key_status) override; + + // Used by CDM12 and beyond + void OnResolveKeyStatusPromise(uint32_t promise_id, cdm::KeyStatus_2 key_status) override; + + void OnResolveNewSessionPromise(uint32_t promise_id, + const char* session_id, + uint32_t session_id_size) override; + + void OnResolvePromise(uint32_t promise_id) override; + + void OnRejectPromise(uint32_t promise_id, + cdm::Exception exception, + uint32_t system_code, + const char* error_message, + uint32_t error_message_size) override; + + void OnSessionMessage(const char* session_id, + uint32_t session_id_size, + cdm::MessageType message_type, + const char* message, + uint32_t message_size) override; + + void OnSessionKeysChange(const char* session_id, + uint32_t session_id_size, + bool has_additional_usable_key, + const cdm::KeyInformation* keys_info, + uint32_t keys_info_count) override; + + // Used by CDM12 and beyond + void OnSessionKeysChange(const char* session_id, + uint32_t session_id_size, + bool has_additional_usable_key, + const cdm::KeyInformation_2* keys_info, + uint32_t keys_info_count) override; + + void OnExpirationChange(const char* session_id, + uint32_t session_id_size, + cdm::Time new_expiry_time) override; + + void OnSessionClosed(const char* session_id, + uint32_t session_id_size) override; + + void SendPlatformChallenge(const char* service_id, + uint32_t service_id_size, + const char* challenge, + uint32_t challenge_size) override; + + void EnableOutputProtection(uint32_t desired_protection_mask) override; + + void QueryOutputProtectionStatus() override; + + void OnDeferredInitializationDone(cdm::StreamType stream_type, + cdm::Status decoder_status) override; + + cdm::FileIO* CreateFileIO(cdm::FileIOClient* client) override; + + void RequestStorageId(uint32_t version) override; + + void ReportMetrics(cdm::MetricName metric_name, uint64_t value) override; + + void OnInitialized(bool success) override; + + std::future PrepareSessionFuture(uint32_t promiseId); + + //Misc + ~CdmAdapter(); + bool LoadCDM(); + bool Initialize(); + std::string GetVersion() const; + +protected: + std::mutex m_sessionMx; + std::unordered_map> m_sessionPromises; + +private: + void UnloadCDM(); + using InitializeCdmModuleFunc = void(*)(); + using DeinitializeCdmModuleFunc = void(*)(); + using GetCdmVersionFunc = char* (*)(); + using CreateCdmFunc = void* (*)(int cdm_interface_version, + const char* key_system, + uint32_t key_system_size, + GetCdmHostFunc get_cdm_host_func, + void* user_data); + + InitializeCdmModuleFunc init_cdm_func{nullptr}; + CreateCdmFunc create_cdm_func{nullptr}; + GetCdmVersionFunc get_cdm_verion_func{nullptr}; + DeinitializeCdmModuleFunc deinit_cdm_func{nullptr}; + + void SendClientMessage(const char* session, uint32_t session_size, CdmAdapterClient::CDMADPMSG msg, const uint8_t *data, size_t data_size, uint32_t status); + void SendOnKeyStatusChange(const std::string& sessionId, + const std::vector& keysInfo); + + // Keep a reference to the CDM. + base::NativeLibrary library_{nullptr}; + + std::string cdm_path_; + std::string cdm_base_path_; + CdmAdapterClient* client_{nullptr}; + std::mutex client_mutex_; + std::mutex decrypt_mutex_; + std::mutex m_closeSessionMutex; + std::atomic m_isClosingSession; + std::condition_variable m_sessionClosingCond; + std::vector> m_asyncTimerTasks; + + std::string key_system_; + CdmConfig cdm_config_; + + cdm::Buffer* active_buffer_{nullptr}; + + std::promise m_initPromise; + std::future m_initFuture; + std::atomic m_provisioningCompleteOrStarted; + + cdm::ContentDecryptionModule_10* cdm10_{nullptr}; + cdm::ContentDecryptionModule_11* cdm11_{nullptr}; + cdm::ContentDecryptionModule_12* cdm12_{nullptr}; + + DISALLOW_COPY_AND_ASSIGN(CdmAdapter); +}; + +class CdmFileIoImpl : NON_EXPORTED_BASE(public cdm::FileIO) +{ +public: + CdmFileIoImpl(std::string base_path, cdm::FileIOClient* client); + + virtual void Open(const char* file_name, uint32_t file_name_size) override; + virtual void Read() override; + virtual void Write(const uint8_t* data, uint32_t data_size) override; + virtual void Close() override; + +private: + std::string base_path_; + std::string m_filepath; + cdm::FileIOClient* client_; + FILE *file_descriptor_; + uint8_t *data_buffer_; + bool opened_; +}; + + +} // namespace media + +#endif // MEDIA_CDM_CDM_ADAPTER_H_ --- a/Source/WebKit/UIProcess/Launcher/glib/BubblewrapLauncher.cpp +++ b/Source/WebKit/UIProcess/Launcher/glib/BubblewrapLauncher.cpp @@ -468,6 +468,18 @@ static void bindGStreamerData(Vector& args) +{ + // The external CDM module is dlopen()ed inside the web process, so the + // configured blob must be reachable in the sandbox. + const char* cdmPath = g_getenv("WPE_WIDEVINE_CDM_PATH"); + if (!cdmPath || !*cdmPath) + return; + bindIfExists(args, cdmPath); +} +#endif + static void bindOpenGL(Vector& args) { args.appendList({ @@ -953,6 +965,9 @@ GRefPtr bubblewrapSpawn(GSu bindSndio(sandboxArgs); bindFonts(sandboxArgs); bindGStreamerData(sandboxArgs); +#if ENABLE(EXTERNAL_CDM) + bindExternalCDM(sandboxArgs); +#endif bindOpenGL(sandboxArgs); // Although cameras can be managed via the DesktopPortal, we still need v4l2 for video decoding.