[EME][GStreamer] Introduce CDMProxy
authorcturner@igalia.com <cturner@igalia.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 12 Feb 2020 11:11:22 +0000 (11:11 +0000)
committercturner@igalia.com <cturner@igalia.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 12 Feb 2020 11:11:22 +0000 (11:11 +0000)
https://bugs.webkit.org/show_bug.cgi?id=206730

Reviewed by Xabier Rodriguez-Calvar.

Introduce a new subclass of CDMInstance, CDMProxyInstance.

CDMInstance is a main-thread only class, its purpose is to provide
an interface that will satisfy the JavaScript EME APIs, which by
design, don't actually interact with a real DRM system, what might
also be called "The CDM Instance". That's why the naming is
misleading here, CDMInstance isn't actually an instance of a real
CDM, rather it is a facade for JavaScript.

CDMProxyInstance is a sub-class which provides two APIs,
1/ For background media threads to safely interrogate the status
of keys and to perform decryption using said keys. This API is
exposed by CDMProxy and is platform specific.
2/ For media players to safely assess the state of decryptors and
what their status is.

CDMProxy exists to allow thread-safe access to a real CDM, along
with their quirks. The two main problems GTK integrators of a CDM
have in WebKit is key management and CDM access from background
media threads.

Key management is how to manage the state of keys in a way
coherent with JavaScript, the cross-platform EME implementation,
the real CDMs themselves, and the background media
threads. CDMProxy provides a default key store interface for
sub-classes. Sub-classes can, on receipt of new information from a
real CDM, tell CDMProxy about the keys and their
statuses. CDMProxy is intended to Do The Right Thing from there,
letting JavaScript and WebCore know all the details they need to
about the new state, as well as the media decode threads when they
next come asking.

Access from background threads is ill-advised when using the
thread-unsafe CDMInstance. We used to get away with doing exactly
this, but WebCore recently became more diligent about
thread-unsafe access of main-thread-only data, and we had to do
something to fix the asserts. That something was ProxyCDM (a
terrible name, removed now), which was a hack and please forget
about it. CDMProxy can be safely passed to background threads, and
it will by default provide a thread-safe view of the key store for
background threads to interrogate the key store and perform
CDM-specific decryption / decode.

Covered by existing tests.

* WebCore.xcodeproj/project.pbxproj: Add CDMProxy to build
definition.
* platform/GStreamer.cmake: Add CDMProxy include directories and
implementation files.
* platform/encryptedmedia/CDMInstance.h: Remove references to old
ProxyCDM shim. This approach was a temporary hack to avoid some
thread-safety asserts. I took the liberty of renaming to CDMProxy,
for no other reason that it felt like a better name, sorry for the
confusion.
* platform/encryptedmedia/CDMProxy.cpp: Added.
(WebCore::Key::idAsString const):
(WebCore::Key::valueAsString const):
(WebCore::KeyStore::containsKeyID const):
(WebCore::KeyStore::merge):
(WebCore::KeyStore::allKeysAsReleased const):
(WebCore::KeyStore::addKeys):
(WebCore::KeyStore::add):
(WebCore::KeyStore::removeAllKeysFrom):
(WebCore::KeyStore::remove):
(WebCore::KeyStore::keyValue const):
(WebCore::KeyStore::convertToJSKeyStatusVector const):
(WebCore::CDMProxy::updateKeyStore):
(WebCore::CDMProxy::setInstance):
(WebCore::CDMProxy::keyValue const):
(WebCore::CDMProxy::startedWaitingForKey const):
(WebCore::CDMProxy::stoppedWaitingForKey const):
(WebCore::CDMProxy::tryWaitForKey const):
(WebCore::CDMProxy::keyAvailableUnlocked const):
(WebCore::CDMProxy::keyAvailable const):
(WebCore::CDMProxy::getOrWaitForKey const):
(WebCore::CDMInstanceProxy::startedWaitingForKey):
(WebCore::CDMInstanceProxy::stoppedWaitingForKey):
(WebCore::CDMInstanceProxy::mergeKeysFrom):
(WebCore::CDMInstanceProxy::removeAllKeysFrom):
* platform/encryptedmedia/CDMProxy.h: Added.
(WebCore::Key::create):
(WebCore::Key::idAsSharedBuffer const):
(WebCore::Key::id const):
(WebCore::Key::value const):
(WebCore::Key::value):
(WebCore::Key::status const):
(WebCore::Key::operator==):
(WebCore::Key::addSessionReference):
(WebCore::Key::removeSessionReference):
(WebCore::Key::numSessionReferences const):
(WebCore::Key::Key):
(WebCore::KeyStore::removeAllKeys):
(WebCore::KeyStore::numKeys const):
(WebCore::KeyStore::begin):
(WebCore::KeyStore::begin const):
(WebCore::KeyStore::end):
(WebCore::KeyStore::end const):
(WebCore::KeyStore::rbegin):
(WebCore::KeyStore::rbegin const):
(WebCore::KeyStore::rend):
(WebCore::KeyStore::rend const):
(WebCore::CDMInstanceProxy::setProxy):
(WebCore::CDMInstanceProxy::proxy const):
(WebCore::CDMInstanceProxy::isWaitingForKey const):
(WebCore::CDMInstanceProxy::setPlayer):
(WebCore::CDMInstanceProxy::trackSession):
* platform/encryptedmedia/clearkey/CDMClearKey.cpp:
(WebCore::parseLicenseFormat): Refactored to use new Key class.
(WebCore::extractKeyidsLocationFromCencInitData): Refactored to
use better named constants.
(WebCore::extractKeyidsFromCencInitData): Ditto.
(WebCore::CDMInstanceClearKey::createSession): Use the
trackSession method to allow easier monitoring of all EME
sessions.
(WebCore::CDMInstanceSessionClearKey::requestLicense): Refactor to
keep track of generated session IDs.
(WebCore::CDMInstanceSessionClearKey::updateLicense): Refactor to
use new key management classes.
(WebCore::CDMInstanceSessionClearKey::loadSession): Ditto.
(WebCore::CDMInstanceSessionClearKey::removeSessionData): Ditto.
(WebCore::ClearKeyState::keys): Deleted.
(WebCore::ClearKeyState::singleton): Deleted.
(WebCore::isolatedKey): Deleted.
(WebCore::ProxyCDMClearKey::isolatedKeys const): Deleted.
(WebCore::CDMInstanceClearKey::CDMInstanceClearKey): Deleted.
(WebCore::CDMInstanceClearKey::Key::keyIDAsString const): Deleted.
(WebCore::CDMInstanceClearKey::Key::keyValueAsString const): Deleted.
(WebCore::operator==): Deleted.
(WebCore::operator<): Deleted. It looks like we were unnecessarily
sorting the key vectors. It doesn't make any sense to impose an
ordering on keys, so I removed it.
* platform/encryptedmedia/clearkey/CDMClearKey.h: Refactor to use
new key management classes.
* platform/graphics/avfoundation/objc/CDMInstanceFairPlayStreamingAVFObjC.h:
Remove obsolete ProxyCDM method.
* platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp:
(WebCore::MediaPlayerPrivateGStreamer::parseInitDataFromProtectionMessage):
Refactored out of syncMessage. The code was moved here and into
waitForCDMAttachment.
(WebCore::MediaPlayerPrivateGStreamer::waitForCDMAttachment):
Ditto.
(WebCore::MediaPlayerPrivateGStreamer::handleSyncMessage):
(WebCore::MediaPlayerPrivateGStreamer::handleMessage): Remove
waiting-for-key code, it was all broken anyway.
(WebCore::MediaPlayerPrivateGStreamer::cdmInstanceAttached):
Enforce use of CDMInstanceProxy, all GStreamer-based ports will be
using the CDMProxy derived classes from background
threads. CDMProxy's need a handle to the MediaPlayer for
waitingForKey management, so wire that in here too. Also wire the
GStreamer specific ClearKey proxy here.
(WebCore::MediaPlayerPrivateGStreamer::cdmInstanceDetached):
(WebCore::MediaPlayerPrivateGStreamer::waitingForKey const): Key
status notifications are the responsibility of the CDMProxy now.
(WebCore::MediaPlayerPrivateGStreamer::setWaitingForKey): Deleted.
* platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h: Now
we use CDMInstanceProxy, not CDMInstance.
* platform/graphics/gstreamer/eme/CDMProxyClearKey.cpp:
Added. G*-specific ClearKey implementation.
(WebCore::CDMProxyClearKey::~CDMProxyClearKey):
(WebCore::CDMProxyClearKey::cencSetCounterVector):
(WebCore::CDMProxyClearKey::cencSetDecryptionKey):
(WebCore::CDMProxyClearKey::cencDecryptFullSample):
(WebCore::CDMProxyClearKey::cencDecryptSubsampled):
(WebCore::CDMProxyClearKey::cencDecrypt):
(WebCore::CDMProxyClearKey::initializeGcrypt):
* platform/graphics/gstreamer/eme/CDMProxyClearKey.h: Added.
* platform/graphics/gstreamer/eme/WebKitClearKeyDecryptorGStreamer.cpp:
Refactor to use CDMProxy.
(webkit_media_clear_key_decrypt_class_init):
(webkit_media_clear_key_decrypt_init):
(finalize):
(cdmProxyAttached):
(decrypt):
(handleKeyResponse): Deleted. This was a badly named method that
has been changed to cdmProxyAttached.
(findAndSetKey): Deleted.
* platform/graphics/gstreamer/eme/WebKitCommonEncryptionDecryptorGStreamer.cpp:
(webkit_media_common_encryption_decrypt_class_init):
(transformInPlace):
(isCDMProxyAvailable):
(getCDMProxyFromGstContext):
(attachCDMProxy):
(installCDMProxyIfNotAvailable):
(sinkEventHandler):
(changeState):
(setContext):
(): Deleted.
(isCDMInstanceAvailable): Deleted.
(queryHandler): Deleted.
* platform/graphics/gstreamer/eme/WebKitCommonEncryptionDecryptorGStreamer.h:
* testing/MockCDMFactory.cpp:
(WebCore::MockCDMInstance::proxyCDM const): Deleted.
* testing/MockCDMFactory.h:

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@256429 268f45cc-cd09-0410-ab3c-d52691b4dbfc

19 files changed:
Source/WebCore/ChangeLog
Source/WebCore/Sources.txt
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/platform/GStreamer.cmake
Source/WebCore/platform/encryptedmedia/CDMInstance.h
Source/WebCore/platform/encryptedmedia/CDMProxy.cpp [new file with mode: 0644]
Source/WebCore/platform/encryptedmedia/CDMProxy.h [new file with mode: 0644]
Source/WebCore/platform/encryptedmedia/clearkey/CDMClearKey.cpp
Source/WebCore/platform/encryptedmedia/clearkey/CDMClearKey.h
Source/WebCore/platform/graphics/avfoundation/objc/CDMInstanceFairPlayStreamingAVFObjC.h
Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp
Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h
Source/WebCore/platform/graphics/gstreamer/eme/CDMProxyClearKey.cpp [new file with mode: 0644]
Source/WebCore/platform/graphics/gstreamer/eme/CDMProxyClearKey.h [new file with mode: 0644]
Source/WebCore/platform/graphics/gstreamer/eme/WebKitClearKeyDecryptorGStreamer.cpp
Source/WebCore/platform/graphics/gstreamer/eme/WebKitCommonEncryptionDecryptorGStreamer.cpp
Source/WebCore/platform/graphics/gstreamer/eme/WebKitCommonEncryptionDecryptorGStreamer.h
Source/WebCore/testing/MockCDMFactory.cpp
Source/WebCore/testing/MockCDMFactory.h

index bdb0f25..378ac57 100644 (file)
@@ -1,3 +1,204 @@
+2020-02-12  Charlie Turner  <cturner@igalia.com>
+
+        [EME][GStreamer] Introduce CDMProxy
+        https://bugs.webkit.org/show_bug.cgi?id=206730
+
+        Reviewed by Xabier Rodriguez-Calvar.
+
+        Introduce a new subclass of CDMInstance, CDMProxyInstance.
+
+        CDMInstance is a main-thread only class, its purpose is to provide
+        an interface that will satisfy the JavaScript EME APIs, which by
+        design, don't actually interact with a real DRM system, what might
+        also be called "The CDM Instance". That's why the naming is
+        misleading here, CDMInstance isn't actually an instance of a real
+        CDM, rather it is a facade for JavaScript.
+
+        CDMProxyInstance is a sub-class which provides two APIs,
+        1/ For background media threads to safely interrogate the status
+        of keys and to perform decryption using said keys. This API is
+        exposed by CDMProxy and is platform specific.
+        2/ For media players to safely assess the state of decryptors and
+        what their status is.
+
+        CDMProxy exists to allow thread-safe access to a real CDM, along
+        with their quirks. The two main problems GTK integrators of a CDM
+        have in WebKit is key management and CDM access from background
+        media threads.
+
+        Key management is how to manage the state of keys in a way
+        coherent with JavaScript, the cross-platform EME implementation,
+        the real CDMs themselves, and the background media
+        threads. CDMProxy provides a default key store interface for
+        sub-classes. Sub-classes can, on receipt of new information from a
+        real CDM, tell CDMProxy about the keys and their
+        statuses. CDMProxy is intended to Do The Right Thing from there,
+        letting JavaScript and WebCore know all the details they need to
+        about the new state, as well as the media decode threads when they
+        next come asking.
+
+        Access from background threads is ill-advised when using the
+        thread-unsafe CDMInstance. We used to get away with doing exactly
+        this, but WebCore recently became more diligent about
+        thread-unsafe access of main-thread-only data, and we had to do
+        something to fix the asserts. That something was ProxyCDM (a
+        terrible name, removed now), which was a hack and please forget
+        about it. CDMProxy can be safely passed to background threads, and
+        it will by default provide a thread-safe view of the key store for
+        background threads to interrogate the key store and perform
+        CDM-specific decryption / decode.
+
+        Covered by existing tests.
+
+        * WebCore.xcodeproj/project.pbxproj: Add CDMProxy to build
+        definition.
+        * platform/GStreamer.cmake: Add CDMProxy include directories and
+        implementation files.
+        * platform/encryptedmedia/CDMInstance.h: Remove references to old
+        ProxyCDM shim. This approach was a temporary hack to avoid some
+        thread-safety asserts. I took the liberty of renaming to CDMProxy,
+        for no other reason that it felt like a better name, sorry for the
+        confusion.
+        * platform/encryptedmedia/CDMProxy.cpp: Added.
+        (WebCore::Key::idAsString const):
+        (WebCore::Key::valueAsString const):
+        (WebCore::KeyStore::containsKeyID const):
+        (WebCore::KeyStore::merge):
+        (WebCore::KeyStore::allKeysAsReleased const):
+        (WebCore::KeyStore::addKeys):
+        (WebCore::KeyStore::add):
+        (WebCore::KeyStore::removeAllKeysFrom):
+        (WebCore::KeyStore::remove):
+        (WebCore::KeyStore::keyValue const):
+        (WebCore::KeyStore::convertToJSKeyStatusVector const):
+        (WebCore::CDMProxy::updateKeyStore):
+        (WebCore::CDMProxy::setInstance):
+        (WebCore::CDMProxy::keyValue const):
+        (WebCore::CDMProxy::startedWaitingForKey const):
+        (WebCore::CDMProxy::stoppedWaitingForKey const):
+        (WebCore::CDMProxy::tryWaitForKey const):
+        (WebCore::CDMProxy::keyAvailableUnlocked const):
+        (WebCore::CDMProxy::keyAvailable const):
+        (WebCore::CDMProxy::getOrWaitForKey const):
+        (WebCore::CDMInstanceProxy::startedWaitingForKey):
+        (WebCore::CDMInstanceProxy::stoppedWaitingForKey):
+        (WebCore::CDMInstanceProxy::mergeKeysFrom):
+        (WebCore::CDMInstanceProxy::removeAllKeysFrom):
+        * platform/encryptedmedia/CDMProxy.h: Added.
+        (WebCore::Key::create):
+        (WebCore::Key::idAsSharedBuffer const):
+        (WebCore::Key::id const):
+        (WebCore::Key::value const):
+        (WebCore::Key::value):
+        (WebCore::Key::status const):
+        (WebCore::Key::operator==):
+        (WebCore::Key::addSessionReference):
+        (WebCore::Key::removeSessionReference):
+        (WebCore::Key::numSessionReferences const):
+        (WebCore::Key::Key):
+        (WebCore::KeyStore::removeAllKeys):
+        (WebCore::KeyStore::numKeys const):
+        (WebCore::KeyStore::begin):
+        (WebCore::KeyStore::begin const):
+        (WebCore::KeyStore::end):
+        (WebCore::KeyStore::end const):
+        (WebCore::KeyStore::rbegin):
+        (WebCore::KeyStore::rbegin const):
+        (WebCore::KeyStore::rend):
+        (WebCore::KeyStore::rend const):
+        (WebCore::CDMInstanceProxy::setProxy):
+        (WebCore::CDMInstanceProxy::proxy const):
+        (WebCore::CDMInstanceProxy::isWaitingForKey const):
+        (WebCore::CDMInstanceProxy::setPlayer):
+        (WebCore::CDMInstanceProxy::trackSession):
+        * platform/encryptedmedia/clearkey/CDMClearKey.cpp:
+        (WebCore::parseLicenseFormat): Refactored to use new Key class.
+        (WebCore::extractKeyidsLocationFromCencInitData): Refactored to
+        use better named constants.
+        (WebCore::extractKeyidsFromCencInitData): Ditto.
+        (WebCore::CDMInstanceClearKey::createSession): Use the
+        trackSession method to allow easier monitoring of all EME
+        sessions.
+        (WebCore::CDMInstanceSessionClearKey::requestLicense): Refactor to
+        keep track of generated session IDs.
+        (WebCore::CDMInstanceSessionClearKey::updateLicense): Refactor to
+        use new key management classes.
+        (WebCore::CDMInstanceSessionClearKey::loadSession): Ditto.
+        (WebCore::CDMInstanceSessionClearKey::removeSessionData): Ditto.
+        (WebCore::ClearKeyState::keys): Deleted.
+        (WebCore::ClearKeyState::singleton): Deleted.
+        (WebCore::isolatedKey): Deleted.
+        (WebCore::ProxyCDMClearKey::isolatedKeys const): Deleted.
+        (WebCore::CDMInstanceClearKey::CDMInstanceClearKey): Deleted.
+        (WebCore::CDMInstanceClearKey::Key::keyIDAsString const): Deleted.
+        (WebCore::CDMInstanceClearKey::Key::keyValueAsString const): Deleted.
+        (WebCore::operator==): Deleted.
+        (WebCore::operator<): Deleted. It looks like we were unnecessarily
+        sorting the key vectors. It doesn't make any sense to impose an
+        ordering on keys, so I removed it.
+        * platform/encryptedmedia/clearkey/CDMClearKey.h: Refactor to use
+        new key management classes.
+        * platform/graphics/avfoundation/objc/CDMInstanceFairPlayStreamingAVFObjC.h:
+        Remove obsolete ProxyCDM method.
+        * platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp:
+        (WebCore::MediaPlayerPrivateGStreamer::parseInitDataFromProtectionMessage):
+        Refactored out of syncMessage. The code was moved here and into
+        waitForCDMAttachment.
+        (WebCore::MediaPlayerPrivateGStreamer::waitForCDMAttachment):
+        Ditto.
+        (WebCore::MediaPlayerPrivateGStreamer::handleSyncMessage):
+        (WebCore::MediaPlayerPrivateGStreamer::handleMessage): Remove
+        waiting-for-key code, it was all broken anyway.
+        (WebCore::MediaPlayerPrivateGStreamer::cdmInstanceAttached):
+        Enforce use of CDMInstanceProxy, all GStreamer-based ports will be
+        using the CDMProxy derived classes from background
+        threads. CDMProxy's need a handle to the MediaPlayer for
+        waitingForKey management, so wire that in here too. Also wire the
+        GStreamer specific ClearKey proxy here.
+        (WebCore::MediaPlayerPrivateGStreamer::cdmInstanceDetached):
+        (WebCore::MediaPlayerPrivateGStreamer::waitingForKey const): Key
+        status notifications are the responsibility of the CDMProxy now.
+        (WebCore::MediaPlayerPrivateGStreamer::setWaitingForKey): Deleted.
+        * platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h: Now
+        we use CDMInstanceProxy, not CDMInstance.
+        * platform/graphics/gstreamer/eme/CDMProxyClearKey.cpp:
+        Added. G*-specific ClearKey implementation.
+        (WebCore::CDMProxyClearKey::~CDMProxyClearKey):
+        (WebCore::CDMProxyClearKey::cencSetCounterVector):
+        (WebCore::CDMProxyClearKey::cencSetDecryptionKey):
+        (WebCore::CDMProxyClearKey::cencDecryptFullSample):
+        (WebCore::CDMProxyClearKey::cencDecryptSubsampled):
+        (WebCore::CDMProxyClearKey::cencDecrypt):
+        (WebCore::CDMProxyClearKey::initializeGcrypt):
+        * platform/graphics/gstreamer/eme/CDMProxyClearKey.h: Added.
+        * platform/graphics/gstreamer/eme/WebKitClearKeyDecryptorGStreamer.cpp:
+        Refactor to use CDMProxy.
+        (webkit_media_clear_key_decrypt_class_init):
+        (webkit_media_clear_key_decrypt_init):
+        (finalize):
+        (cdmProxyAttached):
+        (decrypt):
+        (handleKeyResponse): Deleted. This was a badly named method that
+        has been changed to cdmProxyAttached.
+        (findAndSetKey): Deleted.
+        * platform/graphics/gstreamer/eme/WebKitCommonEncryptionDecryptorGStreamer.cpp:
+        (webkit_media_common_encryption_decrypt_class_init):
+        (transformInPlace):
+        (isCDMProxyAvailable):
+        (getCDMProxyFromGstContext):
+        (attachCDMProxy):
+        (installCDMProxyIfNotAvailable):
+        (sinkEventHandler):
+        (changeState):
+        (setContext):
+        (): Deleted.
+        (isCDMInstanceAvailable): Deleted.
+        (queryHandler): Deleted.
+        * platform/graphics/gstreamer/eme/WebKitCommonEncryptionDecryptorGStreamer.h:
+        * testing/MockCDMFactory.cpp:
+        (WebCore::MockCDMInstance::proxyCDM const): Deleted.
+        * testing/MockCDMFactory.h:
+
 2020-02-12  Víctor Manuel Jáquez Leal  <vjaquez@igalia.com>
 
         [GL] Remove unused methods in GLContext
index ea222ca..b5f6a29 100644 (file)
@@ -1819,6 +1819,7 @@ platform/audio/VectorMath.cpp
 platform/audio/ZeroPole.cpp
 
 platform/encryptedmedia/CDMFactory.cpp
+platform/encryptedmedia/CDMProxy.cpp
 
 platform/graphics/ANGLEWebKitBridge.cpp
 platform/graphics/BitmapImage.cpp
index 777d4a5..b18e742 100644 (file)
                CD94A5D01F71CB6B00F525C5 /* CDMRestrictions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDMRestrictions.h; sourceTree = "<group>"; };
                CD94A5D11F71CB6B00F525C5 /* CDMFactory.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CDMFactory.cpp; sourceTree = "<group>"; };
                CD94A5D21F71CB6B00F525C5 /* CDMFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDMFactory.h; sourceTree = "<group>"; };
+               CD94A5CF1F71CB6100F525D5 /* CDMProxy.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CDMProxy.cpp; sourceTree = "<group>"; };
+               CD94A5CF1F71CB6100F525C5 /* CDMProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDMProxy.h; sourceTree = "<group>"; };
                CD94A5D31F71CB6C00F525C5 /* CDMMediaCapability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDMMediaCapability.h; sourceTree = "<group>"; };
                CD94A5D41F71CB6C00F525C5 /* CDMKeyStatus.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDMKeyStatus.h; sourceTree = "<group>"; };
                CD94A5D51F71CB6D00F525C5 /* CDMSessionType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDMSessionType.h; sourceTree = "<group>"; };
                                CD94A5D31F71CB6C00F525C5 /* CDMMediaCapability.h */,
                                CD94A5D61F71CB6D00F525C5 /* CDMMessageType.h */,
                                CD94A5CF1F71CB6A00F525C5 /* CDMPrivate.h */,
+                               CD94A5CF1F71CB6100F525D5 /* CDMProxy.cpp */,
+                               CD94A5CF1F71CB6100F525C5 /* CDMProxy.h */,
                                CD94A5CC1F71CB6900F525C5 /* CDMRequirement.h */,
                                CD94A5D01F71CB6B00F525C5 /* CDMRestrictions.h */,
                                CD94A5D51F71CB6D00F525C5 /* CDMSessionType.h */,
index 874d49a..efa5e75 100644 (file)
@@ -164,9 +164,10 @@ if (ENABLE_ENCRYPTED_MEDIA)
     )
 
     list(APPEND WebCore_SOURCES
+        platform/encryptedmedia/CDMProxy.cpp
         platform/encryptedmedia/clearkey/CDMClearKey.cpp
-
         platform/graphics/gstreamer/eme/CDMFactoryGStreamer.cpp
+        platform/graphics/gstreamer/eme/CDMProxyClearKey.cpp
     )
 
     list(APPEND WebCore_SYSTEM_INCLUDE_DIRECTORIES
index a8d7cc1..8f9f3eb 100644 (file)
 namespace WebCore {
 
 class SharedBuffer;
-
 class CDMInstanceSession;
-class ProxyCDM;
-
-// Handle to a "real" CDM, not the JavaScript facade. This can be used
-// from background threads (i.e. decryptors).
-class ProxyCDM : public ThreadSafeRefCounted<ProxyCDM> {
-public:
-    virtual ~ProxyCDM() = default;
-};
-
 struct CDMKeySystemConfiguration;
 
 // JavaScript's handle to a CDMInstance, must be used from the
@@ -77,7 +67,6 @@ public:
     virtual SuccessValue setStorageDirectory(const String&) = 0;
     virtual const String& keySystem() const = 0;
     virtual RefPtr<CDMInstanceSession> createSession() = 0;
-    virtual RefPtr<ProxyCDM> proxyCDM() const = 0;
 
     enum class HDCPStatus {
         Unknown,
diff --git a/Source/WebCore/platform/encryptedmedia/CDMProxy.cpp b/Source/WebCore/platform/encryptedmedia/CDMProxy.cpp
new file mode 100644 (file)
index 0000000..81b2521
--- /dev/null
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2020 Metrological Group B.V.
+ * Copyright (C) 2020 Igalia S.L.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials provided
+ *    with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "CDMProxy.h"
+
+#if ENABLE(ENCRYPTED_MEDIA)
+
+#include "Logging.h"
+#include <wtf/HexNumber.h>
+#include <wtf/Scope.h>
+#include <wtf/text/StringBuilder.h>
+
+namespace WebCore {
+
+namespace {
+
+static String vectorToHexString(const Vector<uint8_t>& vec)
+{
+    StringBuilder stringBuilder;
+    for (auto byte : vec)
+        stringBuilder.append(pad('0', 2, hex(byte)));
+    return stringBuilder.toString();
+}
+
+} // namespace {}
+
+String Key::idAsString() const
+{
+    return makeString("[", vectorToHexString(m_id), "]");
+}
+
+String Key::valueAsString() const
+{
+    return makeString("[", vectorToHexString(m_value), "]");
+}
+
+bool KeyStore::containsKeyID(const Vector<uint8_t>& keyID) const
+{
+    return m_keys.findMatching([&](const RefPtr<Key>& storedKey) {
+        return *storedKey == keyID;
+    }) != WTF::notFound;
+}
+
+void KeyStore::merge(const KeyStore& other)
+{
+    LOG(EME, "EME - CDMProxy - merging %u new keys into a key store of %u keys", other.numKeys(), numKeys());
+    for (const auto& key : other) {
+        // NOTE: Do we care that we will not append a key if it matches a key ID
+        // in the keystore and has different data. Should we overwrite? Which is "newer"?
+        // Don't think we need this extra complexity.
+        if (m_keys.findMatching([&] (const RefPtr<Key>& storedKey) { return *key == *storedKey; }) == WTF::notFound)
+            m_keys.append(key);
+        else
+            // NOTE: This does happen with DASH players, it seems harmless to ignore it.
+            LOG(EME, "EME - CDMProxy - ignored a key with the same ID and different data");
+    }
+
+#if !LOG_DISABLED
+    LOG(EME, "EME - CDMProxy - key store now has %u keys", numKeys());
+    for (const auto& key : m_keys)
+        LOG(EME, "\tEME - CDMProxy - Key ID: %s", key->idAsString().ascii().data());
+#endif // !LOG_DISABLED
+}
+
+CDMInstanceSession::KeyStatusVector KeyStore::allKeysAsReleased() const
+{
+    CDMInstanceSession::KeyStatusVector keyStatusVector = convertToJSKeyStatusVector();
+    for (auto& keyStatus : keyStatusVector)
+        keyStatus.second = CDMInstanceSession::KeyStatus::Released;
+    return keyStatusVector;
+}
+
+bool KeyStore::addKeys(Vector<RefPtr<Key>>&& newKeys)
+{
+    bool didKeyStoreChange = false;
+    for (auto& key : newKeys) {
+        if (add(WTFMove(key)))
+            didKeyStoreChange = true;
+    }
+    return didKeyStoreChange;
+}
+
+bool KeyStore::add(RefPtr<Key>&& key)
+{
+    bool didStoreChange = false;
+    size_t keyWithMatchingKeyIDIndex = m_keys.findMatching([&] (const RefPtr<Key>& storedKey) {
+        return *key == *storedKey;
+    });
+
+    if (keyWithMatchingKeyIDIndex != WTF::notFound) {
+        auto& keyWithMatchingKeyID = m_keys[keyWithMatchingKeyIDIndex];
+        RELEASE_ASSERT(keyWithMatchingKeyID->value() == key->value(), "Can this really happen?");
+    } else {
+        LOG(EME, "EME - ClearKey - New key with ID %s getting added to key store", key->idAsString().ascii().data());      
+        m_keys.append(key);
+        didStoreChange = true;
+    }
+
+    key->addSessionReference();
+    return didStoreChange;
+}
+
+void KeyStore::removeAllKeysFrom(const KeyStore& other)
+{
+    for (const auto& key : other)
+        remove(key);
+}
+
+bool KeyStore::remove(const RefPtr<Key>& key)
+{
+    bool storeChanged = false;
+
+    size_t keyWithMatchingKeyIDIndex = m_keys.find(key);
+    LOG(EME, "EME - ClearKey - requested to remove key with ID %s and %u session references", key->idAsString().ascii().data(), key->numSessionReferences());
+
+    if (keyWithMatchingKeyIDIndex != WTF::notFound) {
+        auto& keyWithMatchingKeyID = m_keys[keyWithMatchingKeyIDIndex];
+        keyWithMatchingKeyID->removeSessionReference();
+        if (!keyWithMatchingKeyID->numSessionReferences()) {
+            LOG(EME, "EME - ClearKey - remove key with ID %s", keyWithMatchingKeyID->idAsString().ascii().data());
+            m_keys.remove(keyWithMatchingKeyIDIndex);
+            storeChanged = true;
+        }
+    } else
+        LOG(EME, "EME - ClearKey - attempt to remove key with ID %s ignored, does not exist", key->idAsString().ascii().data());
+
+    return storeChanged;
+}
+
+const Vector<uint8_t>& KeyStore::keyValue(const Vector<uint8_t>& keyID) const
+{
+    for (const auto& key : m_keys) {
+        if (*key == keyID)
+            return key->value();
+    }
+    
+    RELEASE_ASSERT(false && "key must exist to call this method");
+    UNREACHABLE();
+}
+
+CDMInstanceSession::KeyStatusVector KeyStore::convertToJSKeyStatusVector() const
+{
+    CDMInstanceSession::KeyStatusVector keyStatusVector;
+    keyStatusVector.reserveInitialCapacity(numKeys());
+    for (const auto& key : m_keys)
+        keyStatusVector.uncheckedAppend(std::pair<Ref<SharedBuffer>, CDMInstanceSession::KeyStatus> { key->idAsSharedBuffer(), key->status() });
+    return keyStatusVector;
+}
+
+void CDMProxy::updateKeyStore(const KeyStore& newKeyStore)
+{
+    auto locker = holdLock(m_keysMutex);
+    m_keyStore.merge(newKeyStore);
+    LOG(EME, "EME - CDMProxy - updating key store from a session update");
+    m_keysCondition.notifyAll();
+}
+
+void CDMProxy::setInstance(CDMInstanceProxy* instance)
+{
+    auto locker = holdLock(m_instanceMutex);
+    m_instance = instance;
+}
+
+Vector<uint8_t> CDMProxy::keyValue(const Vector<uint8_t>& keyID) const
+{
+    auto locker = holdLock(m_keysMutex);
+    ASSERT(m_keyStore.containsKeyID(keyID));
+    return m_keyStore.keyValue(keyID);
+}
+
+void CDMProxy::startedWaitingForKey() const
+{
+    auto locker = holdLock(m_instanceMutex);
+    LOG(EME, "EME - CDMProxy - started waiting for a key");
+    ASSERT(m_instance);
+    m_instance->startedWaitingForKey();
+}
+
+void CDMProxy::stoppedWaitingForKey() const
+{
+    auto locker = holdLock(m_instanceMutex);
+    LOG(EME, "EME - CDMProxy - stopped waiting for a key");
+    ASSERT(m_instance);
+    m_instance->stoppedWaitingForKey();
+}
+
+Optional<Vector<uint8_t>> CDMProxy::tryWaitForKey(const Vector<uint8_t>& keyID) const
+{
+    startedWaitingForKey();
+    // Unconditionally saying we have stopped waiting for a key means that decryptors only get
+    // one shot at fetching a key. If MaxKeyWaitTimeSeconds expires, that's it, no more clear bytes
+    // for you.
+    auto stopWaitingForKeyOnReturn = makeScopeExit([this] {
+        stoppedWaitingForKey();
+    });
+    LOG(EME, "EME - CDMProxy - trying to wait for key ID %s", vectorToHexString(keyID).ascii().data());
+    bool wasKeyReceived = false;
+    {
+        auto locker = holdLock(m_keysMutex);
+        wasKeyReceived = m_keysCondition.waitFor(m_keysMutex, CDMProxy::MaxKeyWaitTimeSeconds, [&, this, keyID]() {
+            return keyAvailableUnlocked(keyID);
+        });
+    }
+
+    if (wasKeyReceived) {
+        LOG(EME, "EME - CDMProxy - successfully waited for key ID %s", vectorToHexString(keyID).ascii().data());
+        return keyValue(keyID);
+    }
+    
+    LOG(EME, "EME - CDMProxy - key ID %s not available", vectorToHexString(keyID).ascii().data());
+    return WTF::nullopt;
+}
+
+bool CDMProxy::keyAvailableUnlocked(const Vector<uint8_t>& keyID) const
+{
+    return m_keyStore.containsKeyID(keyID);
+}
+
+bool CDMProxy::keyAvailable(const Vector<uint8_t>& keyID) const
+{
+    auto locker = holdLock(m_keysMutex);
+    return keyAvailableUnlocked(keyID);
+}
+
+Optional<Vector<uint8_t>> CDMProxy::getOrWaitForKey(const Vector<uint8_t>& keyID) const
+{
+    if (!keyAvailable(keyID)) {
+        LOG(EME, "EME - CDMProxy key cache does not contain key ID %s", vectorToHexString(keyID).ascii().data());
+        return tryWaitForKey(keyID);
+    }
+
+    return keyValue(keyID);
+}
+
+void CDMInstanceProxy::startedWaitingForKey()
+{
+    ASSERT(!isMainThread() && m_player);
+    bool wasWaitingForKey = m_numDecryptorsWaitingForKey > 0;
+    m_numDecryptorsWaitingForKey++;
+
+    callOnMainThread([player = m_player, wasWaitingForKey] {
+        if (player && wasWaitingForKey)
+            player->waitingForKeyChanged();
+    });
+}
+
+void CDMInstanceProxy::stoppedWaitingForKey()
+{
+    ASSERT(!isMainThread() && m_player && m_numDecryptorsWaitingForKey > 0);
+    m_numDecryptorsWaitingForKey--;
+    bool isNobodyWaitingForKey = !m_numDecryptorsWaitingForKey;
+
+    callOnMainThread([player = m_player, isNobodyWaitingForKey] {
+        if (player && isNobodyWaitingForKey)
+            player->waitingForKeyChanged();
+    });
+}
+
+void CDMInstanceProxy::mergeKeysFrom(const KeyStore& keyStore)
+{
+    // FIXME: Notify JS when appropriate.
+    ASSERT(isMainThread());
+    m_keyStore.merge(keyStore);
+    LOG(EME, "EME - CDMInstanceProxy - merging keys into proxy instance and notifying CDMProxy of changes");
+    ASSERT(m_cdmProxy);
+    m_cdmProxy->updateKeyStore(keyStore);
+}
+
+void CDMInstanceProxy::removeAllKeysFrom(const KeyStore& keyStore)
+{
+    ASSERT(isMainThread());
+    m_keyStore.removeAllKeysFrom(keyStore);
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(ENCRYPTED_MEDIA)
diff --git a/Source/WebCore/platform/encryptedmedia/CDMProxy.h b/Source/WebCore/platform/encryptedmedia/CDMProxy.h
new file mode 100644 (file)
index 0000000..1b6ad79
--- /dev/null
@@ -0,0 +1,185 @@
+/*
+ * 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)
+
+#include "CDMInstance.h"
+#include "CDMInstanceSession.h"
+#include "MediaPlayerPrivate.h"
+#include "SharedBuffer.h"
+#include <wtf/Condition.h>
+#include <wtf/VectorHash.h>
+
+namespace WebCore {
+
+class Key : public RefCounted<Key> {
+public:
+    using KeyStatus = CDMInstanceSession::KeyStatus;
+
+    static RefPtr<Key> create(KeyStatus status, Vector<uint8_t>&& keyID, Vector<uint8_t>&& keyValue)
+    {
+        return adoptRef(*new Key(status, WTFMove(keyID), WTFMove(keyValue)));
+    }
+    Ref<SharedBuffer> idAsSharedBuffer() const { return SharedBuffer::create(m_id.data(), m_id.size()); }
+
+    const Vector<uint8_t>& id() const { return m_id; }
+    const Vector<uint8_t>& value() const { return m_value; }
+    Vector<uint8_t>& value() { return m_value; }
+    KeyStatus status() const { return m_status; }
+
+    String idAsString() const;
+    String valueAsString() const;
+
+    // Two keys are equal if they have the same ID, ignoring key value and status.
+    friend bool operator==(const Key &k1, const Key &k2) { return k1.m_id == k2.m_id; }
+    friend bool operator==(const Key &k, const Vector<uint8_t>& keyID) { return k.m_id == keyID; }
+    friend bool operator==(const Vector<uint8_t>& keyID, const Key &k) { return k == keyID; }
+
+private:
+    void addSessionReference() { ASSERT(isMainThread()); m_numSessionReferences++; }
+    void removeSessionReference() { ASSERT(isMainThread()); m_numSessionReferences--; }
+    unsigned numSessionReferences() const { ASSERT(isMainThread()); return m_numSessionReferences; }
+    friend class KeyStore;
+
+    Key(KeyStatus status, Vector<uint8_t>&& keyID, Vector<uint8_t>&& keyValue)
+        : m_status(status), m_id(WTFMove(keyID)), m_value(WTFMove(keyValue)) { }
+
+    KeyStatus m_status;
+    Vector<uint8_t> m_id;
+    Vector<uint8_t> m_value;
+    unsigned m_numSessionReferences { 0 };
+};
+
+class KeyStore {
+public:
+    using KeyStatusVector = CDMInstanceSession::KeyStatusVector;
+
+    bool containsKeyID(const Vector<uint8_t>& keyID) const;
+    void merge(const KeyStore& other);
+    void removeAllKeysFrom(const KeyStore& other);
+    void removeAllKeys() { m_keys.clear(); }
+    bool addKeys(Vector<RefPtr<Key>>&&);
+    bool add(RefPtr<Key>&&);
+    bool remove(const RefPtr<Key>&);
+    unsigned numKeys() const { return m_keys.size(); }
+    const Vector<uint8_t>& keyValue(const Vector<uint8_t>& keyID) const;
+    KeyStatusVector allKeysAsReleased() const;
+    KeyStatusVector convertToJSKeyStatusVector() const;
+
+    auto begin() { return m_keys.begin(); }
+    auto begin() const { return m_keys.begin(); }
+    auto end() { return m_keys.end(); }
+    auto end() const { return m_keys.end(); }
+    auto rbegin() { return m_keys.rbegin(); }
+    auto rbegin() const { return m_keys.rbegin(); }
+    auto rend() { return m_keys.rend(); }
+    auto rend() const { return m_keys.rend(); }
+
+private:
+    Vector<RefPtr<Key>> m_keys;
+};
+
+class CDMInstanceProxy;
+
+// Handle to a "real" CDM, not the JavaScript facade. This can be used
+// from background threads (i.e. decryptors).
+class CDMProxy : public ThreadSafeRefCounted<CDMProxy> {
+public:
+    static constexpr Seconds MaxKeyWaitTimeSeconds = 5_s;
+
+    virtual ~CDMProxy() = default;
+
+    void updateKeyStore(const KeyStore& newKeyStore);
+    void setInstance(CDMInstanceProxy*);
+
+protected:
+    Vector<uint8_t> keyValue(const Vector<uint8_t>& keyID) const;
+    bool keyAvailable(const Vector<uint8_t>& keyID) const;
+    bool keyAvailableUnlocked(const Vector<uint8_t>& keyID) const;
+    Optional<Vector<uint8_t>> tryWaitForKey(const Vector<uint8_t>& keyID) const;
+    Optional<Vector<uint8_t>> getOrWaitForKey(const Vector<uint8_t>& keyID) const;
+    void startedWaitingForKey() const;
+    void stoppedWaitingForKey() const;
+
+private:
+    mutable Lock m_instanceMutex;
+    CDMInstanceProxy* m_instance;
+
+    mutable Lock m_keysMutex;
+    mutable Condition m_keysCondition;
+    // FIXME: Duplicated key stores in the instance and the proxy are probably not needed, but simplified
+    // the initial implementation in terms of threading invariants.
+    KeyStore m_keyStore;
+};
+
+// Base class for common session management code and for communicating messages
+// from "real CDM" state changes to JS.
+class CDMInstanceProxy : public CDMInstance {
+public:
+    CDMInstanceProxy() = default;
+    virtual ~CDMInstanceProxy() = default;
+
+    // Main-thread only.
+    void setProxy(Ref<CDMProxy>&& proxy)
+    {
+        m_cdmProxy = WTFMove(proxy);
+        m_cdmProxy->setInstance(this);
+    }
+    void mergeKeysFrom(const KeyStore&);
+    void removeAllKeysFrom(const KeyStore&);
+
+    // Media player query methods - main thread only.
+    RefPtr<CDMProxy> proxy() const { ASSERT(isMainThread()); return m_cdmProxy; }
+    virtual bool isWaitingForKey() const { ASSERT(isMainThread()); return m_numDecryptorsWaitingForKey > 0; }
+    void setPlayer(MediaPlayer* player) { ASSERT(isMainThread()); m_player = player; }
+
+    // Proxy methods - must be thread-safe.
+    void startedWaitingForKey();
+    void stoppedWaitingForKey();
+
+protected:
+    void trackSession(const RefPtr<CDMInstanceSession> session) { m_sessions.append(session); }
+
+private:
+    RefPtr<CDMProxy> m_cdmProxy;
+    // FIXME: WeakPtr for the m_player? This is accessed from background and main threads, it's
+    // concerning we could be accessing it in the middle of a shutdown on the main-thread, eh?
+    // As a CDMProxy, we ***should*** be turned off before this pointer ever goes bad.
+    MediaPlayer* m_player { nullptr }; // FIXME: MainThread<T>?
+
+    std::atomic<int> m_numDecryptorsWaitingForKey;
+    Vector<RefPtr<CDMInstanceSession>> m_sessions;
+
+    KeyStore m_keyStore;
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(ENCRYPTED_MEDIA)
index 6485db7..45111dc 100644 (file)
@@ -36,6 +36,8 @@
 #include "CDMSessionType.h"
 #include "Logging.h"
 #include "SharedBuffer.h"
+#include <algorithm>
+#include <iterator>
 #include <wtf/JSONValues.h>
 #include <wtf/MainThread.h>
 #include <wtf/NeverDestroyed.h>
 
 namespace WebCore {
 
-// ClearKey CENC SystemID.
-// https://www.w3.org/TR/eme-initdata-cenc/#common-system
-const uint8_t clearKeyCencSystemId[] = { 0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b };
-const unsigned clearKeyCencSystemIdSize = sizeof(clearKeyCencSystemId);
-const unsigned keyIdSize = 16;
-
-class ClearKeyState {
-    using KeyStore = HashMap<String, Vector<CDMInstanceClearKey::Key>>;
-
-public:
-    static ClearKeyState& singleton();
-
-    KeyStore& keys() { return m_keys; }
-
-private:
-    friend class NeverDestroyed<ClearKeyState>;
-    ClearKeyState();
-    KeyStore m_keys;
-};
-
-ClearKeyState& ClearKeyState::singleton()
-{
-    static NeverDestroyed<ClearKeyState> s_state;
-    return s_state;
-}
-
-ClearKeyState::ClearKeyState() = default;
-
 static RefPtr<JSON::Object> parseJSONObject(const SharedBuffer& buffer)
 {
     // Fail on large buffers whose size doesn't fit into a 32-bit unsigned integer.
@@ -88,7 +62,7 @@ static RefPtr<JSON::Object> parseJSONObject(const SharedBuffer& buffer)
     return object;
 }
 
-static Optional<Vector<CDMInstanceClearKey::Key>> parseLicenseFormat(const JSON::Object& root)
+static Optional<Vector<RefPtr<Key>>> parseLicenseFormat(const JSON::Object& root)
 {
     // If the 'keys' key is present in the root object, parse the JSON further
     // according to the specified 'license' format.
@@ -101,7 +75,7 @@ static Optional<Vector<CDMInstanceClearKey::Key>> parseLicenseFormat(const JSON:
     if (!it->value->asArray(keysArray))
         return WTF::nullopt;
 
-    Vector<CDMInstanceClearKey::Key> decodedKeys;
+    Vector<RefPtr<Key>> decodedKeys;
     bool validFormat = std::all_of(keysArray->begin(), keysArray->end(),
         [&decodedKeys] (const auto& value) {
             RefPtr<JSON::Object> keyObject;
@@ -116,11 +90,11 @@ static Optional<Vector<CDMInstanceClearKey::Key>> parseLicenseFormat(const JSON:
             if (!keyObject->getString("kid", keyID) || !keyObject->getString("k", keyValue))
                 return false;
 
-            Vector<char> keyIDData, keyValueData;
+            Vector<uint8_t> keyIDData, keyValueData;
             if (!WTF::base64URLDecode(keyID, { keyIDData }) || !WTF::base64URLDecode(keyValue, { keyValueData }))
                 return false;
 
-            decodedKeys.append({ CDMInstanceSession::KeyStatus::Usable, SharedBuffer::create(WTFMove(keyIDData)), SharedBuffer::create(WTFMove(keyValueData)) });
+            decodedKeys.append(Key::create(CDMInstanceSession::KeyStatus::Usable, WTFMove(keyIDData), WTFMove(keyValueData)));
             return true;
         });
     if (!validFormat)
@@ -181,7 +155,7 @@ static std::pair<unsigned, unsigned> extractKeyidsLocationFromCencInitData(const
     while (true) {
 
         // Check the overflow InitData.
-        if (index + 12 + clearKeyCencSystemIdSize >= initDataSize)
+        if (index + 12 + ClearKey::cencSystemIdSize >= initDataSize)
             return keyIdsMap;
 
         psshSize = data[index + 2] * 256 + data[index + 3];
@@ -191,7 +165,7 @@ static std::pair<unsigned, unsigned> extractKeyidsLocationFromCencInitData(const
             return keyIdsMap;
 
         // 12 = BMFF box header + Full box header.
-        if (!memcmp(&data[index + 12], clearKeyCencSystemId, clearKeyCencSystemIdSize)) {
+        if (!memcmp(&data[index + 12], ClearKey::cencSystemId, ClearKey::cencSystemIdSize)) {
             foundPssh = true;
             break;
         }
@@ -202,7 +176,7 @@ static std::pair<unsigned, unsigned> extractKeyidsLocationFromCencInitData(const
     if (!foundPssh)
         return keyIdsMap;
 
-    index += (12 + clearKeyCencSystemIdSize); // 12 (BMFF box header + Full box header) + SystemID size.
+    index += (12 + ClearKey::cencSystemIdSize); // 12 (BMFF box header + Full box header) + SystemID size.
 
     // Check the overflow.
     if (index + 3 >= initDataSize)
@@ -212,7 +186,7 @@ static std::pair<unsigned, unsigned> extractKeyidsLocationFromCencInitData(const
     index += 4; // KeyIdsCount size.
 
     // Check the overflow.
-    if ((index + (keyIdsMap.first * keyIdSize)) >= initDataSize)
+    if ((index + (keyIdsMap.first * ClearKey::KeyIDSizeInBytes)) >= initDataSize)
         return keyIdsMap;
 
     keyIdsMap.second = index; // The location of the first KeyId in initData.
@@ -251,9 +225,9 @@ static Ref<SharedBuffer> extractKeyidsFromCencInitData(const SharedBuffer& initD
     // "kids"
     // An array of key IDs. Each element of the array is the base64url encoding of the octet sequence containing the key ID value.
     for (unsigned i = 0; i < keyIdCount; i++) {
-        String keyId = WTF::base64URLEncode(&data[index], keyIdSize);
+        String keyId = WTF::base64URLEncode(&data[index], ClearKey::KeyIDSizeInBytes);
         keyIdsArray->pushString(keyId);
-        index += keyIdSize;
+        index += ClearKey::KeyIDSizeInBytes;
     }
 
     object->setArray("kids", WTFMove(keyIdsArray));
@@ -456,44 +430,6 @@ Optional<String> CDMPrivateClearKey::sanitizeSessionId(const String& sessionId)
     return sessionId;
 }
 
-// This is for thread-safety during an architectural situation that is
-// less than ideal. The GStreamer decryptors currently need to iterate
-// all known session keys to find the key data for priming
-// GCrypt. Ideally, all decryption would be the responsibility of
-// ProxyCDM object like this one. What the background GStreamer
-// thread was doing was getting copies (i.e. ref()'s) of SharedBuffers
-// created on the main-thread. With the new safety assertions in
-// WebKit, we can no longer do this. Instead, convert the refcounted
-// SharedBuffers into Strings which can be safely copied across
-// threads.
-static ProxyCDMClearKey::Key isolatedKey(const CDMInstanceClearKey::Key& key)
-{
-    return { key.status, String(key.keyIDData->data(), key.keyIDData->size()), String(key.keyValueData->data(), key.keyValueData->size()) };
-}
-
-const Vector<ProxyCDMClearKey::Key> ProxyCDMClearKey::isolatedKeys() const
-{
-    // Return the keys of all sessions, may be copied to background threads.
-    Vector<ProxyCDMClearKey::Key> allKeys { };
-    auto locker = holdLock(m_keysMutex);
-    size_t initialCapacity = 0;
-    for (auto& keyVector : ClearKeyState::singleton().keys().values())
-        initialCapacity += keyVector.size();
-    allKeys.reserveInitialCapacity(initialCapacity);
-
-    for (auto& keyVector : ClearKeyState::singleton().keys().values()) {
-        for (auto& key : keyVector)
-            allKeys.uncheckedAppend(isolatedKey(key));
-    }
-
-    return allKeys;
-}
-
-CDMInstanceClearKey::CDMInstanceClearKey()
-    : m_proxyCDM(adoptRef(*new ProxyCDMClearKey()))
-{
-}
-
 CDMInstanceClearKey::~CDMInstanceClearKey() = default;
 
 CDMInstance::SuccessValue CDMInstanceClearKey::initializeWithConfiguration(const CDMKeySystemConfiguration&)
@@ -535,36 +471,9 @@ const String& CDMInstanceClearKey::keySystem() const
 
 RefPtr<CDMInstanceSession> CDMInstanceClearKey::createSession()
 {
-    return adoptRef(new CDMInstanceSessionClearKey());
-}
-
-String CDMInstanceClearKey::Key::keyIDAsString() const
-{
-    return makeString("[", keyIDData->toHexString(), "]");
-}
-
-String CDMInstanceClearKey::Key::keyValueAsString() const
-{
-    return makeString("[", keyValueData->toHexString(), "]");
-}
-
-bool operator==(const CDMInstanceClearKey::Key& k1, const CDMInstanceClearKey::Key& k2)
-{
-    ASSERT(k1.keyIDData);
-    ASSERT(k2.keyIDData);
-
-    return *k1.keyIDData == *k2.keyIDData;
-}
-
-bool operator<(const CDMInstanceClearKey::Key& k1, const CDMInstanceClearKey::Key& k2)
-{
-    ASSERT(k1.keyIDData);
-    ASSERT(k2.keyIDData);
-
-    if (k1.keyIDData->size() != k2.keyIDData->size())
-        return k1.keyIDData->size() < k2.keyIDData->size();
-
-    return memcmp(k1.keyIDData->data(), k2.keyIDData->data(), k1.keyIDData->size());
+    RefPtr<CDMInstanceSession> newSession = adoptRef(new CDMInstanceSessionClearKey(*this));
+    trackSession(newSession);
+    return newSession;
 }
 
 void CDMInstanceSessionClearKey::requestLicense(LicenseType, const AtomString& initDataType, Ref<SharedBuffer>&& initData, LicenseCallback&& callback)
@@ -572,6 +481,8 @@ void CDMInstanceSessionClearKey::requestLicense(LicenseType, const AtomString& i
     static uint32_t s_sessionIdValue = 0;
     ++s_sessionIdValue;
 
+    m_sessionID = String::number(s_sessionIdValue);
+
     if (equalLettersIgnoringASCIICase(initDataType, "cenc"))
         initData = extractKeyidsFromCencInitData(initData.get());
 
@@ -579,18 +490,21 @@ void CDMInstanceSessionClearKey::requestLicense(LicenseType, const AtomString& i
         initData = extractKeyIdFromWebMInitData(initData.get());
 
     callOnMainThread(
-        [weakThis = makeWeakPtr(*this), callback = WTFMove(callback), initData = WTFMove(initData), sessionIdValue = s_sessionIdValue]() mutable {
+        [weakThis = makeWeakPtr(*this), this, callback = WTFMove(callback), initData = WTFMove(initData)]() mutable {
             if (!weakThis)
                 return;
 
-            callback(WTFMove(initData), String::number(sessionIdValue), false, Succeeded);
+            callback(WTFMove(initData), m_sessionID, false, Succeeded);
         });
 }
 
 void CDMInstanceSessionClearKey::updateLicense(const String& sessionId, LicenseType, const SharedBuffer& response, LicenseUpdateCallback&& callback)
 {
-    // Use a helper functor that schedules the callback dispatch, avoiding
-    // duplicated callOnMainThread() calls.
+#if LOG_DISABLED
+    // We only use the sesion ID for debug logging. The verbose preprocessor checks are because
+    // the Mac port has -Werror -Wunused-parameter.
+    UNUSED_PARAM(sessionId);
+#endif
     auto dispatchCallback =
         [this, &callback](bool sessionWasClosed, Optional<KeyStatusVector>&& changedKeys, SuccessValue succeeded) {
             callOnMainThread(
@@ -604,52 +518,23 @@ void CDMInstanceSessionClearKey::updateLicense(const String& sessionId, LicenseT
 
     RefPtr<JSON::Object> root = parseJSONObject(response);
     if (!root) {
+        LOG(EME, "EME - ClearKey - session %s update payload was not valid JSON", sessionId.utf8().data());
         dispatchCallback(false, WTF::nullopt, SuccessValue::Failed);
         return;
     }
 
-    LOG(EME, "EME - ClearKey - updating license for session %s", sessionId.utf8().data());
+    LOG(EME, "EME - ClearKey - updating license for session %s which currently contains %u keys", sessionId.utf8().data(), m_keyStore.numKeys());
 
     if (auto decodedKeys = parseLicenseFormat(*root)) {
-        // Retrieve the target Vector of Key objects for this session.
-        // FIXME: Refactor this state management code.
-        Vector<CDMInstanceClearKey::Key>& keyVector = ClearKeyState::singleton().keys().ensure(sessionId, [] { return Vector<CDMInstanceClearKey::Key> { }; }).iterator->value;
-
-        bool keysChanged = false;
-        for (auto& decodedKey : *decodedKeys) {
-            LOG(EME, "EME - ClearKey - Decoded a key with ID %s and key data %s", decodedKey.keyIDAsString().utf8().data(), decodedKey.keyValueAsString().utf8().data());
-            auto keyWithMatchingKeyID = std::find_if(keyVector.begin(), keyVector.end(),
-                [&decodedKey] (const CDMInstanceClearKey::Key& containedKey) {
-                    return containedKey == decodedKey;
-                });
-            if (keyWithMatchingKeyID != keyVector.end()) {
-                LOG(EME, "EME - ClearKey - Existing key found with data %s", keyWithMatchingKeyID->keyValueAsString().utf8().data());
-
-                if (!keyWithMatchingKeyID->hasSameKeyValue(decodedKey)) {
-                    LOG(EME, "EME - ClearKey - Updating key since the data are different");
-                    *keyWithMatchingKeyID = WTFMove(decodedKey);
-                    keysChanged = true;
-                }
-            } else {
-                LOG(EME, "EME - ClearKey - This is a new key");
-                keyVector.append(WTFMove(decodedKey));
-                keysChanged = true;
-            }
-        }
+        bool keysChanged = m_keyStore.addKeys(WTFMove(*decodedKeys));
 
-        LOG(EME, "EME - ClearKey - Update has provided %zu keys", keyVector.size());
+        LOG(EME, "EME - ClearKey - session %s has %u keys after update()", sessionId.utf8().data(), m_keyStore.numKeys());
 
         Optional<KeyStatusVector> changedKeys;
         if (keysChanged) {
-            // Sort by key IDs.
-            std::sort(keyVector.begin(), keyVector.end());
-
-            KeyStatusVector keyStatusVector;
-            keyStatusVector.reserveInitialCapacity(keyVector.size());
-            for (auto& key : keyVector)
-                keyStatusVector.uncheckedAppend(std::pair<Ref<SharedBuffer>, KeyStatus> { *key.keyIDData, key.status });
-
-            changedKeys = WTFMove(keyStatusVector);
+            LOG(EME, "EME - ClearKey - session %s has changed keys", sessionId.utf8().data());
+            m_parentInstance.mergeKeysFrom(m_keyStore);
+            changedKeys = m_keyStore.convertToJSKeyStatusVector();
         }
 
         dispatchCallback(false, WTFMove(changedKeys), SuccessValue::Succeeded);
@@ -657,47 +542,31 @@ void CDMInstanceSessionClearKey::updateLicense(const String& sessionId, LicenseT
     }
 
     if (parseLicenseReleaseAcknowledgementFormat(*root)) {
-        // FIXME: Retrieve the key ID information and use it to validate the keys for this sessionId.
-        ClearKeyState::singleton().keys().remove(sessionId);
+        LOG(EME, "EME - ClearKey - session %s release acknowledged, clearing all known keys", sessionId.utf8().data());
+        m_parentInstance.removeAllKeysFrom(m_keyStore);
+        m_keyStore.removeAllKeys();
         dispatchCallback(true, WTF::nullopt, SuccessValue::Succeeded);
         return;
     }
 
-    // Bail in case no format was recognized.
+    LOG(EME, "EME - ClearKey - session %s update payload was an unrecognized format", sessionId.utf8().data());
     dispatchCallback(false, WTF::nullopt, SuccessValue::Failed);
 }
 
 void CDMInstanceSessionClearKey::loadSession(LicenseType, const String& sessionId, const String&, LoadSessionCallback&& callback)
 {
-    // Use a helper functor that schedules the callback dispatch, avoiding duplicated callOnMainThread() calls.
-    auto dispatchCallback =
-        [this, &callback](Optional<KeyStatusVector>&& existingKeys, SuccessValue success, SessionLoadFailure loadFailure) {
-            callOnMainThread(
-                [weakThis = makeWeakPtr(*this), callback = WTFMove(callback), existingKeys = WTFMove(existingKeys), success, loadFailure]() mutable {
-                    if (!weakThis)
-                        return;
-
-                    callback(WTFMove(existingKeys), WTF::nullopt, WTF::nullopt, success, loadFailure);
-                });
-        };
+#ifdef NDEBUG
+    UNUSED_PARAM(sessionId);
+#endif
 
-    // Construct the KeyStatusVector object, representing all the known keys for this session.
-    KeyStatusVector keyStatusVector;
-    {
-        auto& keys = ClearKeyState::singleton().keys();
-        auto it = keys.find(sessionId);
-        if (it == keys.end()) {
-            dispatchCallback(WTF::nullopt, Failed, SessionLoadFailure::NoSessionData);
+    ASSERT(sessionId == m_sessionID);
+    KeyStatusVector keyStatusVector = m_keyStore.convertToJSKeyStatusVector();
+    callOnMainThread([weakThis = makeWeakPtr(*this), callback = WTFMove(callback), &keyStatusVector]() mutable {
+        if (!weakThis)
             return;
-        }
-
-        auto& keyVector = it->value;
-        keyStatusVector.reserveInitialCapacity(keyVector.size());
-        for (auto& key : keyVector)
-            keyStatusVector.uncheckedAppend(std::pair<Ref<SharedBuffer>, KeyStatus> { *key.keyIDData, key.status });
-    }
 
-    dispatchCallback(WTFMove(keyStatusVector), Succeeded, SessionLoadFailure::None);
+        callback(WTFMove(keyStatusVector), WTF::nullopt, WTF::nullopt, Succeeded, SessionLoadFailure::None);
+    });
 }
 
 void CDMInstanceSessionClearKey::closeSession(const String&, CloseSessionCallback&& callback)
@@ -713,7 +582,12 @@ void CDMInstanceSessionClearKey::closeSession(const String&, CloseSessionCallbac
 
 void CDMInstanceSessionClearKey::removeSessionData(const String& sessionId, LicenseType, RemoveSessionDataCallback&& callback)
 {
-    // Use a helper functor that schedules the callback dispatch, avoiding duplicated callOnMainThread() calls.
+#ifdef NDEBUG
+    UNUSED_PARAM(sessionId);
+#endif
+
+    ASSERT(sessionId == m_sessionID);
+
     auto dispatchCallback =
         [this, &callback](KeyStatusVector&& keyStatusVector, Optional<Ref<SharedBuffer>>&& message, SuccessValue success) {
             callOnMainThread(
@@ -727,35 +601,17 @@ void CDMInstanceSessionClearKey::removeSessionData(const String& sessionId, Lice
 
     // Construct the KeyStatusVector object, representing released keys, and the message in the
     // 'license release' format.
-    KeyStatusVector keyStatusVector;
+    KeyStatusVector keyStatusVector = m_keyStore.allKeysAsReleased();
     RefPtr<SharedBuffer> message;
     {
-        // Retrieve information for the given session ID, bailing if none is found.
-        auto& keys = ClearKeyState::singleton().keys();
-        auto it = keys.find(sessionId);
-        if (it == keys.end()) {
-            dispatchCallback(KeyStatusVector { }, WTF::nullopt, SuccessValue::Failed);
-            return;
-        }
-
-        // Retrieve the Key vector, containing all the keys for this session, and
-        // then remove the key map entry for this session.
-        auto keyVector = WTFMove(it->value);
-        keys.remove(it);
-
-        // Construct the KeyStatusVector object, pairing key IDs with the 'released' status.
-        keyStatusVector.reserveInitialCapacity(keyVector.size());
-        for (auto& key : keyVector)
-            keyStatusVector.uncheckedAppend(std::pair<Ref<SharedBuffer>, KeyStatus> { *key.keyIDData, KeyStatus::Released });
-
         // Construct JSON that represents the 'license release' format, creating a 'kids' array
         // of base64URL-encoded key IDs for all keys that were associated with this session.
         auto rootObject = JSON::Object::create();
         {
             auto array = JSON::Array::create();
-            for (auto& key : keyVector) {
-                ASSERT(key.keyIDData->size() <= std::numeric_limits<unsigned>::max());
-                array->pushString(WTF::base64URLEncode(key.keyIDData->data(), static_cast<unsigned>(key.keyIDData->size())));
+            for (const auto& key : m_keyStore) {
+                ASSERT(key->id().size() <= std::numeric_limits<unsigned>::max());
+                array->pushString(WTF::base64URLEncode(key->id().data(), key->id().size()));
             }
             rootObject->setArray("kids", WTFMove(array));
         }
@@ -766,6 +622,7 @@ void CDMInstanceSessionClearKey::removeSessionData(const String& sessionId, Lice
         message = SharedBuffer::create(messageCString.data(), messageCString.length());
     }
 
+    m_keyStore.removeAllKeys();
     dispatchCallback(WTFMove(keyStatusVector), Ref<SharedBuffer>(*message), SuccessValue::Succeeded);
 }
 
index 85b319b..6704ae7 100644 (file)
 #if ENABLE(ENCRYPTED_MEDIA)
 
 #include "CDMFactory.h"
-#include "CDMInstance.h"
 #include "CDMInstanceSession.h"
 #include "CDMPrivate.h"
+#include "CDMProxy.h"
 #include "SharedBuffer.h"
 #include <wtf/WeakPtr.h>
 
 namespace WebCore {
 
+namespace ClearKey {
+
+// ClearKey CENC SystemID.
+// https://www.w3.org/TR/eme-initdata-cenc/#common-system
+const uint8_t cencSystemId[] = { 0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b };
+const unsigned cencSystemIdSize = sizeof(cencSystemId);
+enum {
+    AES128CTRBlockSizeInBytes = 16,
+    KeyIDSizeInBytes = 16,
+    IVSizeInBytes = 16,
+};
+
+} // namespace ClearKey
+
 class CDMFactoryClearKey final : public CDMFactory {
     WTF_MAKE_FAST_ALLOCATED;
 public:
@@ -77,27 +91,12 @@ public:
     Optional<String> sanitizeSessionId(const String&) const final;
 };
 
-class ProxyCDMClearKey final : public ProxyCDM {
+class CDMInstanceClearKey final : public CDMInstanceProxy, public CanMakeWeakPtr<CDMInstanceClearKey> {
 public:
-    struct Key {
-        CDMInstanceSession::KeyStatus status;
-        String keyIDData;
-        String keyValueData;
-    };
-
-    virtual ~ProxyCDMClearKey() = default;
-    const Vector<Key> isolatedKeys() const;
-private:
-    mutable Lock m_keysMutex;
-};
-
-class CDMInstanceClearKey final : public CDMInstance, public CanMakeWeakPtr<CDMInstanceClearKey> {
-public:
-    CDMInstanceClearKey();
     virtual ~CDMInstanceClearKey();
 
+    // CDMInstance
     ImplementationType implementationType() const final { return ImplementationType::ClearKey; }
-
     SuccessValue initializeWithConfiguration(const CDMKeySystemConfiguration&) final;
     SuccessValue setDistinctiveIdentifiersAllowed(bool) final;
     SuccessValue setPersistentStateAllowed(bool) final;
@@ -105,47 +104,22 @@ public:
     SuccessValue setStorageDirectory(const String&) final;
     const String& keySystem() const final;
     RefPtr<CDMInstanceSession> createSession() final;
-
-    struct Key {
-        CDMInstanceSession::KeyStatus status;
-        RefPtr<SharedBuffer> keyIDData;
-        RefPtr<SharedBuffer> keyValueData;
-
-        String keyIDAsString() const;
-        String keyValueAsString() const;
-
-        bool hasSameKeyValue(const Key &other)
-        {
-            ASSERT(keyValueData);
-            ASSERT(other.keyValueData);
-            return *keyValueData == *other.keyValueData;
-        }
-
-        // Two keys are equal if they have the same ID, ignoring key value and status.
-        friend bool operator==(const Key &k1, const Key &k2);
-        // Key's are ordered by their IDs, first by size, then by contents.
-        friend bool operator<(const Key &k1, const Key &k2);
-
-        friend bool operator!=(const Key &k1, const Key &k2) { return !(operator==(k1, k2)); }
-        friend bool operator>(const Key &k1, const Key &k2) { return !operator==(k1, k2) && !operator<(k1, k2); }
-        friend bool operator<=(const Key &k1, const Key &k2) { return !operator>(k1, k2); }
-        friend bool operator>=(const Key &k1, const Key &k2) { return !operator<(k1, k2); }
-    };
-
-    RefPtr<ProxyCDM> proxyCDM() const final { return m_proxyCDM; }
-
-private:
-    RefPtr<ProxyCDM> m_proxyCDM;
 };
 
 class CDMInstanceSessionClearKey final : public CDMInstanceSession, public CanMakeWeakPtr<CDMInstanceSessionClearKey> {
 public:
+    CDMInstanceSessionClearKey(CDMInstanceClearKey& parent)
+        : m_parentInstance(parent) { }
     void requestLicense(LicenseType, const AtomString& initDataType, Ref<SharedBuffer>&& initData, LicenseCallback&&) final;
     void updateLicense(const String&, LicenseType, const SharedBuffer&, LicenseUpdateCallback&&) final;
     void loadSession(LicenseType, const String&, const String&, LoadSessionCallback&&) final;
     void closeSession(const String&, CloseSessionCallback&&) final;
     void removeSessionData(const String&, LicenseType, RemoveSessionDataCallback&&) final;
     void storeRecordOfKeyUsage(const String&) final;
+private:
+    String m_sessionID;
+    CDMInstanceClearKey& m_parentInstance;
+    KeyStore m_keyStore;
 };
 
 } // namespace WebCore
index 4772fcb..b11f71a 100644 (file)
@@ -80,7 +80,6 @@ public:
     SuccessValue setServerCertificate(Ref<SharedBuffer>&&) final;
     SuccessValue setStorageDirectory(const String&) final;
     RefPtr<CDMInstanceSession> createSession() final;
-    RefPtr<ProxyCDM> proxyCDM() const final { return nullptr; }
 
     const String& keySystem() const final;
 
index 6526a18..989f1f2 100644 (file)
@@ -65,6 +65,7 @@
 
 #if ENABLE(ENCRYPTED_MEDIA)
 #include "CDMInstance.h"
+#include "CDMProxyClearKey.h"
 #include "GStreamerEMEUtilities.h"
 #include "SharedBuffer.h"
 #include "WebKitCommonEncryptionDecryptorGStreamer.h"
@@ -1788,6 +1789,50 @@ void MediaPlayerPrivateGStreamer::setPipeline(GstElement* pipeline)
     }, this, nullptr);
 }
 
+InitData MediaPlayerPrivateGStreamer::parseInitDataFromProtectionMessage(GstMessage* message)
+{
+    ASSERT(!isMainThread());
+
+    InitData initData;
+    {
+        LockHolder lock(m_protectionMutex);
+        ProtectionSystemEvents protectionSystemEvents(message);
+        GST_TRACE_OBJECT(pipeline(), "found %zu protection events, %zu decryptors available", protectionSystemEvents.events().size(), protectionSystemEvents.availableSystems().size());
+
+        for (auto& event : protectionSystemEvents.events()) {
+            const char* eventKeySystemId = nullptr;
+            GstBuffer* data = nullptr;
+            gst_event_parse_protection(event.get(), &eventKeySystemId, &data, nullptr);
+
+            initData.append({eventKeySystemId, data});
+            m_handledProtectionEvents.add(GST_EVENT_SEQNUM(event.get()));
+        }
+    }
+
+    return initData;
+}
+
+bool MediaPlayerPrivateGStreamer::waitForCDMAttachment()
+{
+    if (isMainThread()) {
+        GST_ERROR_OBJECT(pipeline(), "can't block the main thread waiting for a CDM instance");
+        ASSERT_NOT_REACHED();
+        return false;
+    }
+
+    GST_INFO_OBJECT(pipeline(), "waiting for a CDM instance");
+
+    bool didCDMAttach = false;
+    {
+        auto cdmAttachmentLocker = holdLock(m_cdmAttachmentMutex);
+        didCDMAttach = m_cdmAttachmentCondition.waitFor(m_cdmAttachmentMutex, 4_s, [this]() {
+            return isCDMAttached();
+        });
+    }
+
+    return didCDMAttach;
+}
+
 bool MediaPlayerPrivateGStreamer::handleSyncMessage(GstMessage* message)
 {
     if (GST_MESSAGE_TYPE(message) == GST_MESSAGE_STREAM_COLLECTION && !m_isLegacyPlaybin) {
@@ -1823,41 +1868,9 @@ bool MediaPlayerPrivateGStreamer::handleSyncMessage(GstMessage* message)
 
 #if ENABLE(ENCRYPTED_MEDIA)
     if (!g_strcmp0(contextType, "drm-preferred-decryption-system-id")) {
-        if (isMainThread()) {
-            GST_ERROR("can't handle drm-preferred-decryption-system-id need context message in the main thread");
-            ASSERT_NOT_REACHED();
-            return false;
-        }
-        GST_DEBUG_OBJECT(pipeline(), "handling drm-preferred-decryption-system-id need context message");
-
-        InitData initData;
-        {
-            LockHolder lock(m_protectionMutex);
-            ProtectionSystemEvents protectionSystemEvents(message);
-            GST_TRACE("found %zu protection events, %zu decryptors available", protectionSystemEvents.events().size(), protectionSystemEvents.availableSystems().size());
-
-            for (auto& event : protectionSystemEvents.events()) {
-                const char* eventKeySystemId = nullptr;
-                GstBuffer* data = nullptr;
-                gst_event_parse_protection(event.get(), &eventKeySystemId, &data, nullptr);
-
-                initData.append({eventKeySystemId, data});
-                m_handledProtectionEvents.add(GST_EVENT_SEQNUM(event.get()));
-            }
-        }
-        initializationDataEncountered(WTFMove(initData));
-
-        GST_INFO_OBJECT(pipeline(), "waiting for a CDM instance");
-
-        bool didCDMAttach = false;
-        {
-            auto cdmAttachmentLocker = holdLock(m_cdmAttachmentMutex);
-            didCDMAttach = m_cdmAttachmentCondition.waitFor(m_cdmAttachmentMutex, 4_s, [this]() {
-                return isCDMAttached();
-            });
-        }
-
-        if (didCDMAttach && !isPlayerShuttingDown() && !m_cdmInstance->keySystem().isEmpty()) {
+        initializationDataEncountered(parseInitDataFromProtectionMessage(message));
+        bool isCDMAttached = waitForCDMAttachment();
+        if (isCDMAttached && !isPlayerShuttingDown() && !m_cdmInstance->keySystem().isEmpty()) {
             const char* preferredKeySystemUuid = GStreamerEMEUtilities::keySystemToUuid(m_cdmInstance->keySystem());
             GST_INFO_OBJECT(pipeline(), "working with key system %s, continuing with key system %s on %s", m_cdmInstance->keySystem().utf8().data(), preferredKeySystemUuid, GST_MESSAGE_SRC_NAME(message));
 
@@ -1865,10 +1878,11 @@ bool MediaPlayerPrivateGStreamer::handleSyncMessage(GstMessage* message)
             GstStructure* contextStructure = gst_context_writable_structure(context.get());
             gst_structure_set(contextStructure, "decryption-system-id", G_TYPE_STRING, preferredKeySystemUuid, nullptr);
             gst_element_set_context(GST_ELEMENT(GST_MESSAGE_SRC(message)), context.get());
-        } else
-            GST_WARNING("CDM instance not initialized");
+            return true;
+        }
 
-        return true;
+        GST_WARNING_OBJECT(pipeline(), "waiting for a CDM failed, no CDM available");
+        return false;
     }
 #endif // ENABLE(ENCRYPTED_MEDIA)
 
@@ -2199,19 +2213,6 @@ void MediaPlayerPrivateGStreamer::handleMessage(GstMessage* message)
             gst_mpegts_section_unref(section);
         }
 #endif
-#if ENABLE(ENCRYPTED_MEDIA)
-        else if (gst_structure_has_name(structure, "drm-waiting-for-key")) {
-            GST_DEBUG_OBJECT(pipeline(), "drm-waiting-for-key message from %s", GST_MESSAGE_SRC_NAME(message));
-            setWaitingForKey(true);
-            // FIXME: The decryptors should be able to attempt to decrypt after being created and linked in a pipeline but currently they are not and current
-            // architecture does not make this very easy. Fortunately, the arch will change soon and it does not pay off to fix this now with something that could be
-            // more convoluted. In the meantime, force attempt to decrypt when they get blocked.
-            attemptToDecryptWithLocalInstance();
-        } else if (gst_structure_has_name(structure, "drm-key-received")) {
-            GST_DEBUG_OBJECT(pipeline(), "drm-key-received message from %s", GST_MESSAGE_SRC_NAME(message));
-            setWaitingForKey(false);
-        }
-#endif
         else if (gst_structure_has_name(structure, "http-headers")) {
             GST_DEBUG_OBJECT(pipeline(), "Processing HTTP headers: %" GST_PTR_FORMAT, structure);
             if (const char* uri = gst_structure_get_string(structure, "uri")) {
@@ -3728,14 +3729,17 @@ void MediaPlayerPrivateGStreamer::cdmInstanceAttached(CDMInstance& instance)
         return;
     }
 
-    m_cdmInstance = &instance;
+    m_cdmInstance = reinterpret_cast<CDMInstanceProxy*>(&instance);
+    RELEASE_ASSERT(m_cdmInstance);
+    m_cdmInstance->setPlayer(m_player);
+    m_cdmInstance->setProxy(adoptRef(*new CDMProxyClearKey));
 
-    GRefPtr<GstContext> context = adoptGRef(gst_context_new("drm-cdm-instance", FALSE));
+    GRefPtr<GstContext> context = adoptGRef(gst_context_new("drm-cdm-proxy", FALSE));
     GstStructure* contextStructure = gst_context_writable_structure(context.get());
-    gst_structure_set(contextStructure, "cdm-instance", G_TYPE_POINTER, m_cdmInstance->proxyCDM().get(), nullptr);
+    gst_structure_set(contextStructure, "cdm-proxy", G_TYPE_POINTER, m_cdmInstance->proxy().get(), nullptr);
     gst_element_set_context(GST_ELEMENT(m_pipeline.get()), context.get());
 
-    GST_DEBUG_OBJECT(m_pipeline.get(), "CDM proxy instance %p dispatched as context", m_cdmInstance->proxyCDM().get());
+    GST_DEBUG_OBJECT(m_pipeline.get(), "CDM proxy instance %p dispatched as context", m_cdmInstance->proxy().get());
 
     LockHolder lock(m_cdmAttachmentMutex);
     // We must notify all waiters, since several demuxers can be simultaneously waiting for a CDM.
@@ -3757,7 +3761,7 @@ void MediaPlayerPrivateGStreamer::cdmInstanceDetached(CDMInstance& instance)
     GST_DEBUG_OBJECT(m_pipeline.get(), "detaching CDM instance %p, setting empty context", m_cdmInstance.get());
     m_cdmInstance = nullptr;
 
-    GRefPtr<GstContext> context = adoptGRef(gst_context_new("drm-cdm-instance", FALSE));
+    GRefPtr<GstContext> context = adoptGRef(gst_context_new("drm-cdm-proxy", FALSE));
     gst_element_set_context(GST_ELEMENT(m_pipeline.get()), context.get());
 }
 
@@ -3790,51 +3794,12 @@ void MediaPlayerPrivateGStreamer::handleProtectionEvent(GstEvent* event)
     initializationDataEncountered({eventKeySystemUUID, initData});
 }
 
-void MediaPlayerPrivateGStreamer::setWaitingForKey(bool isWaitingForKey)
-{
-    // We bail out if values did not change or if we are requested to not wait anymore but there are still waiting decryptors.
-    GST_TRACE("isWaitingForKey %s, m_isWaitingForKey %s", boolForPrinting(isWaitingForKey), boolForPrinting(m_isWaitingForKey));
-    if (isWaitingForKey == m_isWaitingForKey || (!isWaitingForKey && this->waitingForKey()))
-        return;
-
-    m_isWaitingForKey = isWaitingForKey;
-    GST_DEBUG("waiting for key changed %s", boolForPrinting(m_isWaitingForKey));
-    m_player->waitingForKeyChanged();
-}
-
 bool MediaPlayerPrivateGStreamer::waitingForKey() const
 {
-    if (!m_pipeline)
+    if (!m_pipeline || !m_cdmInstance)
         return false;
 
-    GstState state;
-    gst_element_get_state(m_pipeline.get(), &state, nullptr, 0);
-
-    bool result = false;
-    GRefPtr<GstQuery> query = adoptGRef(gst_query_new_custom(GST_QUERY_CUSTOM, gst_structure_new_empty("any-decryptor-waiting-for-key")));
-    if (state >= GST_STATE_PAUSED) {
-        result = gst_element_query(m_pipeline.get(), query.get());
-        GST_TRACE("query result %s, on %s", boolForPrinting(result), gst_element_state_get_name(state));
-    } else if (state >= GST_STATE_READY) {
-        // Running a query in the pipeline is easier but it only works when the pipeline is set up and running, otherwise we need to inspect it and ask the decryptors directly.
-        GUniquePtr<GstIterator> iterator(gst_bin_iterate_recurse(GST_BIN(m_pipeline.get())));
-        GstIteratorResult iteratorResult;
-        do {
-            iteratorResult = gst_iterator_fold(iterator.get(), [](const GValue *item, GValue *, gpointer data) -> gboolean {
-                GstElement* element = GST_ELEMENT(g_value_get_object(item));
-                GstQuery* query = GST_QUERY(data);
-                return !WEBKIT_IS_MEDIA_CENC_DECRYPT(element) || !gst_element_query(element, query);
-            }, nullptr, query.get());
-            if (iteratorResult == GST_ITERATOR_RESYNC)
-                gst_iterator_resync(iterator.get());
-        } while (iteratorResult == GST_ITERATOR_RESYNC);
-        if (iteratorResult == GST_ITERATOR_ERROR)
-            GST_WARNING("iterator returned an error");
-        result = iteratorResult == GST_ITERATOR_OK;
-        GST_TRACE("iterator result %d, waiting %s", iteratorResult, boolForPrinting(result));
-    }
-
-    return result;
+    return m_cdmInstance->isWaitingForKey();
 }
 #endif
 
index b2bb541..521b20d 100644 (file)
@@ -76,6 +76,10 @@ typedef struct _GstMpegtsSection GstMpegtsSection;
 #endif
 #endif
 
+#if ENABLE(ENCRYPTED_MEDIA)
+#include "CDMProxy.h"
+#endif
+
 typedef struct _GstStreamVolume GstStreamVolume;
 typedef struct _GstVideoInfo GstVideoInfo;
 typedef struct _GstGLContext GstGLContext;
@@ -371,7 +375,7 @@ protected:
 #if ENABLE(ENCRYPTED_MEDIA)
     Lock m_cdmAttachmentMutex;
     Condition m_cdmAttachmentCondition;
-    RefPtr<const CDMInstance> m_cdmInstance;
+    RefPtr<CDMInstanceProxy> m_cdmInstance;
 
     Lock m_protectionMutex; // Guards access to m_handledProtectionEvents.
     HashSet<uint32_t> m_handledProtectionEvents;
@@ -450,7 +454,8 @@ private:
     bool isCDMAttached() const { return m_cdmInstance; }
     void attemptToDecryptWithLocalInstance();
     void initializationDataEncountered(InitData&&);
-    void setWaitingForKey(bool);
+    InitData parseInitDataFromProtectionMessage(GstMessage*);
+    bool waitForCDMAttachment();
 #endif
 
     Atomic<bool> m_isPlayerShuttingDown;
diff --git a/Source/WebCore/platform/graphics/gstreamer/eme/CDMProxyClearKey.cpp b/Source/WebCore/platform/graphics/gstreamer/eme/CDMProxyClearKey.cpp
new file mode 100644 (file)
index 0000000..8040452
--- /dev/null
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2020 Metrological Group B.V.
+ * Copyright (C) 2020 Igalia S.L.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials provided
+ *    with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "CDMProxyClearKey.h"
+
+#if ENABLE(ENCRYPTED_MEDIA)
+
+#include "Logging.h"
+#include <wtf/ByteOrder.h>
+
+namespace WebCore {
+
+namespace {
+
+static bool readUInt32(const uint8_t* buffer, size_t bufferSize, size_t& offset, uint32_t& value)
+{
+    ASSERT_ARG(offset, offset <= bufferSize);
+    if (bufferSize - offset < sizeof(value))
+        return false;
+
+    value = ntohl(*reinterpret_cast_ptr<const uint32_t*>(buffer + offset));
+    offset += sizeof(value);
+
+    return true;
+}
+
+static bool readUInt16(const uint8_t* buffer, size_t bufferSize, size_t& offset, uint16_t& value)
+{
+    ASSERT_ARG(offset, offset <= bufferSize);
+    if (bufferSize - offset < sizeof(value))
+        return false;
+
+    value = ntohs(*reinterpret_cast_ptr<const uint16_t*>(buffer + offset));
+    offset += sizeof(value);
+
+    return true;
+}
+
+} // namespace {}
+
+CDMProxyClearKey::~CDMProxyClearKey()
+{
+    gcry_cipher_close(m_gcryHandle);
+}
+
+bool CDMProxyClearKey::cencSetCounterVector(const cencDecryptContext& input)
+{
+    uint8_t ctr[ClearKey::AES128CTRBlockSizeInBytes];
+    if (input.ivSizeInBytes == 8) {
+        // ISO/IEC 23001-7:2016 Section 9.3
+
+        // When an 8-byte IV is indicated, the least significant 8
+        // bytes of the 16 byte IV (bytes 8 to 15) SHALL be set to
+        // zero.
+        memset(ctr + 8, 0, 8);
+        memcpy(ctr, input.iv, 8);
+    } else
+        memcpy(ctr, input.iv, ClearKey::IVSizeInBytes);
+
+    if (gcry_error_t cipherError = gcry_cipher_setctr(m_gcryHandle, ctr, ClearKey::IVSizeInBytes)) {
+        LOG(EME, "EME - CDMProxyClearKey - ERROR(gcry_cipher_setctr): %s", gpg_strerror(cipherError));
+        return false;
+    }
+
+    return true;
+}
+
+bool CDMProxyClearKey::cencSetDecryptionKey(const cencDecryptContext& in)
+{
+    // FIXME: Unnecessary copy, can we avoid this while still exposing
+    // a non-GStreamer-specific DecryptInput API? These buffers are
+    // small (16 bytes), so not a huge deal, I guess.
+    Vector<uint8_t> keyIDVec;
+    keyIDVec.append(in.keyID, in.keyIDSizeInBytes);
+
+    auto keyData = getOrWaitForKey(keyIDVec);
+    if (!keyData)
+        return false;
+
+    if (gcry_error_t error = gcry_cipher_setkey(m_gcryHandle, keyData->data(), keyData->size())) {
+        LOG(EME, "EME - CDMProxyClearKey - ERROR(gcry_cipher_setkey): %s", gpg_strerror(error));
+        return false;
+    }
+
+    return true;
+}
+
+bool CDMProxyClearKey::cencDecryptFullSample(cencDecryptContext& in)
+{
+    if (!in.encryptedBufferSizeInBytes)
+        return true;
+
+    LOG(EME, "EME - CDMProxyClearKey - full-sample decryption: %zu encrypted bytes", in.encryptedBufferSizeInBytes);
+
+    if (gcry_error_t cipherError = gcry_cipher_decrypt(m_gcryHandle, in.encryptedBuffer, in.encryptedBufferSizeInBytes, 0, 0)) {
+        LOG(EME, "EME - CDMProxyClearKey - ERROR(gcry_cipher_decrypt): %s", gpg_strerror(cipherError));
+        return false;
+    }
+
+    return true;
+}
+
+bool CDMProxyClearKey::cencDecryptSubsampled(cencDecryptContext& input)
+{
+    unsigned encryptedBufferByteOffset = 0;
+    size_t subSampleBufferByteOffset = 0;
+    unsigned subsampleIndex = 0;
+    while (encryptedBufferByteOffset < input.encryptedBufferSizeInBytes) {
+        uint16_t subsampleNumClearBytes = 0;
+        uint32_t subsampleNumEncryptedBytes = 0;
+
+        if (subsampleIndex < input.numSubsamples) {
+            if (!readUInt16(input.subsamplesBuffer, input.subsamplesBufferSizeInBytes, subSampleBufferByteOffset, subsampleNumClearBytes)) {
+                LOG(EME, "EME - CDMProxyClearKey - could not read number of clear bytes in subsample at index %u", subsampleIndex);
+                return false;
+            }
+            if (!readUInt32(input.subsamplesBuffer, input.subsamplesBufferSizeInBytes, subSampleBufferByteOffset, subsampleNumEncryptedBytes)) {
+                LOG(EME, "EME - CDMProxyClearKey - could not read number of encrypted bytes in subsample at index %u", subsampleIndex);
+                return false;
+            }
+            subsampleIndex++;
+        } else {
+            subsampleNumClearBytes = 0;
+            subsampleNumEncryptedBytes = input.encryptedBufferSizeInBytes - encryptedBufferByteOffset;
+        }
+
+        // FIXME: These are high-frequency messages, not sure if there's a better logging lib in WebCore.
+        LOG(EME, "EME - subsample index %u - %u bytes clear (%lu bytes left to decrypt)", subsampleIndex, subsampleNumClearBytes, input.encryptedBufferSizeInBytes - encryptedBufferByteOffset);
+
+        encryptedBufferByteOffset += subsampleNumClearBytes;
+
+        if (subsampleNumEncryptedBytes) {
+            LOG(EME, "EME - subsample index %u - %u bytes encrypted (%lu bytes left to decrypt)", subsampleIndex, subsampleNumEncryptedBytes, input.encryptedBufferSizeInBytes - encryptedBufferByteOffset);
+
+            if (gcry_error_t cipherError = gcry_cipher_decrypt(m_gcryHandle, input.encryptedBuffer + encryptedBufferByteOffset, subsampleNumEncryptedBytes, 0, 0)) {
+                LOG(EME, "EME - CDMProxyClearKey - ERROR(gcry_cipher_decrypt): %s", gpg_strerror(cipherError));
+                return false;
+            }
+
+            encryptedBufferByteOffset += subsampleNumEncryptedBytes;
+        }
+    }
+
+    return true;
+}
+
+bool CDMProxyClearKey::cencDecrypt(CDMProxyClearKey::cencDecryptContext& input)
+{
+    if (!cencSetCounterVector(input) || !cencSetDecryptionKey(input))
+        return false;
+
+    return input.isSubsampled() ? cencDecryptSubsampled(input) : cencDecryptFullSample(input);
+}
+
+void CDMProxyClearKey::initializeGcrypt()
+{
+    if (gcry_error_t error = gcry_cipher_open(&m_gcryHandle, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_CTR, GCRY_CIPHER_SECURE)) {
+        LOG(EME, "EME - CDMProxyClearKey - ERROR(gcry_cipher_open): %s", gpg_strerror(error));
+        RELEASE_ASSERT(false && "Should not get this far with a functional GCrypt!");
+    }
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(ENCRYPTED_MEDIA)
diff --git a/Source/WebCore/platform/graphics/gstreamer/eme/CDMProxyClearKey.h b/Source/WebCore/platform/graphics/gstreamer/eme/CDMProxyClearKey.h
new file mode 100644 (file)
index 0000000..50240c9
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * 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)
+
+#include "CDMClearKey.h"
+#include "CDMInstanceSession.h"
+#include "MediaPlayerPrivate.h"
+#include "SharedBuffer.h"
+#include <gcrypt.h>
+#include <wtf/Condition.h>
+#include <wtf/VectorHash.h>
+
+namespace WebCore {
+
+// This is the thread-safe API that decode threads should use to make use of a
+// platform CDM module.
+class CDMProxyClearKey final : public CDMProxy, public CanMakeWeakPtr<CDMProxyClearKey, WeakPtrFactoryInitialization::Eager> {
+public:
+    CDMProxyClearKey()
+    {
+        initializeGcrypt();
+    }
+    virtual ~CDMProxyClearKey();
+
+    // FIXME: There's a lack of consistency between SharedBuffers,
+    // Vector<char>'s and {uint8_t*,size_t} idioms for representing a chunk of
+    // bytes. Fix that somehow. Note SharedBuffers are dangerous since
+    // they're not thread-safe and this class must maintain
+    // thread-safety, but maybe SharedBuffer::DataSegment could be
+    // made to work, however that has zero helpers for dropping into
+    // iterator-aware / comparator-aware containers...
+    struct cencDecryptContext {
+        // FIXME: Enacapsulate these fields in non-copied types.
+        const uint8_t* keyID;
+        size_t keyIDSizeInBytes;
+
+        const uint8_t* iv;
+        size_t ivSizeInBytes;
+
+        uint8_t* encryptedBuffer;
+        size_t encryptedBufferSizeInBytes;
+
+        // FIXME: GStreamer-specific data layout.
+        // If we want to get rid of the specific data layout, we can parse it before
+        // assigning it here and maybe have a Vector<size_t> that is a series of
+        // clear/decrypted/clear/decrypted... Or maybe even a Vector<std::pair<size_t, size_t>>
+        // representing sequences of clear/decrypted sizes. That std::pair could even
+        // become a struct ClearDecryptedChunkSizesInBytes for example.
+        // https://bugs.webkit.org/show_bug.cgi?id=206730
+        const uint8_t* subsamplesBuffer;
+        size_t subsamplesBufferSizeInBytes;
+        size_t numSubsamples;
+
+        bool isSubsampled() const { return numSubsamples; }
+    };
+
+    // FIXME: Unconditional in-place decryption. What about SVP?
+    // FIXME: GStreamer-specific, in that the format of the subsample
+    // data is defined by whatever qtdemux decides to do with it.
+    bool cencDecrypt(cencDecryptContext&);
+
+private:
+    void initializeGcrypt();
+
+    // FIXME: For now we only support AES in CTR mode, in the future
+    // we will surely have to support more protection schemes. Can we
+    // reuse some Crypto APIs in WebCore?
+    bool cencSetCounterVector(const cencDecryptContext&);
+    bool cencSetDecryptionKey(const cencDecryptContext&);
+    bool cencDecryptFullSample(cencDecryptContext&);
+    bool cencDecryptSubsampled(cencDecryptContext&);
+
+    // FIXME: It would be nice to use something in WebCore for crypto...
+    gcry_cipher_hd_t m_gcryHandle;
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(ENCRYPTED_MEDIA)
index d8c0254..583f261 100644 (file)
@@ -25,6 +25,7 @@
 #if ENABLE(ENCRYPTED_MEDIA) && USE(GSTREAMER)
 
 #include "CDMClearKey.h"
+#include "CDMProxyClearKey.h"
 #include "GStreamerCommon.h"
 #include "GStreamerEMEUtilities.h"
 #include <gcrypt.h>
@@ -37,12 +38,11 @@ using namespace WebCore;
 
 #define WEBKIT_MEDIA_CK_DECRYPT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), WEBKIT_TYPE_MEDIA_CK_DECRYPT, WebKitMediaClearKeyDecryptPrivate))
 struct _WebKitMediaClearKeyDecryptPrivate {
-    RefPtr<ProxyCDMClearKey> proxyCDM;
-    gcry_cipher_hd_t handle;
+    RefPtr<CDMProxyClearKey> cdmProxy;
 };
 
 static void finalize(GObject*);
-static bool handleKeyResponse(WebKitMediaCommonEncryptionDecrypt* self, RefPtr<ProxyCDM>);
+static bool cdmProxyAttached(WebKitMediaCommonEncryptionDecrypt* self, RefPtr<CDMProxy>);
 static bool decrypt(WebKitMediaCommonEncryptionDecrypt*, GstBuffer* iv, GstBuffer* keyid, GstBuffer* sample, unsigned subSamplesCount, GstBuffer* subSamples);
 
 GST_DEBUG_CATEGORY_STATIC(webkit_media_clear_key_decrypt_debug_category);
@@ -84,7 +84,7 @@ static void webkit_media_clear_key_decrypt_class_init(WebKitMediaClearKeyDecrypt
 
     WebKitMediaCommonEncryptionDecryptClass* cencClass = WEBKIT_MEDIA_CENC_DECRYPT_CLASS(klass);
     cencClass->protectionSystemId = GStreamerEMEUtilities::s_ClearKeyUUID;
-    cencClass->handleKeyResponse = GST_DEBUG_FUNCPTR(handleKeyResponse);
+    cencClass->cdmProxyAttached = GST_DEBUG_FUNCPTR(cdmProxyAttached);
     cencClass->decrypt = GST_DEBUG_FUNCPTR(decrypt);
 
     g_type_class_add_private(klass, sizeof(WebKitMediaClearKeyDecryptPrivate));
@@ -93,61 +93,32 @@ static void webkit_media_clear_key_decrypt_class_init(WebKitMediaClearKeyDecrypt
 static void webkit_media_clear_key_decrypt_init(WebKitMediaClearKeyDecrypt* self)
 {
     WebKitMediaClearKeyDecryptPrivate* priv = WEBKIT_MEDIA_CK_DECRYPT_GET_PRIVATE(self);
-
     self->priv = priv;
     new (priv) WebKitMediaClearKeyDecryptPrivate();
-    if (gcry_error_t error = gcry_cipher_open(&(priv->handle), GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_CTR, GCRY_CIPHER_SECURE)) {
-        GST_ERROR_OBJECT(self, "Failed to create AES 128 CTR cipher handle: %s", gpg_strerror(error));
-        ASSERT(!error);
-    }
 }
 
 static void finalize(GObject* object)
 {
     WebKitMediaClearKeyDecrypt* self = WEBKIT_MEDIA_CK_DECRYPT(object);
     WebKitMediaClearKeyDecryptPrivate* priv = self->priv;
-    gcry_cipher_close(priv->handle);
     priv->~WebKitMediaClearKeyDecryptPrivate();
 
     GST_CALL_PARENT(G_OBJECT_CLASS, finalize, (object));
 }
 
-static bool handleKeyResponse(WebKitMediaCommonEncryptionDecrypt* self, RefPtr<ProxyCDM> proxyCDM)
+static bool cdmProxyAttached(WebKitMediaCommonEncryptionDecrypt* self, RefPtr<CDMProxy> cdmProxy)
 {
     WebKitMediaClearKeyDecryptPrivate* priv = WEBKIT_MEDIA_CK_DECRYPT_GET_PRIVATE(WEBKIT_MEDIA_CK_DECRYPT(self));
-    priv->proxyCDM = reinterpret_cast<ProxyCDMClearKey*>(proxyCDM.get());
-    return priv->proxyCDM;
+    priv->cdmProxy = reinterpret_cast<CDMProxyClearKey*>(cdmProxy.get());
+    return priv->cdmProxy;
 }
 
-static bool findAndSetKey(WebKitMediaClearKeyDecryptPrivate* priv, const String&& keyID)
+static bool decrypt(WebKitMediaCommonEncryptionDecrypt* self, GstBuffer* ivBuffer, GstBuffer* keyIDBuffer, GstBuffer* buffer, unsigned subsampleCount, GstBuffer* subsamplesBuffer)
 {
-    String keyValue;
-
-    for (const auto& key : priv->proxyCDM->isolatedKeys()) {
-        if (key.keyIDData == keyID) {
-            keyValue = key.keyValueData;
-            break;
-        }
-    }
-
-    if (keyValue.isEmpty()) {
-        GST_ERROR_OBJECT(priv, "failed to find a decryption key");
-        return false;
-    }
-
-    ASSERT(keyValue.sizeInBytes() == CLEARKEY_SIZE);
-    if (gcry_error_t error = gcry_cipher_setkey(priv->handle, keyValue.characters8(), keyValue.sizeInBytes())) {
-        GST_ERROR_OBJECT(priv, "gcry_cipher_setkey failed: %s", gpg_strerror(error));
-        return false;
-    }
-
-    return true;
-}
+    WebKitMediaClearKeyDecryptPrivate* priv = WEBKIT_MEDIA_CK_DECRYPT_GET_PRIVATE(WEBKIT_MEDIA_CK_DECRYPT(self));
 
-static bool decrypt(WebKitMediaCommonEncryptionDecrypt* self, GstBuffer* ivBuffer, GstBuffer* keyIDBuffer, GstBuffer* buffer, unsigned subSampleCount, GstBuffer* subSamplesBuffer)
-{
-    if (!ivBuffer) {
-        GST_ERROR_OBJECT(self, "no IV buffer");
+    if (!ivBuffer || !keyIDBuffer || !buffer) {
+        GST_ERROR_OBJECT(self, "invalid decrypt() parameter");
         return false;
     }
 
@@ -157,27 +128,6 @@ static bool decrypt(WebKitMediaCommonEncryptionDecrypt* self, GstBuffer* ivBuffe
         return false;
     }
 
-    uint8_t ctr[CLEARKEY_SIZE];
-    if (mappedIVBuffer->size() == 8) {
-        memset(ctr + 8, 0, 8);
-        memcpy(ctr, mappedIVBuffer->data(), 8);
-    } else {
-        ASSERT(mappedIVBuffer->size() == CLEARKEY_SIZE);
-        memcpy(ctr, mappedIVBuffer->data(), CLEARKEY_SIZE);
-    }
-
-    WebKitMediaClearKeyDecryptPrivate* priv = WEBKIT_MEDIA_CK_DECRYPT_GET_PRIVATE(WEBKIT_MEDIA_CK_DECRYPT(self));
-    gcry_error_t cipherError = gcry_cipher_setctr(priv->handle, ctr, CLEARKEY_SIZE);
-    if (cipherError) {
-        GST_ERROR_OBJECT(self, "gcry_cipher_setctr failed: %s", gpg_strerror(cipherError));
-        return false;
-    }
-
-    if (!buffer) {
-        GST_ERROR_OBJECT(self, "No buffer to decrypt");
-        return false;
-    }
-
     auto mappedKeyIdBuffer = WebCore::GstMappedBuffer::create(keyIDBuffer, GST_MAP_READ);
     if (!mappedKeyIdBuffer) {
         GST_ERROR_OBJECT(self, "Failed to map key id buffer");
@@ -190,72 +140,29 @@ static bool decrypt(WebKitMediaCommonEncryptionDecrypt* self, GstBuffer* ivBuffe
         return false;
     }
 
-    findAndSetKey(priv, String(mappedKeyIdBuffer->data(), mappedKeyIdBuffer->size()));
-
-    unsigned position = 0;
-    unsigned sampleIndex = 0;
-
-    if (!subSampleCount) {
-        // Full sample encryption.
-        GST_TRACE_OBJECT(self, "full sample encryption: %zu encrypted bytes", mappedBuffer->size());
-
-        // Check if the buffer is empty.
-        if (mappedBuffer->size()) {
-            cipherError = gcry_cipher_decrypt(priv->handle, mappedBuffer->data(), mappedBuffer->size(), 0, 0);
-            if (cipherError) {
-                GST_ERROR_OBJECT(self, "full sample decryption failed: %s", gpg_strerror(cipherError));
-                return false;
-            }
-        }
-        return true;
-    }
-
-    // Check subSamplesBuffer isn't null.
-    if (!subSamplesBuffer) {
-        GST_ERROR_OBJECT(self, "Error, the subSampleBuffer is null");
-        return false;
-    }
-
-    // Subsample encryption.
-    auto mappedSubSamplesBuffer = WebCore::GstMappedBuffer::create(subSamplesBuffer, GST_MAP_READ);
-    if (!mappedSubSamplesBuffer) {
-        GST_ERROR_OBJECT(self, "Failed to map subsample buffer");
-        return false;
-    }
-
-    GUniquePtr<GstByteReader> reader(gst_byte_reader_new(mappedSubSamplesBuffer->data(), mappedSubSamplesBuffer->size()));
-    GST_DEBUG_OBJECT(self, "position: %d, size: %zu", position, mappedBuffer->size());
-
-    while (position < mappedBuffer->size()) {
-        guint16 nBytesClear = 0;
-        guint32 nBytesEncrypted = 0;
-
-        if (sampleIndex < subSampleCount) {
-            if (!gst_byte_reader_get_uint16_be(reader.get(), &nBytesClear)
-                || !gst_byte_reader_get_uint32_be(reader.get(), &nBytesEncrypted)) {
-                GST_DEBUG_OBJECT(self, "unsupported");
-                return false;
-            }
-            sampleIndex++;
-        } else {
-            nBytesClear = 0;
-            nBytesEncrypted = mappedBuffer->size() - position;
-        }
-
-        GST_TRACE_OBJECT(self, "subsample index %u - %hu bytes clear (todo=%zu)", sampleIndex, nBytesClear, mappedBuffer->size() - position);
-        position += nBytesClear;
-        if (nBytesEncrypted) {
-            GST_TRACE_OBJECT(self, "subsample index %u - %u bytes encrypted (todo=%zu)", sampleIndex, nBytesEncrypted, mappedBuffer->size() - position);
-            cipherError = gcry_cipher_decrypt(priv->handle, mappedBuffer->data() + position, nBytesEncrypted, 0, 0);
-            if (cipherError) {
-                GST_ERROR_OBJECT(self, "sub sample index %u decryption failed: %s", sampleIndex, gpg_strerror(cipherError));
-                return false;
-            }
-            position += nBytesEncrypted;
+    RefPtr<GstMappedBuffer> mappedSubsamplesBuffer;
+    CDMProxyClearKey::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)
+        context.subsamplesBuffer = nullptr;
+    else {
+        ASSERT(subsamplesBuffer);
+        mappedSubsamplesBuffer = WebCore::GstMappedBuffer::create(subsamplesBuffer, GST_MAP_READ);
+        if (!mappedSubsamplesBuffer) {
+            GST_ERROR_OBJECT(self, "Failed to map subsample buffer");
+            return false;
         }
+        context.subsamplesBuffer = mappedSubsamplesBuffer->data();
+        context.subsamplesBufferSizeInBytes = mappedSubsamplesBuffer->size();
     }
 
-    return true;
+    return priv->cdmProxy->cencDecrypt(context);
 }
 
 #endif // ENABLE(ENCRYPTED_MEDIA) && USE(GSTREAMER)
index 3683d04..47b560f 100644 (file)
 
 #if ENABLE(ENCRYPTED_MEDIA) && USE(GSTREAMER)
 
+#include "CDMProxy.h"
 #include "GStreamerCommon.h"
 #include "GStreamerEMEUtilities.h"
 #include <wtf/Condition.h>
 #include <wtf/PrintStream.h>
 #include <wtf/RunLoop.h>
+#include <wtf/Scope.h>
 
-using WebCore::ProxyCDM;
+using WebCore::CDMProxy;
 
 #define WEBKIT_MEDIA_CENC_DECRYPT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), WEBKIT_TYPE_MEDIA_CENC_DECRYPT, WebKitMediaCommonEncryptionDecryptPrivate))
 struct _WebKitMediaCommonEncryptionDecryptPrivate {
     GRefPtr<GstEvent> protectionEvent;
-    RefPtr<ProxyCDM> proxyCDM;
-    bool keyReceived { false };
-    bool waitingForKey { false };
-    Lock mutex;
-    Condition condition;
+    RefPtr<CDMProxy> cdmProxy;
+
+    Lock cdmAttachmentMutex;
+    Condition cdmAttachmentCondition;
 };
 
+static constexpr Seconds MaxSecondsToWaitForCDMProxy = 5_s;
+
 static GstStateChangeReturn changeState(GstElement*, GstStateChange transition);
 static void finalize(GObject*);
 static GstCaps* transformCaps(GstBaseTransform*, GstPadDirection, GstCaps*, GstCaps*);
 static GstFlowReturn transformInPlace(GstBaseTransform*, GstBuffer*);
 static gboolean sinkEventHandler(GstBaseTransform*, GstEvent*);
-static gboolean queryHandler(GstBaseTransform*, GstPadDirection, GstQuery*);
-static bool isCDMInstanceAvailable(WebKitMediaCommonEncryptionDecrypt*);
 static void setContext(GstElement*, GstContext*);
 
-
 GST_DEBUG_CATEGORY_STATIC(webkit_media_common_encryption_decrypt_debug_category);
 #define GST_CAT_DEFAULT webkit_media_common_encryption_decrypt_debug_category
 
@@ -76,7 +76,6 @@ static void webkit_media_common_encryption_decrypt_class_init(WebKitMediaCommonE
     baseTransformClass->transform_caps = GST_DEBUG_FUNCPTR(transformCaps);
     baseTransformClass->transform_ip_on_passthrough = FALSE;
     baseTransformClass->sink_event = GST_DEBUG_FUNCPTR(sinkEventHandler);
-    baseTransformClass->query = GST_DEBUG_FUNCPTR(queryHandler);
 
     g_type_class_add_private(klass, sizeof(WebKitMediaCommonEncryptionDecryptPrivate));
 }
@@ -177,29 +176,20 @@ static GstFlowReturn transformInPlace(GstBaseTransform* base, GstBuffer* buffer)
 {
     WebKitMediaCommonEncryptionDecrypt* self = WEBKIT_MEDIA_CENC_DECRYPT(base);
     WebKitMediaCommonEncryptionDecryptPrivate* priv = WEBKIT_MEDIA_CENC_DECRYPT_GET_PRIVATE(self);
-    LockHolder locker(priv->mutex);
 
-    // The key might not have been received yet. Wait for it.
-    if (!priv->keyReceived) {
-        GST_DEBUG_OBJECT(self, "key not available yet, waiting for it");
-        if (GST_STATE(GST_ELEMENT(self)) < GST_STATE_PAUSED || (GST_STATE_TARGET(GST_ELEMENT(self)) != GST_STATE_VOID_PENDING && GST_STATE_TARGET(GST_ELEMENT(self)) < GST_STATE_PAUSED)) {
-            GST_ERROR_OBJECT(self, "can't process key requests in less than PAUSED state");
-            return GST_FLOW_NOT_SUPPORTED;
-        }
-        // Send "drm-cdm-instance-needed" message to the player to resend the CDMInstance if available and inform we are waiting for key.
-        priv->waitingForKey = true;
-        gst_element_post_message(GST_ELEMENT(self), gst_message_new_element(GST_OBJECT(self), gst_structure_new_empty("drm-waiting-for-key")));
+    LockHolder locker(priv->cdmAttachmentMutex);
 
-        priv->condition.waitFor(priv->mutex, Seconds(5), [priv] {
-            return priv->keyReceived;
+    // 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->cdmAttachmentCondition.waitFor(priv->cdmAttachmentMutex, MaxSecondsToWaitForCDMProxy, [priv] {
+            return priv->cdmProxy;
         });
-        if (!priv->keyReceived) {
-            GST_ERROR_OBJECT(self, "key not available");
+        if (!priv->cdmProxy) {
+            GST_ERROR_OBJECT(self, "CDMProxy was not retrieved in time");
             return GST_FLOW_NOT_SUPPORTED;
         }
-        GST_DEBUG_OBJECT(self, "key received, continuing");
-        priv->waitingForKey = false;
-        gst_element_post_message(GST_ELEMENT(self), gst_message_new_element(GST_OBJECT(self), gst_structure_new_empty("drm-key-received")));
+        GST_DEBUG_OBJECT(self, "CDM now available with address %p", priv->cdmProxy.get());
     }
 
     GstProtectionMeta* protectionMeta = reinterpret_cast<GstProtectionMeta*>(gst_buffer_get_protection_meta(buffer));
@@ -207,32 +197,30 @@ static GstFlowReturn transformInPlace(GstBaseTransform* base, GstBuffer* buffer)
         GST_ERROR_OBJECT(self, "Failed to get GstProtection metadata from buffer %p", buffer);
         return GST_FLOW_NOT_SUPPORTED;
     }
+    auto removeProtectionMetaOnReturn = makeScopeExit([buffer, protectionMeta] {
+        gst_buffer_remove_meta(buffer, reinterpret_cast<GstMeta*>(protectionMeta));
+    });
 
     unsigned ivSize;
     if (!gst_structure_get_uint(protectionMeta->info, "iv_size", &ivSize)) {
         GST_ERROR_OBJECT(self, "Failed to get iv_size");
-        gst_buffer_remove_meta(buffer, reinterpret_cast<GstMeta*>(protectionMeta));
         return GST_FLOW_NOT_SUPPORTED;
     }
 
     gboolean encrypted;
     if (!gst_structure_get_boolean(protectionMeta->info, "encrypted", &encrypted)) {
         GST_ERROR_OBJECT(self, "Failed to get encrypted flag");
-        gst_buffer_remove_meta(buffer, reinterpret_cast<GstMeta*>(protectionMeta));
         return GST_FLOW_NOT_SUPPORTED;
     }
 
-    if (!ivSize || !encrypted) {
-        gst_buffer_remove_meta(buffer, reinterpret_cast<GstMeta*>(protectionMeta));
+    if (!ivSize || !encrypted)
         return GST_FLOW_OK;
-    }
 
     GST_DEBUG_OBJECT(base, "protection meta: %" GST_PTR_FORMAT, protectionMeta->info);
 
     unsigned subSampleCount;
     if (!gst_structure_get_uint(protectionMeta->info, "subsample_count", &subSampleCount)) {
         GST_ERROR_OBJECT(self, "Failed to get subsample_count");
-        gst_buffer_remove_meta(buffer, reinterpret_cast<GstMeta*>(protectionMeta));
         return GST_FLOW_NOT_SUPPORTED;
     }
 
@@ -242,17 +230,19 @@ static GstFlowReturn transformInPlace(GstBaseTransform* base, GstBuffer* buffer)
         value = gst_structure_get_value(protectionMeta->info, "subsamples");
         if (!value) {
             GST_ERROR_OBJECT(self, "Failed to get subsamples");
-            gst_buffer_remove_meta(buffer, reinterpret_cast<GstMeta*>(protectionMeta));
             return GST_FLOW_NOT_SUPPORTED;
         }
         subSamplesBuffer = gst_value_get_buffer(value);
+        if (!subSamplesBuffer) {
+            GST_ERROR_OBJECT(self, "There is no subsamples buffer, but a positive subsample count");
+            return GST_FLOW_NOT_SUPPORTED;
+        }
     }
 
     value = gst_structure_get_value(protectionMeta->info, "kid");
 
     if (!value) {
         GST_ERROR_OBJECT(self, "Failed to get key id for buffer");
-        gst_buffer_remove_meta(buffer, reinterpret_cast<GstMeta*>(protectionMeta));
         return GST_FLOW_NOT_SUPPORTED;
     }
     GstBuffer* keyIDBuffer = gst_value_get_buffer(value);
@@ -260,7 +250,6 @@ static GstFlowReturn transformInPlace(GstBaseTransform* base, GstBuffer* buffer)
     value = gst_structure_get_value(protectionMeta->info, "iv");
     if (!value) {
         GST_ERROR_OBJECT(self, "Failed to get IV for sample");
-        gst_buffer_remove_meta(buffer, reinterpret_cast<GstMeta*>(protectionMeta));
         return GST_FLOW_NOT_SUPPORTED;
     }
 
@@ -270,71 +259,91 @@ static GstFlowReturn transformInPlace(GstBaseTransform* base, GstBuffer* buffer)
     GST_TRACE_OBJECT(self, "decrypting");
     if (!klass->decrypt(self, ivBuffer, keyIDBuffer, buffer, subSampleCount, subSamplesBuffer)) {
         GST_ERROR_OBJECT(self, "Decryption failed");
-        gst_buffer_remove_meta(buffer, reinterpret_cast<GstMeta*>(protectionMeta));
         return GST_FLOW_NOT_SUPPORTED;
     }
 
-    gst_buffer_remove_meta(buffer, reinterpret_cast<GstMeta*>(protectionMeta));
     return GST_FLOW_OK;
 }
 
-static bool isCDMInstanceAvailable(WebKitMediaCommonEncryptionDecrypt* self)
+static bool isCDMProxyAvailable(WebKitMediaCommonEncryptionDecrypt* self)
 {
     WebKitMediaCommonEncryptionDecryptPrivate* priv = WEBKIT_MEDIA_CENC_DECRYPT_GET_PRIVATE(self);
+    auto locker = holdLock(priv->cdmAttachmentMutex);
+    return priv->cdmProxy;
+}
 
-    ASSERT(priv->mutex.isLocked());
-
-    if (!priv->proxyCDM) {
-        GRefPtr<GstContext> context = adoptGRef(gst_element_get_context(GST_ELEMENT(self), "drm-cdm-instance"));
-        // 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
-        // message. In this case that does not make a lot of sense since only the app (player) answers it, meaning that no query is going to solve it. A message
-        // could be helpful but the player sets the context as soon as it gets the CDMInstance and if it does not have it, we have no way of asking for one as it is
-        // something provided by crossplatform code. This means that we won't be able to answer the bus request in any way either. Summing up, neither queries nor bus
-        // requests are useful here.
-        if (context) {
-            const GValue* value = gst_structure_get_value(gst_context_get_structure(context.get()), "cdm-instance");
-            priv->proxyCDM = value ? reinterpret_cast<ProxyCDM*>(g_value_get_pointer(value)) : nullptr;
-            if (priv->proxyCDM)
-                GST_DEBUG_OBJECT(self, "received a new CDM proxy instance %p, refcount %u", priv->proxyCDM.get(), priv->proxyCDM->refCount());
-            else
-                GST_TRACE_OBJECT(self, "CDM instance was detached");
+static CDMProxy* getCDMProxyFromGstContext(WebKitMediaCommonEncryptionDecrypt* self)
+{
+    GRefPtr<GstContext> context = adoptGRef(gst_element_get_context(GST_ELEMENT(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
+    // message. In this case that does not make a lot of sense since only the app (player) answers it, meaning that no query is going to solve it. A message
+    // could be helpful but the player sets the context as soon as it gets the CDMInstance and if it does not have it, we have no way of asking for one as it is
+    // something provided by crossplatform code. This means that we won't be able to answer the bus request in any way either. Summing up, neither queries nor bus
+    // requests are useful here.
+    if (context) {
+        const GValue* value = gst_structure_get_value(gst_context_get_structure(context.get()), "cdm-proxy");
+        if (value) {
+            proxy = reinterpret_cast<CDMProxy*>(g_value_get_pointer(value));
+            if (proxy) {
+                GST_DEBUG_OBJECT(self, "received a new CDM proxy instance %p, refcount %u", proxy, proxy->refCount());
+                return proxy;
+            }
         }
+        GST_TRACE_OBJECT(self, "CDMProxy not available in the context");
     }
-
-    GST_TRACE_OBJECT(self, "CDM instance available %s", boolForPrinting(priv->proxyCDM.get()));
-    return priv->proxyCDM;
+    return nullptr;
 }
 
-static gboolean sinkEventHandler(GstBaseTransform* trans, GstEvent* event)
+static void attachCDMProxy(WebKitMediaCommonEncryptionDecrypt* self, CDMProxy* proxy)
 {
-    WebKitMediaCommonEncryptionDecrypt* self = WEBKIT_MEDIA_CENC_DECRYPT(trans);
     WebKitMediaCommonEncryptionDecryptPrivate* priv = WEBKIT_MEDIA_CENC_DECRYPT_GET_PRIVATE(self);
     WebKitMediaCommonEncryptionDecryptClass* klass = WEBKIT_MEDIA_CENC_DECRYPT_GET_CLASS(self);
-    gboolean result = FALSE;
 
-    switch (GST_EVENT_TYPE(event)) {
-    case GST_EVENT_CUSTOM_DOWNSTREAM_OOB: {
-        // FIXME: https://bugs.webkit.org/show_bug.cgi?id=191355
-        // We should be handling protection events in this class in
-        // addition to out-of-band data. In regular playback, after a
-        // preferred system ID context is set, any future protection
-        // events will not be handled by the demuxer, so the must be
-        // handled in here.
-        LockHolder locker(priv->mutex);
-        if (!isCDMInstanceAvailable(self)) {
-            GST_ERROR_OBJECT(self, "No CDM instance available");
+    auto locker = holdLock(priv->cdmAttachmentMutex);
+    GST_ERROR_OBJECT(self, "Attaching CDMProxy %p", proxy);
+    priv->cdmProxy = proxy;
+    klass->cdmProxyAttached(self, priv->cdmProxy);
+    priv->cdmAttachmentCondition.notifyOne();
+}
+
+static gboolean installCDMProxyIfNotAvailable(WebKitMediaCommonEncryptionDecrypt* self)
+{
+    if (!isCDMProxyAvailable(self)) {
+        gboolean result = FALSE;
+
+        CDMProxy* proxy = getCDMProxyFromGstContext(self);
+        if (proxy) {
+            attachCDMProxy(self, proxy);
+            result = TRUE;
+        } else {
+            GST_ERROR_OBJECT(self, "Failed to retrieve CDMProxy from context");
             result = FALSE;
-            break;
         }
+        return result;
+    }
 
-        if (klass->handleKeyResponse(self, priv->proxyCDM)) {
-            GST_DEBUG_OBJECT(self, "key received");
-            priv->keyReceived = true;
-            priv->condition.notifyOne();
-        }
+    return TRUE;
+}
+
+static gboolean sinkEventHandler(GstBaseTransform* trans, GstEvent* event)
+{
+    WebKitMediaCommonEncryptionDecrypt* self = WEBKIT_MEDIA_CENC_DECRYPT(trans);
+    gboolean result = FALSE;
 
+    // FIXME: https://bugs.webkit.org/show_bug.cgi?id=191355
+    // We should be handling protection events in this class in
+    // addition to out-of-band data. In regular playback, after a
+    // preferred system ID context is set, any future protection
+    // events will not be handled by the demuxer, so they must be
+    // handled in here.
+    switch (GST_EVENT_TYPE(event)) {
+    case GST_EVENT_CUSTOM_DOWNSTREAM_OOB: {
+        ASSERT(gst_event_has_name(event, "attempt-to-decrypt"));
+        GST_DEBUG_OBJECT(self, "Handling attempt-to-decrypt");
+        result = installCDMProxyIfNotAvailable(self);
         gst_event_unref(event);
-        result = TRUE;
         break;
     }
     default:
@@ -345,16 +354,6 @@ static gboolean sinkEventHandler(GstBaseTransform* trans, GstEvent* event)
     return result;
 }
 
-static gboolean queryHandler(GstBaseTransform* trans, GstPadDirection direction, GstQuery* query)
-{
-    if (gst_structure_has_name(gst_query_get_structure(query), "any-decryptor-waiting-for-key")) {
-        WebKitMediaCommonEncryptionDecryptPrivate* priv = WEBKIT_MEDIA_CENC_DECRYPT_GET_PRIVATE(trans);
-        GST_TRACE_OBJECT(trans, "any-decryptor-waiting-for-key query, waitingforkey %s", boolForPrinting(priv->waitingForKey));
-        return priv->waitingForKey;
-    }
-    return GST_BASE_TRANSFORM_CLASS(parent_class)->query(trans, direction, query);
-}
-
 static GstStateChangeReturn changeState(GstElement* element, GstStateChange transition)
 {
     WebKitMediaCommonEncryptionDecrypt* self = WEBKIT_MEDIA_CENC_DECRYPT(element);
@@ -363,7 +362,7 @@ static GstStateChangeReturn changeState(GstElement* element, GstStateChange tran
     switch (transition) {
     case GST_STATE_CHANGE_PAUSED_TO_READY:
         GST_DEBUG_OBJECT(self, "PAUSED->READY");
-        priv->condition.notifyOne();
+        priv->cdmAttachmentCondition.notifyOne();
         break;
     default:
         break;
@@ -380,12 +379,14 @@ static void setContext(GstElement* element, GstContext* context)
 {
     WebKitMediaCommonEncryptionDecrypt* self = WEBKIT_MEDIA_CENC_DECRYPT(element);
     WebKitMediaCommonEncryptionDecryptPrivate* priv = WEBKIT_MEDIA_CENC_DECRYPT_GET_PRIVATE(self);
+    WebKitMediaCommonEncryptionDecryptClass* klass = WEBKIT_MEDIA_CENC_DECRYPT_GET_CLASS(self);
 
-    if (gst_context_has_context_type(context, "drm-cdm-instance")) {
-        const GValue* value = gst_structure_get_value(gst_context_get_structure(context), "cdm-instance");
-        LockHolder locker(priv->mutex);
-        priv->proxyCDM = value ? reinterpret_cast<ProxyCDM*>(g_value_get_pointer(value)) : nullptr;
-        GST_DEBUG_OBJECT(self, "received new CDMInstance %p", priv->proxyCDM.get());
+    if (gst_context_has_context_type(context, "drm-cdm-proxy")) {
+        const GValue* value = gst_structure_get_value(gst_context_get_structure(context), "cdm-proxy");
+        LockHolder locker(priv->cdmAttachmentMutex);
+        priv->cdmProxy = value ? reinterpret_cast<CDMProxy*>(g_value_get_pointer(value)) : nullptr;
+        GST_DEBUG_OBJECT(self, "received new CDMInstance %p", priv->cdmProxy.get());
+        klass->cdmProxyAttached(self, priv->cdmProxy);
         return;
     }
 
index 0651dfb..8575a07 100644 (file)
@@ -24,7 +24,7 @@
 
 #if ENABLE(ENCRYPTED_MEDIA) && USE(GSTREAMER)
 
-#include <CDMInstance.h>
+#include <CDMProxy.h>
 #include <gst/base/gstbasetransform.h>
 #include <gst/gst.h>
 #include <wtf/RefPtr.h>
@@ -55,8 +55,8 @@ struct _WebKitMediaCommonEncryptionDecryptClass {
     GstBaseTransformClass parentClass;
 
     const char* protectionSystemId;
-    bool (*handleKeyResponse)(WebKitMediaCommonEncryptionDecrypt*, RefPtr<WebCore::ProxyCDM>);
-    bool (*decrypt)(WebKitMediaCommonEncryptionDecrypt*, GstBuffer* ivBuffer, GstBuffer* keyIDBuffer, GstBuffer* buffer, unsigned subSamplesCount, GstBuffer* subSamplesBuffer);
+    bool (*cdmProxyAttached)(WebKitMediaCommonEncryptionDecrypt*, RefPtr<WebCore::CDMProxy>);
+    bool (*decrypt)(WebKitMediaCommonEncryptionDecrypt*, GstBuffer* ivBuffer, GstBuffer* keyIDBuffer, GstBuffer* buffer, unsigned subsamplesCount, GstBuffer* subsamplesBuffer);
 };
 
 G_END_DECLS
index b32b5a0..ce5d8f7 100644 (file)
@@ -226,8 +226,6 @@ Optional<String> MockCDM::sanitizeSessionId(const String& sessionId) const
     return WTF::nullopt;
 }
 
-ProxyCDMMock::~ProxyCDMMock() = default;
-
 MockCDMInstance::MockCDMInstance(WeakPtr<MockCDM> cdm)
     : m_cdm(cdm)
 {
@@ -296,11 +294,6 @@ RefPtr<CDMInstanceSession> MockCDMInstance::createSession()
     return adoptRef(new MockCDMInstanceSession(makeWeakPtr(*this)));
 }
 
-RefPtr<ProxyCDM> MockCDMInstance::proxyCDM() const
-{
-    return adoptRef(new ProxyCDMMock());
-}
-
 MockCDMInstanceSession::MockCDMInstanceSession(WeakPtr<MockCDMInstance>&& instance)
     : m_instance(WTFMove(instance))
 {
index d7e66e6..39909b9 100644 (file)
@@ -99,11 +99,6 @@ private:
     HashMap<String, Vector<Ref<SharedBuffer>>> m_sessions;
 };
 
-class ProxyCDMMock final : public ProxyCDM {
-public:
-    virtual ~ProxyCDMMock();
-};
-
 class MockCDM : public CDMPrivate, public CanMakeWeakPtr<MockCDM> {
     WTF_MAKE_FAST_ALLOCATED;
 public:
@@ -150,7 +145,6 @@ private:
     SuccessValue setStorageDirectory(const String&) final;
     const String& keySystem() const final;
     RefPtr<CDMInstanceSession> createSession() final;
-    RefPtr<ProxyCDM> proxyCDM() const final;
 
     WeakPtr<MockCDM> m_cdm;
     bool m_distinctiveIdentifiersAllowed { true };