| /* |
| * Copyright (C) 2016 Apple Inc. All rights reserved. |
| * |
| * 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 APPLE INC. AND ITS 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 APPLE INC. OR ITS 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 "MockCDMFactory.h" |
| |
| #if ENABLE(ENCRYPTED_MEDIA) |
| |
| #include "InitDataRegistry.h" |
| #include <JavaScriptCore/ArrayBuffer.h> |
| #include <wtf/Algorithms.h> |
| #include <wtf/NeverDestroyed.h> |
| #include <wtf/UUID.h> |
| #include <wtf/text/StringHash.h> |
| #include <wtf/text/StringView.h> |
| |
| namespace WebCore { |
| |
| MockCDMFactory::MockCDMFactory() |
| : m_supportedSessionTypes({ MediaKeySessionType::Temporary, MediaKeySessionType::PersistentUsageRecord, MediaKeySessionType::PersistentLicense }) |
| , m_supportedEncryptionSchemes({ MediaKeyEncryptionScheme::cenc }) |
| { |
| CDMFactory::registerFactory(*this); |
| } |
| |
| MockCDMFactory::~MockCDMFactory() |
| { |
| unregister(); |
| } |
| |
| void MockCDMFactory::unregister() |
| { |
| if (m_registered) { |
| CDMFactory::unregisterFactory(*this); |
| m_registered = false; |
| } |
| } |
| |
| bool MockCDMFactory::supportsKeySystem(const String& keySystem) |
| { |
| return equalIgnoringASCIICase(keySystem, "org.webkit.mock"); |
| } |
| |
| void MockCDMFactory::addKeysToSessionWithID(const String& id, Vector<Ref<SharedBuffer>>&& keys) |
| { |
| auto addResult = m_sessions.add(id, WTFMove(keys)); |
| if (addResult.isNewEntry) |
| return; |
| |
| auto& value = addResult.iterator->value; |
| for (auto& key : keys) |
| value.append(WTFMove(key)); |
| } |
| |
| Vector<Ref<SharedBuffer>> MockCDMFactory::removeKeysFromSessionWithID(const String& id) |
| { |
| auto it = m_sessions.find(id); |
| if (it == m_sessions.end()) |
| return { }; |
| |
| return WTFMove(it->value); |
| } |
| |
| const Vector<Ref<SharedBuffer>>* MockCDMFactory::keysForSessionWithID(const String& id) const |
| { |
| auto it = m_sessions.find(id); |
| if (it == m_sessions.end()) |
| return nullptr; |
| return &it->value; |
| } |
| |
| void MockCDMFactory::setSupportedDataTypes(Vector<String>&& types) |
| { |
| m_supportedDataTypes.clear(); |
| for (auto& type : types) |
| m_supportedDataTypes.append(type); |
| } |
| |
| std::unique_ptr<CDMPrivate> MockCDMFactory::createCDM(const String&) |
| { |
| return makeUnique<MockCDM>(makeWeakPtr(*this)); |
| } |
| |
| MockCDM::MockCDM(WeakPtr<MockCDMFactory> factory) |
| : m_factory(WTFMove(factory)) |
| { |
| } |
| |
| bool MockCDM::supportsInitDataType(const AtomString& initDataType) const |
| { |
| if (m_factory) |
| return m_factory->supportedDataTypes().contains(initDataType); |
| return false; |
| } |
| |
| bool MockCDM::supportsConfiguration(const MediaKeySystemConfiguration& configuration) const |
| { |
| auto capabilityHasSupportedEncryptionScheme = [&] (auto& capability) { |
| if (capability.encryptionScheme) |
| return m_factory->supportedEncryptionSchemes().contains(capability.encryptionScheme.value()); |
| return true; |
| }; |
| |
| if (!configuration.audioCapabilities.isEmpty() && !anyOf(configuration.audioCapabilities, capabilityHasSupportedEncryptionScheme)) |
| return false; |
| |
| if (!configuration.videoCapabilities.isEmpty() && !anyOf(configuration.videoCapabilities, capabilityHasSupportedEncryptionScheme)) |
| return false; |
| |
| return true; |
| |
| } |
| |
| bool MockCDM::supportsConfigurationWithRestrictions(const MediaKeySystemConfiguration&, const MediaKeysRestrictions&) const |
| { |
| // NOTE: Implement; |
| return true; |
| } |
| |
| bool MockCDM::supportsSessionTypeWithConfiguration(MediaKeySessionType& sessionType, const MediaKeySystemConfiguration&) const |
| { |
| if (!m_factory || !m_factory->supportedSessionTypes().contains(sessionType)) |
| return false; |
| |
| // NOTE: Implement configuration checking; |
| return true; |
| } |
| |
| bool MockCDM::supportsRobustness(const String& robustness) const |
| { |
| if (m_factory) |
| return m_factory->supportedRobustness().contains(robustness); |
| return false; |
| } |
| |
| MediaKeysRequirement MockCDM::distinctiveIdentifiersRequirement(const MediaKeySystemConfiguration&, const MediaKeysRestrictions&) const |
| { |
| if (m_factory) |
| return m_factory->distinctiveIdentifiersRequirement(); |
| return MediaKeysRequirement::Optional; |
| } |
| |
| MediaKeysRequirement MockCDM::persistentStateRequirement(const MediaKeySystemConfiguration&, const MediaKeysRestrictions&) const |
| { |
| if (m_factory) |
| return m_factory->persistentStateRequirement(); |
| return MediaKeysRequirement::Optional; |
| } |
| |
| bool MockCDM::distinctiveIdentifiersAreUniquePerOriginAndClearable(const MediaKeySystemConfiguration&) const |
| { |
| // NOTE: Implement; |
| return true; |
| } |
| |
| RefPtr<CDMInstance> MockCDM::createInstance() |
| { |
| if (m_factory && !m_factory->canCreateInstances()) |
| return nullptr; |
| return adoptRef(new MockCDMInstance(makeWeakPtr(*this))); |
| } |
| |
| void MockCDM::loadAndInitialize() |
| { |
| // No-op. |
| } |
| |
| bool MockCDM::supportsServerCertificates() const |
| { |
| return m_factory && m_factory->supportsServerCertificates(); |
| } |
| |
| bool MockCDM::supportsSessions() const |
| { |
| return m_factory && m_factory->supportsSessions(); |
| } |
| |
| bool MockCDM::supportsInitData(const AtomString& initDataType, const SharedBuffer& initData) const |
| { |
| if (!supportsInitDataType(initDataType)) |
| return false; |
| |
| UNUSED_PARAM(initData); |
| return true; |
| } |
| |
| RefPtr<SharedBuffer> MockCDM::sanitizeResponse(const SharedBuffer& response) const |
| { |
| if (!charactersAreAllASCII(reinterpret_cast<const LChar*>(response.data()), response.size())) |
| return nullptr; |
| |
| Vector<String> responseArray = String(response.data(), response.size()).split(' '); |
| |
| if (!responseArray.contains(String("valid-response"_s))) |
| return nullptr; |
| |
| return response.copy(); |
| } |
| |
| Optional<String> MockCDM::sanitizeSessionId(const String& sessionId) const |
| { |
| if (equalLettersIgnoringASCIICase(sessionId, "valid-loaded-session")) |
| return sessionId; |
| return WTF::nullopt; |
| } |
| |
| ProxyCDMMock::~ProxyCDMMock() = default; |
| |
| MockCDMInstance::MockCDMInstance(WeakPtr<MockCDM> cdm) |
| : m_cdm(cdm) |
| { |
| } |
| |
| CDMInstance::SuccessValue MockCDMInstance::initializeWithConfiguration(const MediaKeySystemConfiguration& configuration) |
| { |
| if (!m_cdm || !m_cdm->supportsConfiguration(configuration)) |
| return Failed; |
| |
| return Succeeded; |
| } |
| |
| CDMInstance::SuccessValue MockCDMInstance::setDistinctiveIdentifiersAllowed(bool distinctiveIdentifiersAllowed) |
| { |
| if (m_distinctiveIdentifiersAllowed == distinctiveIdentifiersAllowed) |
| return Succeeded; |
| |
| auto* factory = m_cdm ? m_cdm->factory() : nullptr; |
| |
| if (!factory || (!distinctiveIdentifiersAllowed && factory->distinctiveIdentifiersRequirement() == MediaKeysRequirement::Required)) |
| return Failed; |
| |
| m_distinctiveIdentifiersAllowed = distinctiveIdentifiersAllowed; |
| return Succeeded; |
| } |
| |
| CDMInstance::SuccessValue MockCDMInstance::setPersistentStateAllowed(bool persistentStateAllowed) |
| { |
| if (m_persistentStateAllowed == persistentStateAllowed) |
| return Succeeded; |
| |
| MockCDMFactory* factory = m_cdm ? m_cdm->factory() : nullptr; |
| |
| if (!factory || (!persistentStateAllowed && factory->persistentStateRequirement() == MediaKeysRequirement::Required)) |
| return Failed; |
| |
| m_persistentStateAllowed = persistentStateAllowed; |
| return Succeeded; |
| } |
| |
| CDMInstance::SuccessValue MockCDMInstance::setServerCertificate(Ref<SharedBuffer>&& certificate) |
| { |
| StringView certificateStringView(certificate->data(), certificate->size()); |
| |
| if (equalIgnoringASCIICase(certificateStringView, "valid")) |
| return Succeeded; |
| return Failed; |
| } |
| |
| CDMInstance::SuccessValue MockCDMInstance::setStorageDirectory(const String&) |
| { |
| // On disk storage is unused; no-op. |
| return Succeeded; |
| } |
| |
| const String& MockCDMInstance::keySystem() const |
| { |
| static const NeverDestroyed<String> s_keySystem = MAKE_STATIC_STRING_IMPL("org.webkit.mock"); |
| |
| return s_keySystem; |
| } |
| |
| 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)) |
| { |
| } |
| |
| void MockCDMInstanceSession::requestLicense(LicenseType licenseType, const AtomString& initDataType, Ref<SharedBuffer>&& initData, LicenseCallback&& callback) |
| { |
| MockCDMFactory* factory = m_instance ? m_instance->factory() : nullptr; |
| if (!factory) { |
| callback(SharedBuffer::create(), emptyAtom(), false, SuccessValue::Failed); |
| return; |
| } |
| |
| if (!factory->supportedSessionTypes().contains(licenseType) || !factory->supportedDataTypes().contains(initDataType)) { |
| callback(SharedBuffer::create(), emptyString(), false, SuccessValue::Failed); |
| return; |
| } |
| |
| auto keyIDs = InitDataRegistry::shared().extractKeyIDs(initDataType, initData); |
| if (!keyIDs || keyIDs.value().isEmpty()) { |
| callback(SharedBuffer::create(), emptyString(), false, SuccessValue::Failed); |
| return; |
| } |
| |
| String sessionID = createCanonicalUUIDString(); |
| factory->addKeysToSessionWithID(sessionID, WTFMove(keyIDs.value())); |
| |
| CString license { "license" }; |
| callback(SharedBuffer::create(license.data(), license.length()), sessionID, false, SuccessValue::Succeeded); |
| } |
| |
| void MockCDMInstanceSession::updateLicense(const String& sessionID, LicenseType, const SharedBuffer& response, LicenseUpdateCallback&& callback) |
| { |
| MockCDMFactory* factory = m_instance ? m_instance->factory() : nullptr; |
| if (!factory) { |
| callback(false, WTF::nullopt, WTF::nullopt, WTF::nullopt, SuccessValue::Failed); |
| return; |
| } |
| |
| Vector<String> responseVector = String(response.data(), response.size()).split(' '); |
| |
| if (responseVector.contains(String("invalid-format"_s))) { |
| callback(false, WTF::nullopt, WTF::nullopt, WTF::nullopt, SuccessValue::Failed); |
| return; |
| } |
| |
| Optional<KeyStatusVector> changedKeys; |
| if (responseVector.contains(String("keys-changed"_s))) { |
| const auto* keys = factory->keysForSessionWithID(sessionID); |
| if (keys) { |
| KeyStatusVector keyStatusVector; |
| keyStatusVector.reserveInitialCapacity(keys->size()); |
| for (auto& key : *keys) |
| keyStatusVector.uncheckedAppend({ key.copyRef(), KeyStatus::Usable }); |
| |
| changedKeys = WTFMove(keyStatusVector); |
| } |
| } |
| |
| // FIXME: Session closure, expiration and message handling should be implemented |
| // once the relevant algorithms are supported. |
| |
| callback(false, WTFMove(changedKeys), WTF::nullopt, WTF::nullopt, SuccessValue::Succeeded); |
| } |
| |
| void MockCDMInstanceSession::loadSession(LicenseType, const String&, const String&, LoadSessionCallback&& callback) |
| { |
| MockCDMFactory* factory = m_instance ? m_instance->factory() : nullptr; |
| if (!factory) { |
| callback(WTF::nullopt, WTF::nullopt, WTF::nullopt, SuccessValue::Failed, SessionLoadFailure::Other); |
| return; |
| } |
| |
| // FIXME: Key status and expiration handling should be implemented once the relevant algorithms are supported. |
| |
| CString messageData { "session loaded" }; |
| Message message { MessageType::LicenseRenewal, SharedBuffer::create(messageData.data(), messageData.length()) }; |
| |
| callback(WTF::nullopt, WTF::nullopt, WTFMove(message), SuccessValue::Succeeded, SessionLoadFailure::None); |
| } |
| |
| void MockCDMInstanceSession::closeSession(const String& sessionID, CloseSessionCallback&& callback) |
| { |
| MockCDMFactory* factory = m_instance ? m_instance->factory() : nullptr; |
| if (!factory) { |
| callback(); |
| return; |
| } |
| |
| factory->removeSessionWithID(sessionID); |
| callback(); |
| } |
| |
| void MockCDMInstanceSession::removeSessionData(const String& id, LicenseType, RemoveSessionDataCallback&& callback) |
| { |
| MockCDMFactory* factory = m_instance ? m_instance->factory() : nullptr; |
| if (!factory) { |
| callback({ }, WTF::nullopt, SuccessValue::Failed); |
| return; |
| } |
| |
| auto keys = factory->removeKeysFromSessionWithID(id); |
| KeyStatusVector keyStatusVector; |
| keyStatusVector.reserveInitialCapacity(keys.size()); |
| for (auto& key : keys) |
| keyStatusVector.uncheckedAppend({ WTFMove(key), KeyStatus::Released }); |
| |
| CString message { "remove-message" }; |
| callback(WTFMove(keyStatusVector), SharedBuffer::create(message.data(), message.length()), SuccessValue::Succeeded); |
| } |
| |
| void MockCDMInstanceSession::storeRecordOfKeyUsage(const String&) |
| { |
| // FIXME: This should be implemented along with the support for persistent-usage-record sessions. |
| } |
| |
| } |
| |
| #endif |