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 | |
39 | namespace WebCore { |
40 | |
41 | MockCDMFactory::MockCDMFactory() |
42 | : m_supportedSessionTypes({ MediaKeySessionType::Temporary, MediaKeySessionType::PersistentUsageRecord, MediaKeySessionType::PersistentLicense }) |
43 | , m_supportedEncryptionSchemes({ MediaKeyEncryptionScheme::cenc }) |
44 | { |
45 | CDMFactory::registerFactory(*this); |
46 | } |
47 | |
48 | MockCDMFactory::~MockCDMFactory() |
49 | { |
50 | unregister(); |
51 | } |
52 | |
53 | void MockCDMFactory::unregister() |
54 | { |
55 | if (m_registered) { |
56 | CDMFactory::unregisterFactory(*this); |
57 | m_registered = false; |
58 | } |
59 | } |
60 | |
61 | bool MockCDMFactory::supportsKeySystem(const String& keySystem) |
62 | { |
63 | return equalIgnoringASCIICase(keySystem, "org.webkit.mock" ); |
64 | } |
65 | |
66 | void 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 | |
77 | Vector<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 | |
86 | const 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 | |
94 | void MockCDMFactory::setSupportedDataTypes(Vector<String>&& types) |
95 | { |
96 | m_supportedDataTypes.clear(); |
97 | for (auto& type : types) |
98 | m_supportedDataTypes.append(type); |
99 | } |
100 | |
101 | std::unique_ptr<CDMPrivate> MockCDMFactory::createCDM(const String&) |
102 | { |
103 | return std::make_unique<MockCDM>(makeWeakPtr(*this)); |
104 | } |
105 | |
106 | MockCDM::MockCDM(WeakPtr<MockCDMFactory> factory) |
107 | : m_factory(WTFMove(factory)) |
108 | { |
109 | } |
110 | |
111 | bool MockCDM::supportsInitDataType(const AtomicString& initDataType) const |
112 | { |
113 | if (m_factory) |
114 | return m_factory->supportedDataTypes().contains(initDataType); |
115 | return false; |
116 | } |
117 | |
118 | bool 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 | |
136 | bool MockCDM::supportsConfigurationWithRestrictions(const MediaKeySystemConfiguration&, const MediaKeysRestrictions&) const |
137 | { |
138 | // NOTE: Implement; |
139 | return true; |
140 | } |
141 | |
142 | bool 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 | |
151 | bool MockCDM::supportsRobustness(const String& robustness) const |
152 | { |
153 | if (m_factory) |
154 | return m_factory->supportedRobustness().contains(robustness); |
155 | return false; |
156 | } |
157 | |
158 | MediaKeysRequirement MockCDM::distinctiveIdentifiersRequirement(const MediaKeySystemConfiguration&, const MediaKeysRestrictions&) const |
159 | { |
160 | if (m_factory) |
161 | return m_factory->distinctiveIdentifiersRequirement(); |
162 | return MediaKeysRequirement::Optional; |
163 | } |
164 | |
165 | MediaKeysRequirement MockCDM::persistentStateRequirement(const MediaKeySystemConfiguration&, const MediaKeysRestrictions&) const |
166 | { |
167 | if (m_factory) |
168 | return m_factory->persistentStateRequirement(); |
169 | return MediaKeysRequirement::Optional; |
170 | } |
171 | |
172 | bool MockCDM::distinctiveIdentifiersAreUniquePerOriginAndClearable(const MediaKeySystemConfiguration&) const |
173 | { |
174 | // NOTE: Implement; |
175 | return true; |
176 | } |
177 | |
178 | RefPtr<CDMInstance> MockCDM::createInstance() |
179 | { |
180 | if (m_factory && !m_factory->canCreateInstances()) |
181 | return nullptr; |
182 | return adoptRef(new MockCDMInstance(makeWeakPtr(*this))); |
183 | } |
184 | |
185 | void MockCDM::loadAndInitialize() |
186 | { |
187 | // No-op. |
188 | } |
189 | |
190 | bool MockCDM::supportsServerCertificates() const |
191 | { |
192 | return m_factory && m_factory->supportsServerCertificates(); |
193 | } |
194 | |
195 | bool MockCDM::supportsSessions() const |
196 | { |
197 | return m_factory && m_factory->supportsSessions(); |
198 | } |
199 | |
200 | bool MockCDM::supportsInitData(const AtomicString& initDataType, const SharedBuffer& initData) const |
201 | { |
202 | if (!supportsInitDataType(initDataType)) |
203 | return false; |
204 | |
205 | UNUSED_PARAM(initData); |
206 | return true; |
207 | } |
208 | |
209 | RefPtr<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 | |
222 | Optional<String> MockCDM::sanitizeSessionId(const String& sessionId) const |
223 | { |
224 | if (equalLettersIgnoringASCIICase(sessionId, "valid-loaded-session" )) |
225 | return sessionId; |
226 | return WTF::nullopt; |
227 | } |
228 | |
229 | MockCDMInstance::MockCDMInstance(WeakPtr<MockCDM> cdm) |
230 | : m_cdm(cdm) |
231 | { |
232 | } |
233 | |
234 | CDMInstance::SuccessValue MockCDMInstance::initializeWithConfiguration(const MediaKeySystemConfiguration& configuration) |
235 | { |
236 | if (!m_cdm || !m_cdm->supportsConfiguration(configuration)) |
237 | return Failed; |
238 | |
239 | return Succeeded; |
240 | } |
241 | |
242 | CDMInstance::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 | |
256 | CDMInstance::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 | |
270 | CDMInstance::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 | |
279 | CDMInstance::SuccessValue MockCDMInstance::setStorageDirectory(const String&) |
280 | { |
281 | // On disk storage is unused; no-op. |
282 | return Succeeded; |
283 | } |
284 | |
285 | const 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 | |
292 | RefPtr<CDMInstanceSession> MockCDMInstance::createSession() |
293 | { |
294 | return adoptRef(new MockCDMInstanceSession(makeWeakPtr(*this))); |
295 | } |
296 | |
297 | MockCDMInstanceSession::MockCDMInstanceSession(WeakPtr<MockCDMInstance>&& instance) |
298 | : m_instance(WTFMove(instance)) |
299 | { |
300 | } |
301 | |
302 | void MockCDMInstanceSession::requestLicense(LicenseType licenseType, const AtomicString& 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 | |
328 | void 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 | |
362 | void 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 | |
378 | void 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 | |
390 | void 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 | |
408 | void 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 | |