4551944d944de571a135e1c555556198c153b18a
[WebKit-https.git] / Source / WebCore / Modules / encryptedmedia / MediaKeySession.cpp
1 /*
2  * Copyright (C) 2016 Metrological Group B.V.
3  * Copyright (C) 2016 Igalia S.L.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above
12  *    copyright notice, this list of conditions and the following
13  *    disclaimer in the documentation and/or other materials provided
14  *    with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20  * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #include "config.h"
30 #include "MediaKeySession.h"
31
32 #if ENABLE(ENCRYPTED_MEDIA)
33
34 #include "CDM.h"
35 #include "CDMInstance.h"
36 #include "MediaKeyMessageEvent.h"
37 #include "MediaKeyMessageType.h"
38 #include "MediaKeyStatusMap.h"
39 #include "NotImplemented.h"
40 #include "SharedBuffer.h"
41 #include <wtf/NeverDestroyed.h>
42
43 namespace WebCore {
44
45 static AtomicString& messageEventName()
46 {
47     NeverDestroyed<AtomicString> message { "message" };
48     return message.get();
49 }
50
51 Ref<MediaKeySession> MediaKeySession::create(ScriptExecutionContext& context, MediaKeySessionType sessionType, bool useDistinctiveIdentifier, Ref<CDM>&& implementation, Ref<CDMInstance>&& instance)
52 {
53     auto session = adoptRef(*new MediaKeySession(context, sessionType, useDistinctiveIdentifier, WTFMove(implementation), WTFMove(instance)));
54     session->suspendIfNeeded();
55     return session;
56 }
57
58 MediaKeySession::MediaKeySession(ScriptExecutionContext& context, MediaKeySessionType sessionType, bool useDistinctiveIdentifier, Ref<CDM>&& implementation, Ref<CDMInstance>&& instance)
59     : ActiveDOMObject(&context)
60     , m_expiration(std::numeric_limits<double>::quiet_NaN())
61     , m_keyStatuses(MediaKeyStatusMap::create())
62     , m_useDistinctiveIdentifier(useDistinctiveIdentifier)
63     , m_sessionType(sessionType)
64     , m_implementation(WTFMove(implementation))
65     , m_instance(WTFMove(instance))
66     , m_eventQueue(*this)
67     , m_weakPtrFactory(this)
68 {
69     // https://w3c.github.io/encrypted-media/#dom-mediakeys-setservercertificate
70     // W3C Editor's Draft 09 November 2016
71     // createSession(), ctd.
72
73     // 3.1. Let the sessionId attribute be the empty string.
74     // 3.2. Let the expiration attribute be NaN.
75     // 3.3. Let the closed attribute be a new promise.
76     // 3.4. Let key status be a new empty MediaKeyStatusMap object, and initialize it as follows:
77     // 3.4.1. Let the size attribute be 0.
78     // 3.5. Let the session type value be sessionType.
79     // 3.6. Let the uninitialized value be true.
80     // 3.7. Let the callable value be false.
81     // 3.8. Let the use distinctive identifier value be this object's use distinctive identifier value.
82     // 3.9. Let the cdm implementation value be this object's cdm implementation.
83     // 3.10. Let the cdm instance value be this object's cdm instance.
84
85     UNUSED_PARAM(m_callable);
86     UNUSED_PARAM(m_sessionType);
87     UNUSED_PARAM(m_useDistinctiveIdentifier);
88     UNUSED_PARAM(m_closed);
89     UNUSED_PARAM(m_uninitialized);
90 }
91
92 MediaKeySession::~MediaKeySession() = default;
93
94 const String& MediaKeySession::sessionId() const
95 {
96     return m_sessionId;
97 }
98
99 double MediaKeySession::expiration() const
100 {
101     return m_expiration;
102 }
103
104 Ref<MediaKeyStatusMap> MediaKeySession::keyStatuses() const
105 {
106     return m_keyStatuses.copyRef();
107 }
108
109 void MediaKeySession::generateRequest(const AtomicString& initDataType, const BufferSource& initData, Ref<DeferredPromise>&& promise)
110 {
111     // https://w3c.github.io/encrypted-media/#dom-mediakeysession-generaterequest
112     // W3C Editor's Draft 09 November 2016
113
114     // When this method is invoked, the user agent must run the following steps:
115     // 1. If this object is closed, return a promise rejected with an InvalidStateError.
116     // 2. If this object's uninitialized value is false, return a promise rejected with an InvalidStateError.
117     if (m_closed || !m_uninitialized) {
118         promise->reject(INVALID_STATE_ERR);
119         return;
120     }
121
122     // 3. Let this object's uninitialized value be false.
123     m_uninitialized = false;
124
125     // 4. If initDataType is the empty string, return a promise rejected with a newly created TypeError.
126     // 5. If initData is an empty array, return a promise rejected with a newly created TypeError.
127     if (initDataType.isEmpty() || !initData.length()) {
128         promise->reject(TypeError);
129         return;
130     }
131
132     // 6. If the Key System implementation represented by this object's cdm implementation value does not support
133     //    initDataType as an Initialization Data Type, return a promise rejected with a NotSupportedError. String
134     //    comparison is case-sensitive.
135     if (!m_implementation->supportsInitDataType(initDataType)) {
136         promise->reject(NOT_SUPPORTED_ERR);
137         return;
138     }
139
140     // 7. Let init data be a copy of the contents of the initData parameter.
141     // 8. Let session type be this object's session type.
142     // 9. Let promise be a new promise.
143     // 10. Run the following steps in parallel:
144     m_taskQueue.enqueueTask([this, initData = SharedBuffer::create(initData.data(), initData.length()), initDataType, promise = WTFMove(promise)] () mutable {
145         // 10.1. If the init data is not valid for initDataType, reject promise with a newly created TypeError.
146         // 10.2. Let sanitized init data be a validated and sanitized version of init data.
147         RefPtr<SharedBuffer> sanitizedInitData = m_implementation->sanitizeInitData(initDataType, initData);
148
149         // 10.3. If the preceding step failed, reject promise with a newly created TypeError.
150         if (!sanitizedInitData) {
151             promise->reject(TypeError);
152             return;
153         }
154
155         // 10.4. If sanitized init data is empty, reject promise with a NotSupportedError.
156         if (sanitizedInitData->isEmpty()) {
157             promise->reject(NOT_SUPPORTED_ERR);
158             return;
159         }
160
161         // 10.5. Let session id be the empty string.
162         // 10.6. Let message be null.
163         // 10.7. Let message type be null.
164         // 10.8. Let cdm be the CDM instance represented by this object's cdm instance value.
165         // 10.9. Use the cdm to execute the following steps:
166         // 10.9.1. If the sanitized init data is not supported by the cdm, reject promise with a NotSupportedError.
167         if (!m_implementation->supportsInitData(initDataType, *sanitizedInitData)) {
168             promise->reject(NOT_SUPPORTED_ERR);
169             return;
170         }
171
172         // 10.9.2 Follow the steps for the value of session type from the following list:
173         CDMInstance::LicenseType requestedLicenseType;
174         switch (m_sessionType) {
175         case MediaKeySessionType::Temporary:
176             // ↳ "temporary"
177             // Let requested license type be a temporary non-persistable license.
178             requestedLicenseType = CDMInstance::LicenseType::Temporary;
179             break;
180         case MediaKeySessionType::PersistentLicense:
181             // ↳ "persistent-license"
182             // Let requested license type be a persistable license.
183             requestedLicenseType = CDMInstance::LicenseType::Persistable;
184             break;
185         case MediaKeySessionType::PersistentUsageRecord:
186             // ↳ "persistent-usage-record"
187             // 1. Initialize this object's record of key usage as follows.
188             //    Set the list of key IDs known to the session to an empty list.
189             m_recordOfKeyUsage.clear();
190
191             //    Set the first decrypt time to null.
192             m_firstDecryptTime = 0;
193
194             //    Set the latest decrypt time to null.
195             m_latestDecryptTime = 0;
196
197             // 2. Let requested license type be a non-persistable license that will
198             //    persist a record of key usage.
199             requestedLicenseType = CDMInstance::LicenseType::UsageRecord;
200             break;
201         }
202
203         m_instance->requestLicense(requestedLicenseType, initDataType, WTFMove(initData), [this, weakThis = m_weakPtrFactory.createWeakPtr(), promise = WTFMove(promise)] (Ref<SharedBuffer>&& message, const String& sessionId, bool needsIndividualization, CDMInstance::SuccessValue succeeded) mutable {
204             if (!weakThis)
205                 return;
206
207             // 10.9.3. Let session id be a unique Session ID string.
208
209             MediaKeyMessageType messageType;
210             if (!needsIndividualization) {
211                 // 10.9.4. If a license request for the requested license type can be generated based on the sanitized init data:
212                 // 10.9.4.1. Let message be a license request for the requested license type generated based on the sanitized init data interpreted per initDataType.
213                 // 10.9.4.2. Let message type be "license-request".
214                 messageType = MediaKeyMessageType::LicenseRequest;
215             } else {
216                 // 10.9.5. Otherwise:
217                 // 10.9.5.1. Let message be the request that needs to be processed before a license request request for the requested license
218                 //           type can be generated based on the sanitized init data.
219                 // 10.9.5.2. Let message type reflect the type of message, either "license-request" or "individualization-request".
220                 messageType = MediaKeyMessageType::IndividualizationRequest;
221             }
222
223             // 10.10. Queue a task to run the following steps:
224             m_taskQueue.enqueueTask([this, promise = WTFMove(promise), message = WTFMove(message), messageType, sessionId, succeeded] () mutable {
225                 // 10.10.1. If any of the preceding steps failed, reject promise with a new DOMException whose name is the appropriate error name.
226                 if (succeeded == CDMInstance::SuccessValue::Failed) {
227                     promise->reject(NOT_SUPPORTED_ERR);
228                     return;
229                 }
230                 // 10.10.2. Set the sessionId attribute to session id.
231                 m_sessionId = sessionId;
232
233                 // 10.9.3. Let this object's callable value be true.
234                 m_callable = true;
235
236                 // 10.9.3. Run the Queue a "message" Event algorithm on the session, providing message type and message.
237                 enqueueMessage(messageType, message);
238
239                 // 10.9.3. Resolve promise.
240                 promise->resolve();
241             });
242         });
243     });
244
245     // 11. Return promise.
246 }
247
248 void MediaKeySession::load(const String&, Ref<DeferredPromise>&&)
249 {
250     notImplemented();
251 }
252
253 void MediaKeySession::update(const BufferSource&, Ref<DeferredPromise>&&)
254 {
255     notImplemented();
256 }
257
258 void MediaKeySession::close(Ref<DeferredPromise>&&)
259 {
260     notImplemented();
261 }
262
263 void MediaKeySession::remove(Ref<DeferredPromise>&&)
264 {
265     notImplemented();
266 }
267
268 void MediaKeySession::enqueueMessage(MediaKeyMessageType messageType, const SharedBuffer& message)
269 {
270     // 6.4.1 Queue a "message" Event
271     // https://w3c.github.io/encrypted-media/#queue-message
272     // W3C Editor's Draft 09 November 2016
273
274     // The following steps are run:
275     // 1. Let the session be the specified MediaKeySession object.
276     // 2. Queue a task to create an event named message that does not bubble and is not cancellable using the MediaKeyMessageEvent
277     //    interface with its type attribute set to message and its isTrusted attribute initialized to true, and dispatch it at the
278     //    session.
279     auto messageEvent = MediaKeyMessageEvent::create(messageEventName(), {messageType, message.createArrayBuffer()}, Event::IsTrusted::Yes);
280     m_eventQueue.enqueueEvent(WTFMove(messageEvent));
281 }
282
283 bool MediaKeySession::hasPendingActivity() const
284 {
285     notImplemented();
286     return false;
287 }
288
289 const char* MediaKeySession::activeDOMObjectName() const
290 {
291     notImplemented();
292     return "MediaKeySession";
293 }
294
295 bool MediaKeySession::canSuspendForDocumentSuspension() const
296 {
297     notImplemented();
298     return false;
299 }
300
301 void MediaKeySession::stop()
302 {
303     notImplemented();
304 }
305
306 } // namespace WebCore
307
308 #endif