1/*
2 * Copyright (C) 2013 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 "MediaKeySession.h"
28
29#if ENABLE(ENCRYPTED_MEDIA_V2)
30
31#include "CDM.h"
32#include "CDMSession.h"
33#include "Event.h"
34#include "GenericEventQueue.h"
35#include "MediaKeyError.h"
36#include "MediaKeyMessageEvent.h"
37#include "MediaKeys.h"
38
39namespace WebCore {
40
41PassRefPtr<MediaKeySession> MediaKeySession::create(ScriptExecutionContext* context, MediaKeys* keys, const String& keySystem)
42{
43    auto session = adoptRef(new MediaKeySession(context, keys, keySystem));
44    session->suspendIfNeeded();
45    return session;
46}
47
48MediaKeySession::MediaKeySession(ScriptExecutionContext* context, MediaKeys* keys, const String& keySystem)
49    : ActiveDOMObject(context)
50    , m_keys(keys)
51    , m_keySystem(keySystem)
52    , m_asyncEventQueue(*this)
53    , m_session(keys->cdm()->createSession())
54    , m_keyRequestTimer(this, &MediaKeySession::keyRequestTimerFired)
55    , m_addKeyTimer(this, &MediaKeySession::addKeyTimerFired)
56{
57    m_session->setClient(this);
58}
59
60MediaKeySession::~MediaKeySession()
61{
62    close();
63}
64
65void MediaKeySession::setError(MediaKeyError* error)
66{
67    m_error = error;
68}
69
70void MediaKeySession::close()
71{
72    if (m_session) {
73        m_session->releaseKeys();
74        m_session->setClient(nullptr);
75    }
76    m_session = nullptr;
77    m_asyncEventQueue.cancelAllEvents();
78}
79
80const String& MediaKeySession::sessionId() const
81{
82    return m_session->sessionId();
83}
84
85void MediaKeySession::generateKeyRequest(const String& mimeType, Uint8Array* initData)
86{
87    m_pendingKeyRequests.append(PendingKeyRequest(mimeType, initData));
88    m_keyRequestTimer.startOneShot(0);
89}
90
91void MediaKeySession::keyRequestTimerFired(Timer<MediaKeySession>&)
92{
93    ASSERT(m_pendingKeyRequests.size());
94    if (!m_session)
95        return;
96
97    while (!m_pendingKeyRequests.isEmpty()) {
98        PendingKeyRequest request = m_pendingKeyRequests.takeFirst();
99
100        // NOTE: Continued from step 5 in MediaKeys::createSession().
101        // The user agent will asynchronously execute the following steps in the task:
102
103        // 1. Let cdm be the cdm loaded in the MediaKeys constructor.
104        // 2. Let destinationURL be null.
105        String destinationURL;
106        MediaKeyError::Code errorCode = 0;
107        unsigned long systemCode = 0;
108
109        // 3. Use cdm to generate a key request and follow the steps for the first matching condition from the following list:
110
111        RefPtr<Uint8Array> keyRequest = m_session->generateKeyRequest(request.mimeType, request.initData.get(), destinationURL, errorCode, systemCode);
112
113        // Otherwise [if a request is not successfully generated]:
114        if (!keyRequest) {
115            // 3.1. Create a new MediaKeyError object with the following attributes:
116            //      code = the appropriate MediaKeyError code
117            //      systemCode = a Key System-specific value, if provided, and 0 otherwise
118            // 3.2. Set the MediaKeySession object's error attribute to the error object created in the previous step.
119            // 3.3. queue a task to fire a simple event named keyerror at the MediaKeySession object.
120            sendError(errorCode, systemCode);
121            // 3.4. Abort the task.
122            continue;
123        }
124
125        // 4. queue a task to fire a simple event named keymessage at the new object
126        //    The event is of type MediaKeyMessageEvent and has:
127        //    message = key request
128        //    destinationURL = destinationURL
129        sendMessage(keyRequest.get(), destinationURL);
130    }
131}
132
133void MediaKeySession::update(Uint8Array* key, ExceptionCode& ec)
134{
135    // From <http://dvcs.w3.org/hg/html-media/raw-file/tip/encrypted-media/encrypted-media.html#dom-addkey>:
136    // The addKey(key) method must run the following steps:
137    // 1. If the first or second argument [sic] is null or an empty array, throw an INVALID_ACCESS_ERR.
138    // NOTE: the reference to a "second argument" is a spec bug.
139    if (!key || !key->length()) {
140        ec = INVALID_ACCESS_ERR;
141        return;
142    }
143
144    // 2. Schedule a task to handle the call, providing key.
145    m_pendingKeys.append(key);
146    m_addKeyTimer.startOneShot(0);
147}
148
149void MediaKeySession::addKeyTimerFired(Timer<MediaKeySession>&)
150{
151    ASSERT(m_pendingKeys.size());
152    if (!m_session)
153        return;
154
155    while (!m_pendingKeys.isEmpty()) {
156        RefPtr<Uint8Array> pendingKey = m_pendingKeys.takeFirst();
157        unsigned short errorCode = 0;
158        unsigned long systemCode = 0;
159
160        // NOTE: Continued from step 2. of MediaKeySession::update()
161        // 2.1. Let cdm be the cdm loaded in the MediaKeys constructor.
162        // NOTE: This is m_session.
163        // 2.2. Let 'did store key' be false.
164        bool didStoreKey = false;
165        // 2.3. Let 'next message' be null.
166        RefPtr<Uint8Array> nextMessage;
167        // 2.4. Use cdm to handle key.
168        didStoreKey = m_session->update(pendingKey.get(), nextMessage, errorCode, systemCode);
169        // 2.5. If did store key is true and the media element is waiting for a key, queue a task to attempt to resume playback.
170        // TODO: Find and restart the media element
171
172        // 2.6. If next message is not null, queue a task to fire a simple event named keymessage at the MediaKeySession object.
173        //      The event is of type MediaKeyMessageEvent and has:
174        //      message = next message
175        //      destinationURL = null
176        if (nextMessage)
177            sendMessage(nextMessage.get(), emptyString());
178
179        // 2.7. If did store key is true, queue a task to fire a simple event named keyadded at the MediaKeySession object.
180        if (didStoreKey) {
181            RefPtr<Event> keyaddedEvent = Event::create(eventNames().webkitkeyaddedEvent, false, false);
182            keyaddedEvent->setTarget(this);
183            m_asyncEventQueue.enqueueEvent(keyaddedEvent.release());
184        }
185
186        // 2.8. If any of the preceding steps in the task failed
187        if (errorCode) {
188            // 2.8.1. Create a new MediaKeyError object with the following attributes:
189            //        code = the appropriate MediaKeyError code
190            //        systemCode = a Key System-specific value, if provided, and 0 otherwise
191            // 2.8.2. Set the MediaKeySession object's error attribute to the error object created in the previous step.
192            // 2.8.3. queue a task to fire a simple event named keyerror at the MediaKeySession object.
193            sendError(errorCode, systemCode);
194            // 2.8.4. Abort the task.
195            // NOTE: no-op
196        }
197    }
198}
199
200void MediaKeySession::sendMessage(Uint8Array* message, String destinationURL)
201{
202    MediaKeyMessageEventInit init;
203    init.bubbles = false;
204    init.cancelable = false;
205    init.message = message;
206    init.destinationURL = destinationURL;
207    RefPtr<MediaKeyMessageEvent> event = MediaKeyMessageEvent::create(eventNames().webkitkeymessageEvent, init);
208    event->setTarget(this);
209    m_asyncEventQueue.enqueueEvent(event.release());
210}
211
212void MediaKeySession::sendError(CDMSessionClient::MediaKeyErrorCode errorCode, unsigned long systemCode)
213{
214    RefPtr<MediaKeyError> error = MediaKeyError::create(errorCode, systemCode).get();
215    setError(error.get());
216
217    RefPtr<Event> keyerrorEvent = Event::create(eventNames().webkitkeyerrorEvent, false, false);
218    keyerrorEvent->setTarget(this);
219    m_asyncEventQueue.enqueueEvent(keyerrorEvent.release());
220}
221
222}
223
224#endif
225