Unreviewed, rolling out r244627.
[WebKit-https.git] / Source / WebCore / platform / graphics / avfoundation / objc / CDMSessionAVStreamSession.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 "CDMSessionAVStreamSession.h"
28
29 #if HAVE(AVSTREAMSESSION) && 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 #import <wtf/UUID.h>
45
46 SOFT_LINK_FRAMEWORK_OPTIONAL(AVFoundation)
47 SOFT_LINK_CLASS(AVFoundation, AVStreamDataParser);
48 SOFT_LINK_CLASS_OPTIONAL(AVFoundation, AVStreamSession);
49 SOFT_LINK_CONSTANT_MAY_FAIL(AVFoundation, AVStreamDataParserContentKeyRequestProtocolVersionsKey, NSString *)
50 SOFT_LINK_CONSTANT_MAY_FAIL(AVFoundation, AVStreamSessionContentProtectionSessionIdentifierChangedNotification, NSString *)
51
52 @interface AVStreamSession : NSObject
53 - (void)addStreamDataParser:(AVStreamDataParser *)streamDataParser;
54 - (void)removeStreamDataParser:(AVStreamDataParser *)streamDataParser;
55 - (void)expire;
56 - (NSData *)contentProtectionSessionIdentifier;
57 + (NSArray *)pendingExpiredSessionReportsWithAppIdentifier:(NSData *)appIdentifier storageDirectoryAtURL:(NSURL *)storageURL;
58 + (void)removePendingExpiredSessionReports:(NSArray *)expiredSessionReports withAppIdentifier:(NSData *)appIdentifier storageDirectoryAtURL:(NSURL *)storageURL;
59 @end
60
61 @interface WebCDMSessionAVStreamSessionObserver : NSObject {
62     WebCore::CDMSessionAVStreamSession *m_parent;
63 }
64 @end
65
66 @implementation WebCDMSessionAVStreamSessionObserver
67 - (id)initWithParent:(WebCore::CDMSessionAVStreamSession *)parent
68 {
69     if ((self = [super init]))
70         m_parent = parent;
71     return self;
72 }
73
74 - (void)contentProtectionSessionIdentifierChanged:(NSNotification *)notification
75 {
76     AVStreamSession* streamSession = (AVStreamSession*)[notification object];
77
78     NSData* identifier = [streamSession contentProtectionSessionIdentifier];
79     RetainPtr<NSString> sessionIdentifierString = identifier ? adoptNS([[NSString alloc] initWithData:identifier encoding:NSUTF8StringEncoding]) : nil;
80
81     if (m_parent)
82         m_parent->setSessionId(sessionIdentifierString.get());
83 }
84 @end
85
86 static const NSString *PlaybackSessionIdKey = @"PlaybackSessionID";
87
88 namespace WebCore {
89
90 CDMSessionAVStreamSession::CDMSessionAVStreamSession(Vector<int>&& protocolVersions, CDMPrivateMediaSourceAVFObjC& cdm, LegacyCDMSessionClient* client)
91     : CDMSessionMediaSourceAVFObjC(cdm, client)
92     , m_dataParserObserver(adoptNS([[WebCDMSessionAVStreamSessionObserver alloc] initWithParent:this]))
93     , m_protocolVersions(WTFMove(protocolVersions))
94     , m_mode(Normal)
95 {
96 }
97
98 CDMSessionAVStreamSession::~CDMSessionAVStreamSession()
99 {
100     setStreamSession(nullptr);
101
102     for (auto& sourceBuffer : m_sourceBuffers)
103         removeParser(sourceBuffer->parser());
104 }
105
106 RefPtr<Uint8Array> CDMSessionAVStreamSession::generateKeyRequest(const String& mimeType, Uint8Array* initData, String& destinationURL, unsigned short& errorCode, uint32_t& systemCode)
107 {
108     UNUSED_PARAM(mimeType);
109     UNUSED_PARAM(destinationURL);
110     ASSERT(initData);
111
112     LOG(Media, "CDMSessionAVStreamSession::generateKeyRequest(%p)", this);
113
114     errorCode = MediaPlayer::NoError;
115     systemCode = 0;
116
117     m_initData = initData;
118
119     if (equalLettersIgnoringASCIICase(mimeType, "keyrelease")) {
120         m_mode = KeyRelease;
121         return generateKeyReleaseMessage(errorCode, systemCode);
122     }
123
124     String certificateString("certificate"_s);
125     auto array = Uint8Array::create(certificateString.length());
126     for (unsigned i = 0, length = certificateString.length(); i < length; ++i)
127         array->set(i, certificateString[i]);
128     return WTFMove(array);
129 }
130
131 void CDMSessionAVStreamSession::releaseKeys()
132 {
133     if (m_streamSession) {
134         m_stopped = true;
135         for (auto& sourceBuffer : m_sourceBuffers)
136             sourceBuffer->flush();
137
138         LOG(Media, "CDMSessionAVStreamSession::releaseKeys(%p) - expiring stream session", this);
139         [m_streamSession expire];
140
141         if (!m_certificate)
142             return;
143
144         String storagePath = this->storagePath();
145         if (storagePath.isEmpty() || ![getAVStreamSessionClass() respondsToSelector:@selector(pendingExpiredSessionReportsWithAppIdentifier:storageDirectoryAtURL:)])
146             return;
147
148         RetainPtr<NSData> certificateData = adoptNS([[NSData alloc] initWithBytes:m_certificate->data() length:m_certificate->length()]);
149         NSArray* expiredSessions = [getAVStreamSessionClass() pendingExpiredSessionReportsWithAppIdentifier:certificateData.get() storageDirectoryAtURL:[NSURL fileURLWithPath:storagePath]];
150         for (NSData* expiredSessionData in expiredSessions) {
151             NSDictionary *expiredSession = [NSPropertyListSerialization propertyListWithData:expiredSessionData options:kCFPropertyListImmutable format:nullptr error:nullptr];
152             NSString *playbackSessionIdValue = (NSString *)[expiredSession objectForKey:PlaybackSessionIdKey];
153             if (![playbackSessionIdValue isKindOfClass:[NSString class]])
154                 continue;
155
156             if (m_sessionId == String(playbackSessionIdValue)) {
157                 LOG(Media, "CDMSessionAVStreamSession::releaseKeys(%p) - found session, sending expiration message");
158                 m_expiredSession = expiredSessionData;
159                 m_client->sendMessage(Uint8Array::create(static_cast<const uint8_t*>([m_expiredSession bytes]), [m_expiredSession length]).ptr(), emptyString());
160                 break;
161             }
162         }
163     }
164 }
165
166 static bool isEqual(Uint8Array* data, const char* literal)
167 {
168     ASSERT(data);
169     ASSERT(literal);
170     unsigned length = data->length();
171
172     for (unsigned i = 0; i < length; ++i) {
173         if (!literal[i])
174             return false;
175
176         if (data->item(i) != static_cast<uint8_t>(literal[i]))
177             return false;
178     }
179     return !literal[length];
180 }
181
182 bool CDMSessionAVStreamSession::update(Uint8Array* key, RefPtr<Uint8Array>& nextMessage, unsigned short& errorCode, uint32_t& systemCode)
183 {
184     bool shouldGenerateKeyRequest = !m_certificate || isEqual(key, "renew");
185     if (!m_certificate) {
186         LOG(Media, "CDMSessionAVStreamSession::update(%p) - certificate data", this);
187
188         m_certificate = key;
189     }
190
191     if (isEqual(key, "acknowledged")) {
192         LOG(Media, "CDMSessionAVStreamSession::update(%p) - acknowleding secure stop message", this);
193
194         if (!m_expiredSession) {
195             errorCode = MediaPlayer::InvalidPlayerState;
196             return false;
197         }
198
199         RetainPtr<NSData> certificateData = adoptNS([[NSData alloc] initWithBytes:m_certificate->data() length:m_certificate->length()]);
200
201         IGNORE_WARNINGS_BEGIN("objc-literal-conversion")
202         String storagePath = this->storagePath();
203         if (!storagePath.isEmpty() && [getAVStreamSessionClass() respondsToSelector:@selector(removePendingExpiredSessionReports:withAppIdentifier:storageDirectoryAtURL:)])
204             [getAVStreamSessionClass() removePendingExpiredSessionReports:@[m_expiredSession.get()] withAppIdentifier:certificateData.get() storageDirectoryAtURL:[NSURL fileURLWithPath:storagePath]];
205         IGNORE_WARNINGS_END
206         m_expiredSession = nullptr;
207         return true;
208     }
209
210     if (m_mode == KeyRelease)
211         return false;
212
213     RefPtr<SourceBufferPrivateAVFObjC> protectedSourceBuffer;
214     for (auto& sourceBuffer : m_sourceBuffers) {
215         if (sourceBuffer->protectedTrackID() != -1) {
216             protectedSourceBuffer = sourceBuffer;
217             break;
218         }
219     }
220
221     if (shouldGenerateKeyRequest) {
222         RetainPtr<NSData> certificateData = adoptNS([[NSData alloc] initWithBytes:m_certificate->data() length:m_certificate->length()]);
223
224         if (m_sourceBuffers.isEmpty())
225             return true;
226
227         if (!protectedSourceBuffer)
228             return true;
229
230         RetainPtr<NSData> initData = adoptNS([[NSData alloc] initWithBytes:m_initData->data() length:m_initData->length()]);
231
232         RetainPtr<NSDictionary> options;
233         if (!m_protocolVersions.isEmpty() && canLoadAVStreamDataParserContentKeyRequestProtocolVersionsKey()) {
234             RetainPtr<NSMutableArray> protocolVersionsOption = adoptNS([[NSMutableArray alloc] init]);
235             for (auto& version : m_protocolVersions) {
236                 if (!version)
237                     continue;
238                 [protocolVersionsOption addObject:@(version)];
239             }
240
241             options = @{ getAVStreamDataParserContentKeyRequestProtocolVersionsKey(): protocolVersionsOption.get() };
242         }
243
244         NSError* error = nil;
245         ALLOW_DEPRECATED_DECLARATIONS_BEGIN
246         RetainPtr<NSData> request = [protectedSourceBuffer->parser() streamingContentKeyRequestDataForApp:certificateData.get() contentIdentifier:initData.get() trackID:protectedSourceBuffer->protectedTrackID() options:options.get() error:&error];
247         ALLOW_DEPRECATED_DECLARATIONS_END
248
249         if (![protectedSourceBuffer->parser() respondsToSelector:@selector(contentProtectionSessionIdentifier)])
250             m_sessionId = createCanonicalUUIDString();
251
252         if (error) {
253             LOG(Media, "CDMSessionAVStreamSession::update(%p) - error:%@", this, [error description]);
254             errorCode = MediaPlayer::InvalidPlayerState;
255             systemCode = mediaKeyErrorSystemCode(error);
256             return false;
257         }
258
259         nextMessage = Uint8Array::create([request length]);
260         [request getBytes:nextMessage->data() length:nextMessage->length()];
261         return false;
262     }
263
264     if (!protectedSourceBuffer) {
265         errorCode = MediaPlayer::InvalidPlayerState;
266         return false;
267     }
268
269     ASSERT(!m_sourceBuffers.isEmpty());
270     LOG(Media, "CDMSessionAVStreamSession::update(%p) - key data", this);
271     errorCode = MediaPlayer::NoError;
272     systemCode = 0;
273     RetainPtr<NSData> keyData = adoptNS([[NSData alloc] initWithBytes:key->data() length:key->length()]);
274     ALLOW_DEPRECATED_DECLARATIONS_BEGIN
275     [protectedSourceBuffer->parser() processContentKeyResponseData:keyData.get() forTrackID:protectedSourceBuffer->protectedTrackID()];
276     ALLOW_DEPRECATED_DECLARATIONS_END
277
278     return true;
279 }
280
281 void CDMSessionAVStreamSession::setStreamSession(AVStreamSession *streamSession)
282 {
283     if (m_streamSession && canLoadAVStreamSessionContentProtectionSessionIdentifierChangedNotification())
284         [[NSNotificationCenter defaultCenter] removeObserver:m_dataParserObserver.get() name:getAVStreamSessionContentProtectionSessionIdentifierChangedNotification() object:m_streamSession.get()];
285
286     m_streamSession = streamSession;
287
288     if (!m_streamSession)
289         return;
290
291     if (canLoadAVStreamSessionContentProtectionSessionIdentifierChangedNotification())
292         [[NSNotificationCenter defaultCenter] addObserver:m_dataParserObserver.get() selector:@selector(contentProtectionSessionIdentifierChanged:) name:getAVStreamSessionContentProtectionSessionIdentifierChangedNotification() object:m_streamSession.get()];
293
294     NSData* identifier = [streamSession contentProtectionSessionIdentifier];
295     RetainPtr<NSString> sessionIdentifierString = identifier ? adoptNS([[NSString alloc] initWithData:identifier encoding:(NSUTF8StringEncoding)]) : nil;
296     setSessionId(sessionIdentifierString.get());
297 }
298
299 void CDMSessionAVStreamSession::addParser(AVStreamDataParser* parser)
300 {
301     if (m_streamSession)
302         [m_streamSession addStreamDataParser:parser];
303 }
304
305 void CDMSessionAVStreamSession::removeParser(AVStreamDataParser* parser)
306 {
307     if (m_streamSession)
308         [m_streamSession removeStreamDataParser:parser];
309 }
310
311 RefPtr<Uint8Array> CDMSessionAVStreamSession::generateKeyReleaseMessage(unsigned short& errorCode, uint32_t& systemCode)
312 {
313     ASSERT(m_mode == KeyRelease);
314     m_certificate = m_initData;
315     RetainPtr<NSData> certificateData = adoptNS([[NSData alloc] initWithBytes:m_certificate->data() length:m_certificate->length()]);
316
317     String storagePath = this->storagePath();
318     if (storagePath.isEmpty() || ![getAVStreamSessionClass() respondsToSelector:@selector(pendingExpiredSessionReportsWithAppIdentifier:storageDirectoryAtURL:)]) {
319         errorCode = MediaPlayer::KeySystemNotSupported;
320         systemCode = '!mor';
321         return nullptr;
322     }
323
324     NSArray* expiredSessions = [getAVStreamSessionClass() pendingExpiredSessionReportsWithAppIdentifier:certificateData.get() storageDirectoryAtURL:[NSURL fileURLWithPath:storagePath]];
325     if (![expiredSessions count]) {
326         LOG(Media, "CDMSessionAVStreamSession::generateKeyReleaseMessage(%p) - no expired sessions found", this);
327
328         errorCode = MediaPlayer::KeySystemNotSupported;
329         systemCode = '!mor';
330         return nullptr;
331     }
332
333     LOG(Media, "CDMSessionAVStreamSession::generateKeyReleaseMessage(%p) - found %d expired sessions", this, [expiredSessions count]);
334
335     errorCode = 0;
336     systemCode = 0;
337     m_expiredSession = [expiredSessions firstObject];
338     return Uint8Array::tryCreate(static_cast<const uint8_t*>([m_expiredSession bytes]), [m_expiredSession length]);
339 }
340
341 }
342
343 #endif