1/*
2 * Copyright (C) 2016 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "MockCDMFactory.h"
28
29#if ENABLE(ENCRYPTED_MEDIA)
30
31#include "InitDataRegistry.h"
32#include <JavaScriptCore/ArrayBuffer.h>
33#include <wtf/Algorithms.h>
34#include <wtf/NeverDestroyed.h>
35#include <wtf/UUID.h>
36#include <wtf/text/StringHash.h>
37#include <wtf/text/StringView.h>
38
39namespace WebCore {
40
41MockCDMFactory::MockCDMFactory()
42 : m_supportedSessionTypes({ MediaKeySessionType::Temporary, MediaKeySessionType::PersistentUsageRecord, MediaKeySessionType::PersistentLicense })
43 , m_supportedEncryptionSchemes({ MediaKeyEncryptionScheme::cenc })
44{
45 CDMFactory::registerFactory(*this);
46}
47
48MockCDMFactory::~MockCDMFactory()
49{
50 unregister();
51}
52
53void MockCDMFactory::unregister()
54{
55 if (m_registered) {
56 CDMFactory::unregisterFactory(*this);
57 m_registered = false;
58 }
59}
60
61bool MockCDMFactory::supportsKeySystem(const String& keySystem)
62{
63 return equalIgnoringASCIICase(keySystem, "org.webkit.mock");
64}
65
66void MockCDMFactory::addKeysToSessionWithID(const String& id, Vector<Ref<SharedBuffer>>&& keys)
67{
68 auto addResult = m_sessions.add(id, WTFMove(keys));
69 if (addResult.isNewEntry)
70 return;
71
72 auto& value = addResult.iterator->value;
73 for (auto& key : keys)
74 value.append(WTFMove(key));
75}
76
77Vector<Ref<SharedBuffer>> MockCDMFactory::removeKeysFromSessionWithID(const String& id)
78{
79 auto it = m_sessions.find(id);
80 if (it == m_sessions.end())
81 return { };
82
83 return WTFMove(it->value);
84}
85
86const Vector<Ref<SharedBuffer>>* MockCDMFactory::keysForSessionWithID(const String& id) const
87{
88 auto it = m_sessions.find(id);
89 if (it == m_sessions.end())
90 return nullptr;
91 return &it->value;
92}
93
94void MockCDMFactory::setSupportedDataTypes(Vector<String>&& types)
95{
96 m_supportedDataTypes.clear();
97 for (auto& type : types)
98 m_supportedDataTypes.append(type);
99}
100
101std::unique_ptr<CDMPrivate> MockCDMFactory::createCDM(const String&)
102{
103 return std::make_unique<MockCDM>(makeWeakPtr(*this));
104}
105
106MockCDM::MockCDM(WeakPtr<MockCDMFactory> factory)
107 : m_factory(WTFMove(factory))
108{
109}
110
111bool MockCDM::supportsInitDataType(const AtomString& initDataType) const
112{
113 if (m_factory)
114 return m_factory->supportedDataTypes().contains(initDataType);
115 return false;
116}
117
118bool MockCDM::supportsConfiguration(const MediaKeySystemConfiguration& configuration) const
119{
120 auto capabilityHasSupportedEncryptionScheme = [&] (auto& capability) {
121 if (capability.encryptionScheme)
122 return m_factory->supportedEncryptionSchemes().contains(capability.encryptionScheme.value());
123 return true;
124 };
125
126 if (!configuration.audioCapabilities.isEmpty() && !anyOf(configuration.audioCapabilities, capabilityHasSupportedEncryptionScheme))
127 return false;
128
129 if (!configuration.videoCapabilities.isEmpty() && !anyOf(configuration.videoCapabilities, capabilityHasSupportedEncryptionScheme))
130 return false;
131
132 return true;
133
134}
135
136bool MockCDM::supportsConfigurationWithRestrictions(const MediaKeySystemConfiguration&, const MediaKeysRestrictions&) const
137{
138 // NOTE: Implement;
139 return true;
140}
141
142bool MockCDM::supportsSessionTypeWithConfiguration(MediaKeySessionType& sessionType, const MediaKeySystemConfiguration&) const
143{
144 if (!m_factory || !m_factory->supportedSessionTypes().contains(sessionType))
145 return false;
146
147 // NOTE: Implement configuration checking;
148 return true;
149}
150
151bool MockCDM::supportsRobustness(const String& robustness) const
152{
153 if (m_factory)
154 return m_factory->supportedRobustness().contains(robustness);
155 return false;
156}
157
158MediaKeysRequirement MockCDM::distinctiveIdentifiersRequirement(const MediaKeySystemConfiguration&, const MediaKeysRestrictions&) const
159{
160 if (m_factory)
161 return m_factory->distinctiveIdentifiersRequirement();
162 return MediaKeysRequirement::Optional;
163}
164
165MediaKeysRequirement MockCDM::persistentStateRequirement(const MediaKeySystemConfiguration&, const MediaKeysRestrictions&) const
166{
167 if (m_factory)
168 return m_factory->persistentStateRequirement();
169 return MediaKeysRequirement::Optional;
170}
171
172bool MockCDM::distinctiveIdentifiersAreUniquePerOriginAndClearable(const MediaKeySystemConfiguration&) const
173{
174 // NOTE: Implement;
175 return true;
176}
177
178RefPtr<CDMInstance> MockCDM::createInstance()
179{
180 if (m_factory && !m_factory->canCreateInstances())
181 return nullptr;
182 return adoptRef(new MockCDMInstance(makeWeakPtr(*this)));
183}
184
185void MockCDM::loadAndInitialize()
186{
187 // No-op.
188}
189
190bool MockCDM::supportsServerCertificates() const
191{
192 return m_factory && m_factory->supportsServerCertificates();
193}
194
195bool MockCDM::supportsSessions() const
196{
197 return m_factory && m_factory->supportsSessions();
198}
199
200bool MockCDM::supportsInitData(const AtomString& initDataType, const SharedBuffer& initData) const
201{
202 if (!supportsInitDataType(initDataType))
203 return false;
204
205 UNUSED_PARAM(initData);
206 return true;
207}
208
209RefPtr<SharedBuffer> MockCDM::sanitizeResponse(const SharedBuffer& response) const
210{
211 if (!charactersAreAllASCII(reinterpret_cast<const LChar*>(response.data()), response.size()))
212 return nullptr;
213
214 Vector<String> responseArray = String(response.data(), response.size()).split(' ');
215
216 if (!responseArray.contains(String("valid-response"_s)))
217 return nullptr;
218
219 return response.copy();
220}
221
222Optional<String> MockCDM::sanitizeSessionId(const String& sessionId) const
223{
224 if (equalLettersIgnoringASCIICase(sessionId, "valid-loaded-session"))
225 return sessionId;
226 return WTF::nullopt;
227}
228
229MockCDMInstance::MockCDMInstance(WeakPtr<MockCDM> cdm)
230 : m_cdm(cdm)
231{
232}
233
234CDMInstance::SuccessValue MockCDMInstance::initializeWithConfiguration(const MediaKeySystemConfiguration& configuration)
235{
236 if (!m_cdm || !m_cdm->supportsConfiguration(configuration))
237 return Failed;
238
239 return Succeeded;
240}
241
242CDMInstance::SuccessValue MockCDMInstance::setDistinctiveIdentifiersAllowed(bool distinctiveIdentifiersAllowed)
243{
244 if (m_distinctiveIdentifiersAllowed == distinctiveIdentifiersAllowed)
245 return Succeeded;
246
247 auto* factory = m_cdm ? m_cdm->factory() : nullptr;
248
249 if (!factory || (!distinctiveIdentifiersAllowed && factory->distinctiveIdentifiersRequirement() == MediaKeysRequirement::Required))
250 return Failed;
251
252 m_distinctiveIdentifiersAllowed = distinctiveIdentifiersAllowed;
253 return Succeeded;
254}
255
256CDMInstance::SuccessValue MockCDMInstance::setPersistentStateAllowed(bool persistentStateAllowed)
257{
258 if (m_persistentStateAllowed == persistentStateAllowed)
259 return Succeeded;
260
261 MockCDMFactory* factory = m_cdm ? m_cdm->factory() : nullptr;
262
263 if (!factory || (!persistentStateAllowed && factory->persistentStateRequirement() == MediaKeysRequirement::Required))
264 return Failed;
265
266 m_persistentStateAllowed = persistentStateAllowed;
267 return Succeeded;
268}
269
270CDMInstance::SuccessValue MockCDMInstance::setServerCertificate(Ref<SharedBuffer>&& certificate)
271{
272 StringView certificateStringView(reinterpret_cast<const LChar*>(certificate->data()), certificate->size());
273
274 if (equalIgnoringASCIICase(certificateStringView, "valid"))
275 return Succeeded;
276 return Failed;
277}
278
279CDMInstance::SuccessValue MockCDMInstance::setStorageDirectory(const String&)
280{
281 // On disk storage is unused; no-op.
282 return Succeeded;
283}
284
285const String& MockCDMInstance::keySystem() const
286{
287 static const NeverDestroyed<String> s_keySystem = MAKE_STATIC_STRING_IMPL("org.webkit.mock");
288
289 return s_keySystem;
290}
291
292RefPtr<CDMInstanceSession> MockCDMInstance::createSession()
293{
294 return adoptRef(new MockCDMInstanceSession(makeWeakPtr(*this)));
295}
296
297MockCDMInstanceSession::MockCDMInstanceSession(WeakPtr<MockCDMInstance>&& instance)
298 : m_instance(WTFMove(instance))
299{
300}
301
302void MockCDMInstanceSession::requestLicense(LicenseType licenseType, const AtomString& initDataType, Ref<SharedBuffer>&& initData, LicenseCallback&& callback)
303{
304 MockCDMFactory* factory = m_instance ? m_instance->factory() : nullptr;
305 if (!factory) {
306 callback(SharedBuffer::create(), emptyAtom(), false, SuccessValue::Failed);
307 return;
308 }
309
310 if (!factory->supportedSessionTypes().contains(licenseType) || !factory->supportedDataTypes().contains(initDataType)) {
311 callback(SharedBuffer::create(), emptyString(), false, SuccessValue::Failed);
312 return;
313 }
314
315 auto keyIDs = InitDataRegistry::shared().extractKeyIDs(initDataType, initData);
316 if (!keyIDs || keyIDs.value().isEmpty()) {
317 callback(SharedBuffer::create(), emptyString(), false, SuccessValue::Failed);
318 return;
319 }
320
321 String sessionID = createCanonicalUUIDString();
322 factory->addKeysToSessionWithID(sessionID, WTFMove(keyIDs.value()));
323
324 CString license { "license" };
325 callback(SharedBuffer::create(license.data(), license.length()), sessionID, false, SuccessValue::Succeeded);
326}
327
328void MockCDMInstanceSession::updateLicense(const String& sessionID, LicenseType, const SharedBuffer& response, LicenseUpdateCallback&& callback)
329{
330 MockCDMFactory* factory = m_instance ? m_instance->factory() : nullptr;
331 if (!factory) {
332 callback(false, WTF::nullopt, WTF::nullopt, WTF::nullopt, SuccessValue::Failed);
333 return;
334 }
335
336 Vector<String> responseVector = String(response.data(), response.size()).split(' ');
337
338 if (responseVector.contains(String("invalid-format"_s))) {
339 callback(false, WTF::nullopt, WTF::nullopt, WTF::nullopt, SuccessValue::Failed);
340 return;
341 }
342
343 Optional<KeyStatusVector> changedKeys;
344 if (responseVector.contains(String("keys-changed"_s))) {
345 const auto* keys = factory->keysForSessionWithID(sessionID);
346 if (keys) {
347 KeyStatusVector keyStatusVector;
348 keyStatusVector.reserveInitialCapacity(keys->size());
349 for (auto& key : *keys)
350 keyStatusVector.uncheckedAppend({ key.copyRef(), KeyStatus::Usable });
351
352 changedKeys = WTFMove(keyStatusVector);
353 }
354 }
355
356 // FIXME: Session closure, expiration and message handling should be implemented
357 // once the relevant algorithms are supported.
358
359 callback(false, WTFMove(changedKeys), WTF::nullopt, WTF::nullopt, SuccessValue::Succeeded);
360}
361
362void MockCDMInstanceSession::loadSession(LicenseType, const String&, const String&, LoadSessionCallback&& callback)
363{
364 MockCDMFactory* factory = m_instance ? m_instance->factory() : nullptr;
365 if (!factory) {
366 callback(WTF::nullopt, WTF::nullopt, WTF::nullopt, SuccessValue::Failed, SessionLoadFailure::Other);
367 return;
368 }
369
370 // FIXME: Key status and expiration handling should be implemented once the relevant algorithms are supported.
371
372 CString messageData { "session loaded" };
373 Message message { MessageType::LicenseRenewal, SharedBuffer::create(messageData.data(), messageData.length()) };
374
375 callback(WTF::nullopt, WTF::nullopt, WTFMove(message), SuccessValue::Succeeded, SessionLoadFailure::None);
376}
377
378void MockCDMInstanceSession::closeSession(const String& sessionID, CloseSessionCallback&& callback)
379{
380 MockCDMFactory* factory = m_instance ? m_instance->factory() : nullptr;
381 if (!factory) {
382 callback();
383 return;
384 }
385
386 factory->removeSessionWithID(sessionID);
387 callback();
388}
389
390void MockCDMInstanceSession::removeSessionData(const String& id, LicenseType, RemoveSessionDataCallback&& callback)
391{
392 MockCDMFactory* factory = m_instance ? m_instance->factory() : nullptr;
393 if (!factory) {
394 callback({ }, WTF::nullopt, SuccessValue::Failed);
395 return;
396 }
397
398 auto keys = factory->removeKeysFromSessionWithID(id);
399 KeyStatusVector keyStatusVector;
400 keyStatusVector.reserveInitialCapacity(keys.size());
401 for (auto& key : keys)
402 keyStatusVector.uncheckedAppend({ WTFMove(key), KeyStatus::Released });
403
404 CString message { "remove-message" };
405 callback(WTFMove(keyStatusVector), SharedBuffer::create(message.data(), message.length()), SuccessValue::Succeeded);
406}
407
408void MockCDMInstanceSession::storeRecordOfKeyUsage(const String&)
409{
410 // FIXME: This should be implemented along with the support for persistent-usage-record sessions.
411}
412
413}
414
415#endif
416