Unreviewed, rolling out r244627.
[WebKit-https.git] / Source / WebCore / platform / graphics / avfoundation / objc / CDMSessionAVContentKeySession.mm
1 /*
2  * Copyright (C) 2015 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 #import "config.h"
27 #import "CDMSessionAVContentKeySession.h"
28
29 #if ENABLE(LEGACY_ENCRYPTED_MEDIA) && ENABLE(MEDIA_SOURCE)
30
31 #import "CDMPrivateMediaSourceAVFObjC.h"
32 #import "LegacyCDM.h"
33 #import "Logging.h"
34 #import "MediaPlayer.h"
35 #import "SourceBufferPrivateAVFObjC.h"
36 #import "WebCoreNSErrorExtras.h"
37 #import <AVFoundation/AVError.h>
38 #import <CoreMedia/CMBase.h>
39 #import <JavaScriptCore/TypedArrayInlines.h>
40 #import <objc/objc-runtime.h>
41 #import <pal/spi/mac/AVFoundationSPI.h>
42 #import <wtf/FileSystem.h>
43 #import <wtf/SoftLinking.h>
44
45 SOFT_LINK_FRAMEWORK_OPTIONAL(AVFoundation)
46 SOFT_LINK_CLASS(AVFoundation, AVStreamDataParser);
47 SOFT_LINK_CLASS_OPTIONAL(AVFoundation, AVContentKeySession);
48 SOFT_LINK_CLASS_OPTIONAL(AVFoundation, AVContentKeyResponse);
49 SOFT_LINK_CONSTANT_MAY_FAIL(AVFoundation, AVContentKeyRequestProtocolVersionsKey, NSString *)
50 SOFT_LINK_CONSTANT_MAY_FAIL(AVFoundation, AVContentKeySystemFairPlayStreaming, NSString *)
51
52 typedef NSString *AVContentKeySystem;
53
54 @interface AVContentKeySession (WebCorePrivate)
55 - (instancetype)initWithStorageDirectoryAtURL:(NSURL *)storageURL;
56 @property (assign) id delegate;
57 - (void)addStreamDataParser:(AVStreamDataParser *)streamDataParser;
58 - (void)removeStreamDataParser:(AVStreamDataParser *)streamDataParser;
59 - (void)processContentKeyRequestInitializationData:(NSData *)initializationData options:(NSDictionary *)options;
60 @end
61
62 @interface AVContentKeyRequest (WebCorePrivate)
63 - (NSData *)contentKeyRequestDataForApp:(NSData *)appIdentifier contentIdentifier:(NSData *)contentIdentifier options:(NSDictionary *)options error:(NSError **)outError;
64 - (void)processContentKeyResponseData:(NSData *)contentKeyResponseData;
65 - (void)renewExpiringContentKeyResponseData;
66 @end
67
68 @interface WebCDMSessionAVContentKeySessionDelegate : NSObject<AVContentKeySessionDelegate> {
69     WebCore::CDMSessionAVContentKeySession *m_parent;
70 }
71 - (void)invalidate;
72 @end
73
74 @implementation WebCDMSessionAVContentKeySessionDelegate
75 - (id)initWithParent:(WebCore::CDMSessionAVContentKeySession *)parent
76 {
77     if ((self = [super init]))
78         m_parent = parent;
79     return self;
80 }
81
82
83 - (void)invalidate
84 {
85     m_parent = nullptr;
86 }
87
88 - (void)contentKeySession:(AVContentKeySession *)session didProvideContentKeyRequest:(AVContentKeyRequest *)keyRequest
89 {
90     UNUSED_PARAM(session);
91
92     if (m_parent)
93         m_parent->didProvideContentKeyRequest(keyRequest);
94 }
95
96 - (void)contentKeySessionContentProtectionSessionIdentifierDidChange:(AVContentKeySession *)session
97 {
98     if (!m_parent)
99         return;
100
101     NSData* identifier = [session contentProtectionSessionIdentifier];
102     RetainPtr<NSString> sessionIdentifierString = identifier ? adoptNS([[NSString alloc] initWithData:identifier encoding:NSUTF8StringEncoding]) : nil;
103     m_parent->setSessionId(sessionIdentifierString.get());
104 }
105 @end
106
107 static const NSString *PlaybackSessionIdKey = @"PlaybackSessionID";
108
109 namespace WebCore {
110
111 CDMSessionAVContentKeySession::CDMSessionAVContentKeySession(Vector<int>&& protocolVersions, int cdmVersion, CDMPrivateMediaSourceAVFObjC& cdm, LegacyCDMSessionClient* client)
112     : CDMSessionMediaSourceAVFObjC(cdm, client)
113     , m_contentKeySessionDelegate(adoptNS([[WebCDMSessionAVContentKeySessionDelegate alloc] initWithParent:this]))
114     , m_protocolVersions(WTFMove(protocolVersions))
115     , m_cdmVersion(cdmVersion)
116     , m_mode(Normal)
117 {
118 }
119
120 CDMSessionAVContentKeySession::~CDMSessionAVContentKeySession()
121 {
122     [m_contentKeySessionDelegate invalidate];
123
124     for (auto& sourceBuffer : m_sourceBuffers)
125         removeParser(sourceBuffer->parser());
126 }
127
128 bool CDMSessionAVContentKeySession::isAvailable()
129 {
130     return getAVContentKeySessionClass();
131 }
132
133 RefPtr<Uint8Array> CDMSessionAVContentKeySession::generateKeyRequest(const String& mimeType, Uint8Array* initData, String& destinationURL, unsigned short& errorCode, uint32_t& systemCode)
134 {
135     UNUSED_PARAM(mimeType);
136     UNUSED_PARAM(destinationURL);
137     ASSERT(initData);
138
139     LOG(Media, "CDMSessionAVContentKeySession::generateKeyRequest(%p)", this);
140
141     errorCode = MediaPlayer::NoError;
142     systemCode = 0;
143
144     if (equalLettersIgnoringASCIICase(mimeType, "keyrelease")) {
145         m_mode = KeyRelease;
146         m_certificate = initData;
147         return generateKeyReleaseMessage(errorCode, systemCode);
148     }
149
150     if (m_cdmVersion == 2)
151         m_identifier = initData;
152     else
153         m_initData = initData;
154
155     ASSERT(!m_certificate);
156     String certificateString("certificate"_s);
157     auto array = Uint8Array::create(certificateString.length());
158     for (unsigned i = 0, length = certificateString.length(); i < length; ++i)
159         array->set(i, certificateString[i]);
160     return WTFMove(array);
161 }
162
163 void CDMSessionAVContentKeySession::releaseKeys()
164 {
165     if (hasContentKeySession()) {
166         m_stopped = true;
167         for (auto& sourceBuffer : m_sourceBuffers)
168             sourceBuffer->flush();
169
170         LOG(Media, "CDMSessionAVContentKeySession::releaseKeys(%p) - expiring stream session", this);
171         [contentKeySession() expire];
172
173         if (!m_certificate)
174             return;
175
176         if (![getAVContentKeySessionClass() respondsToSelector:@selector(pendingExpiredSessionReportsWithAppIdentifier:storageDirectoryAtURL:)])
177             return;
178
179         auto storagePath = this->storagePath();
180         if (storagePath.isEmpty())
181             return;
182
183         RetainPtr<NSData> certificateData = adoptNS([[NSData alloc] initWithBytes:m_certificate->data() length:m_certificate->length()]);
184         NSArray* expiredSessions = [getAVContentKeySessionClass() pendingExpiredSessionReportsWithAppIdentifier:certificateData.get() storageDirectoryAtURL:[NSURL fileURLWithPath:storagePath]];
185         for (NSData* expiredSessionData in expiredSessions) {
186             NSDictionary *expiredSession = [NSPropertyListSerialization propertyListWithData:expiredSessionData options:kCFPropertyListImmutable format:nullptr error:nullptr];
187             NSString *playbackSessionIdValue = (NSString *)[expiredSession objectForKey:PlaybackSessionIdKey];
188             if (![playbackSessionIdValue isKindOfClass:[NSString class]])
189                 continue;
190
191             if (m_sessionId == String(playbackSessionIdValue)) {
192                 LOG(Media, "CDMSessionAVContentKeySession::releaseKeys(%p) - found session, sending expiration message");
193                 m_expiredSession = expiredSessionData;
194                 m_client->sendMessage(Uint8Array::create(static_cast<const uint8_t*>([m_expiredSession bytes]), [m_expiredSession length]).ptr(), emptyString());
195                 break;
196             }
197         }
198     }
199 }
200
201 static bool isEqual(Uint8Array* data, const char* literal)
202 {
203     ASSERT(data);
204     ASSERT(literal);
205     unsigned length = data->length();
206
207     for (unsigned i = 0; i < length; ++i) {
208         if (!literal[i])
209             return false;
210
211         if (data->item(i) != static_cast<uint8_t>(literal[i]))
212             return false;
213     }
214     return !literal[length];
215 }
216
217 bool CDMSessionAVContentKeySession::update(Uint8Array* key, RefPtr<Uint8Array>& nextMessage, unsigned short& errorCode, uint32_t& systemCode)
218 {
219     UNUSED_PARAM(nextMessage);
220
221     if (isEqual(key, "acknowledged")) {
222         LOG(Media, "CDMSessionAVContentKeySession::update(%p) - acknowleding secure stop message", this);
223
224         String storagePath = this->storagePath();
225         if (!m_expiredSession || storagePath.isEmpty()) {
226             errorCode = MediaPlayer::InvalidPlayerState;
227             return false;
228         }
229
230         RetainPtr<NSData> certificateData = adoptNS([[NSData alloc] initWithBytes:m_certificate->data() length:m_certificate->length()]);
231
232         if ([getAVContentKeySessionClass() respondsToSelector:@selector(removePendingExpiredSessionReports:withAppIdentifier:storageDirectoryAtURL:)])
233             [getAVContentKeySessionClass() removePendingExpiredSessionReports:@[m_expiredSession.get()] withAppIdentifier:certificateData.get() storageDirectoryAtURL:[NSURL fileURLWithPath:storagePath]];
234         m_expiredSession = nullptr;
235         return true;
236     }
237
238     if (m_stopped) {
239         errorCode = MediaPlayer::InvalidPlayerState;
240         return false;
241     }
242
243     bool shouldGenerateKeyRequest = !m_certificate || isEqual(key, "renew");
244     if (!m_certificate) {
245         LOG(Media, "CDMSessionAVContentKeySession::update(%p) - certificate data", this);
246
247         m_certificate = key;
248     }
249
250     if (m_mode == KeyRelease)
251         return false;
252     
253     if (m_cdmVersion == 2) {
254         // In the com.apple.fps.2_0 communication protocol, the client must first attach the
255         // session to the protected SourceBuffer in order to get access to the initialization
256         // data.
257         RefPtr<SourceBufferPrivateAVFObjC> protectedSourceBuffer;
258         for (auto& sourceBuffer : m_sourceBuffers) {
259             if (sourceBuffer->protectedTrackID() != -1) {
260                 protectedSourceBuffer = sourceBuffer;
261                 break;
262             }
263         }
264
265         if (!protectedSourceBuffer) {
266             errorCode = MediaPlayer::InvalidPlayerState;
267             return false;
268         }
269         
270         m_initData = protectedSourceBuffer->initData();
271     }
272
273     if (!m_keyRequest) {
274         NSData* nsInitData = m_initData ? [NSData dataWithBytes:m_initData->data() length:m_initData->length()] : nil;
275         NSData* nsIdentifier = m_identifier ? [NSData dataWithBytes:m_identifier->data() length:m_identifier->length()] : nil;
276         if ([contentKeySession() respondsToSelector:@selector(processContentKeyRequestWithIdentifier:initializationData:options:)])
277             [contentKeySession() processContentKeyRequestWithIdentifier:nsIdentifier initializationData:nsInitData options:nil];
278         else
279             [contentKeySession() processContentKeyRequestInitializationData:nsInitData options:nil];
280     }
281
282     if (shouldGenerateKeyRequest) {
283         ASSERT(m_keyRequest);
284         RetainPtr<NSData> certificateData = adoptNS([[NSData alloc] initWithBytes:m_certificate->data() length:m_certificate->length()]);
285
286         RetainPtr<NSDictionary> options;
287         if (!m_protocolVersions.isEmpty() && canLoadAVContentKeyRequestProtocolVersionsKey()) {
288             RetainPtr<NSMutableArray> protocolVersionsOption = adoptNS([[NSMutableArray alloc] init]);
289             for (auto& version : m_protocolVersions) {
290                 if (!version)
291                     continue;
292                 [protocolVersionsOption addObject:@(version)];
293             }
294
295             options = @{ getAVContentKeyRequestProtocolVersionsKey(): protocolVersionsOption.get() };
296         }
297
298         errorCode = MediaPlayer::NoError;
299         systemCode = 0;
300         NSError* error = nil;
301         NSData* nsIdentifier = m_identifier ? [NSData dataWithBytes:m_identifier->data() length:m_identifier->length()] : m_keyRequest.get().identifier;
302
303         NSData* requestData = [m_keyRequest contentKeyRequestDataForApp:certificateData.get() contentIdentifier:nsIdentifier options:options.get() error:&error];
304         if (error) {
305             errorCode = LegacyCDM::DomainError;
306             systemCode = mediaKeyErrorSystemCode(error);
307             return false;
308         }
309
310         nextMessage = Uint8Array::tryCreate(static_cast<const uint8_t*>([requestData bytes]), [requestData length]);
311         return false;
312     }
313
314     LOG(Media, "CDMSessionAVContentKeySession::update(%p) - key data", this);
315     errorCode = MediaPlayer::NoError;
316     systemCode = 0;
317     RetainPtr<NSData> keyData = adoptNS([[NSData alloc] initWithBytes:key->data() length:key->length()]);
318     
319     if ([m_keyRequest respondsToSelector:@selector(processContentKeyResponse:)] && [getAVContentKeyResponseClass() respondsToSelector:@selector(contentKeyResponseWithFairPlayStreamingKeyResponseData:)])
320         [m_keyRequest processContentKeyResponse:[getAVContentKeyResponseClass() contentKeyResponseWithFairPlayStreamingKeyResponseData:keyData.get()]];
321     else
322         [m_keyRequest processContentKeyResponseData:keyData.get()];
323
324     return true;
325 }
326
327 void CDMSessionAVContentKeySession::addParser(AVStreamDataParser* parser)
328 {
329     if ([contentKeySession() respondsToSelector:@selector(addContentKeyRecipient:)])
330         [contentKeySession() addContentKeyRecipient:parser];
331     else
332         [contentKeySession() addStreamDataParser:parser];
333 }
334
335 void CDMSessionAVContentKeySession::removeParser(AVStreamDataParser* parser)
336 {
337     if ([contentKeySession() respondsToSelector:@selector(removeContentKeyRecipient:)])
338         [contentKeySession() removeContentKeyRecipient:parser];
339     else
340         [contentKeySession() removeStreamDataParser:parser];
341 }
342
343 RefPtr<Uint8Array> CDMSessionAVContentKeySession::generateKeyReleaseMessage(unsigned short& errorCode, uint32_t& systemCode)
344 {
345     ASSERT(m_mode == KeyRelease);
346     RetainPtr<NSData> certificateData = adoptNS([[NSData alloc] initWithBytes:m_certificate->data() length:m_certificate->length()]);
347
348     String storagePath = this->storagePath();
349     if (storagePath.isEmpty() || ![getAVContentKeySessionClass() respondsToSelector:@selector(pendingExpiredSessionReportsWithAppIdentifier:storageDirectoryAtURL:)]) {
350         errorCode = MediaPlayer::KeySystemNotSupported;
351         systemCode = '!mor';
352         return nullptr;
353     }
354
355     NSArray* expiredSessions = [getAVContentKeySessionClass() pendingExpiredSessionReportsWithAppIdentifier:certificateData.get() storageDirectoryAtURL:[NSURL fileURLWithPath:storagePath]];
356     if (![expiredSessions count]) {
357         LOG(Media, "CDMSessionAVContentKeySession::generateKeyReleaseMessage(%p) - no expired sessions found", this);
358
359         errorCode = MediaPlayer::KeySystemNotSupported;
360         systemCode = '!mor';
361         return nullptr;
362     }
363
364     LOG(Media, "CDMSessionAVContentKeySession::generateKeyReleaseMessage(%p) - found %d expired sessions", this, [expiredSessions count]);
365
366     errorCode = 0;
367     systemCode = 0;
368     m_expiredSession = [expiredSessions firstObject];
369     return Uint8Array::tryCreate(static_cast<const uint8_t*>([m_expiredSession bytes]), [m_expiredSession length]);
370 }
371
372 void CDMSessionAVContentKeySession::didProvideContentKeyRequest(AVContentKeyRequest *keyRequest)
373 {
374     m_keyRequest = keyRequest;
375 }
376
377 AVContentKeySession* CDMSessionAVContentKeySession::contentKeySession()
378 {
379     if (m_contentKeySession)
380         return m_contentKeySession.get();
381
382     String storagePath = this->storagePath();
383     if (storagePath.isEmpty()) {
384         if (![getAVContentKeySessionClass() respondsToSelector:@selector(contentKeySessionWithKeySystem:)] || !canLoadAVContentKeySystemFairPlayStreaming())
385             return nil;
386
387         m_contentKeySession = [getAVContentKeySessionClass() contentKeySessionWithKeySystem:getAVContentKeySystemFairPlayStreaming()];
388     } else {
389         String storageDirectory = FileSystem::directoryName(storagePath);
390
391         if (!FileSystem::fileExists(storageDirectory)) {
392             if (!FileSystem::makeAllDirectories(storageDirectory))
393                 return nil;
394         }
395
396         auto url = [NSURL fileURLWithPath:storagePath];
397         if ([getAVContentKeySessionClass() respondsToSelector:@selector(contentKeySessionWithKeySystem:storageDirectoryAtURL:)] && canLoadAVContentKeySystemFairPlayStreaming())
398             m_contentKeySession = [getAVContentKeySessionClass() contentKeySessionWithKeySystem:getAVContentKeySystemFairPlayStreaming() storageDirectoryAtURL:url];
399         else
400             m_contentKeySession = adoptNS([allocAVContentKeySessionInstance() initWithStorageDirectoryAtURL:url]);
401     }
402
403     m_contentKeySession.get().delegate = m_contentKeySessionDelegate.get();
404     return m_contentKeySession.get();
405 }
406
407 }
408
409 #endif