WeakPtrFactory should populate m_ref lazily.
[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 ENABLE(LEGACY_ENCRYPTED_MEDIA) && ENABLE(MEDIA_SOURCE)
30
31 #import "CDMPrivateMediaSourceAVFObjC.h"
32 #import "FileSystem.h"
33 #import "LegacyCDM.h"
34 #import "Logging.h"
35 #import "MediaPlayer.h"
36 #import "SourceBufferPrivateAVFObjC.h"
37 #import "WebCoreNSErrorExtras.h"
38 #import <AVFoundation/AVError.h>
39 #import <CoreMedia/CMBase.h>
40 #import <objc/objc-runtime.h>
41 #import <pal/spi/mac/AVFoundationSPI.h>
42 #import <runtime/TypedArrayInlines.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(const Vector<int>& protocolVersions, CDMPrivateMediaSourceAVFObjC& cdm, CDMSessionClient* client)
91     : CDMSessionMediaSourceAVFObjC(cdm, client)
92     , m_dataParserObserver(adoptNS([[WebCDMSessionAVStreamSessionObserver alloc] initWithParent:this]))
93     , m_protocolVersions(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(ASCIILiteral("certificate"));
125     RefPtr<Uint8Array> array = Uint8Array::create(certificateString.length());
126     for (unsigned i = 0, length = certificateString.length(); i < length; ++i)
127         array->set(i, certificateString[i]);
128     return 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]).get(), 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 #pragma clang diagnostic push
202 #pragma clang diagnostic ignored "-Wobjc-literal-conversion"
203         String storagePath = this->storagePath();
204         if (!storagePath.isEmpty() && [getAVStreamSessionClass() respondsToSelector:@selector(removePendingExpiredSessionReports:withAppIdentifier:storageDirectoryAtURL:)])
205             [getAVStreamSessionClass() removePendingExpiredSessionReports:@[m_expiredSession.get()] withAppIdentifier:certificateData.get() storageDirectoryAtURL:[NSURL fileURLWithPath:storagePath]];
206 #pragma clang diagnostic pop
207         m_expiredSession = nullptr;
208         return true;
209     }
210
211     if (m_mode == KeyRelease)
212         return false;
213
214     RefPtr<SourceBufferPrivateAVFObjC> protectedSourceBuffer;
215     for (auto& sourceBuffer : m_sourceBuffers) {
216         if (sourceBuffer->protectedTrackID() != -1) {
217             protectedSourceBuffer = sourceBuffer;
218             break;
219         }
220     }
221
222     if (shouldGenerateKeyRequest) {
223         RetainPtr<NSData> certificateData = adoptNS([[NSData alloc] initWithBytes:m_certificate->data() length:m_certificate->length()]);
224
225         if (m_sourceBuffers.isEmpty())
226             return true;
227
228         if (!protectedSourceBuffer)
229             return true;
230
231         RetainPtr<NSData> initData = adoptNS([[NSData alloc] initWithBytes:m_initData->data() length:m_initData->length()]);
232
233         RetainPtr<NSDictionary> options;
234         if (!m_protocolVersions.isEmpty() && canLoadAVStreamDataParserContentKeyRequestProtocolVersionsKey()) {
235             RetainPtr<NSMutableArray> protocolVersionsOption = adoptNS([[NSMutableArray alloc] init]);
236             for (auto& version : m_protocolVersions) {
237                 if (!version)
238                     continue;
239                 [protocolVersionsOption addObject:@(version)];
240             }
241
242             options = @{ getAVStreamDataParserContentKeyRequestProtocolVersionsKey(): protocolVersionsOption.get() };
243         }
244
245         NSError* error = nil;
246 #pragma clang diagnostic push
247 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
248         RetainPtr<NSData> request = [protectedSourceBuffer->parser() streamingContentKeyRequestDataForApp:certificateData.get() contentIdentifier:initData.get() trackID:protectedSourceBuffer->protectedTrackID() options:options.get() error:&error];
249 #pragma clang diagnostic pop
250
251         if (![protectedSourceBuffer->parser() respondsToSelector:@selector(contentProtectionSessionIdentifier)])
252             m_sessionId = createCanonicalUUIDString();
253
254         if (error) {
255             LOG(Media, "CDMSessionAVStreamSession::update(%p) - error:%@", this, [error description]);
256             errorCode = MediaPlayer::InvalidPlayerState;
257             systemCode = mediaKeyErrorSystemCode(error);
258             return false;
259         }
260
261         nextMessage = Uint8Array::create([request length]);
262         [request getBytes:nextMessage->data() length:nextMessage->length()];
263         return false;
264     }
265
266     if (!protectedSourceBuffer) {
267         errorCode = MediaPlayer::InvalidPlayerState;
268         return false;
269     }
270
271     ASSERT(!m_sourceBuffers.isEmpty());
272     LOG(Media, "CDMSessionAVStreamSession::update(%p) - key data", this);
273     errorCode = MediaPlayer::NoError;
274     systemCode = 0;
275     RetainPtr<NSData> keyData = adoptNS([[NSData alloc] initWithBytes:key->data() length:key->length()]);
276 #pragma clang diagnostic push
277 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
278     [protectedSourceBuffer->parser() processContentKeyResponseData:keyData.get() forTrackID:protectedSourceBuffer->protectedTrackID()];
279 #pragma clang diagnostic pop
280
281     return true;
282 }
283
284 void CDMSessionAVStreamSession::setStreamSession(AVStreamSession *streamSession)
285 {
286     if (m_streamSession && canLoadAVStreamSessionContentProtectionSessionIdentifierChangedNotification())
287         [[NSNotificationCenter defaultCenter] removeObserver:m_dataParserObserver.get() name:getAVStreamSessionContentProtectionSessionIdentifierChangedNotification() object:m_streamSession.get()];
288
289     m_streamSession = streamSession;
290
291     if (!m_streamSession)
292         return;
293
294     if (canLoadAVStreamSessionContentProtectionSessionIdentifierChangedNotification())
295         [[NSNotificationCenter defaultCenter] addObserver:m_dataParserObserver.get() selector:@selector(contentProtectionSessionIdentifierChanged:) name:getAVStreamSessionContentProtectionSessionIdentifierChangedNotification() object:m_streamSession.get()];
296
297     NSData* identifier = [streamSession contentProtectionSessionIdentifier];
298     RetainPtr<NSString> sessionIdentifierString = identifier ? adoptNS([[NSString alloc] initWithData:identifier encoding:(NSUTF8StringEncoding)]) : nil;
299     setSessionId(sessionIdentifierString.get());
300 }
301
302 void CDMSessionAVStreamSession::addParser(AVStreamDataParser* parser)
303 {
304     if (m_streamSession)
305         [m_streamSession addStreamDataParser:parser];
306 }
307
308 void CDMSessionAVStreamSession::removeParser(AVStreamDataParser* parser)
309 {
310     if (m_streamSession)
311         [m_streamSession removeStreamDataParser:parser];
312 }
313
314 RefPtr<Uint8Array> CDMSessionAVStreamSession::generateKeyReleaseMessage(unsigned short& errorCode, uint32_t& systemCode)
315 {
316     ASSERT(m_mode == KeyRelease);
317     m_certificate = m_initData;
318     RetainPtr<NSData> certificateData = adoptNS([[NSData alloc] initWithBytes:m_certificate->data() length:m_certificate->length()]);
319
320     String storagePath = this->storagePath();
321     if (storagePath.isEmpty() || ![getAVStreamSessionClass() respondsToSelector:@selector(pendingExpiredSessionReportsWithAppIdentifier:storageDirectoryAtURL:)]) {
322         errorCode = MediaPlayer::KeySystemNotSupported;
323         systemCode = '!mor';
324         return nullptr;
325     }
326
327     NSArray* expiredSessions = [getAVStreamSessionClass() pendingExpiredSessionReportsWithAppIdentifier:certificateData.get() storageDirectoryAtURL:[NSURL fileURLWithPath:storagePath]];
328     if (![expiredSessions count]) {
329         LOG(Media, "CDMSessionAVStreamSession::generateKeyReleaseMessage(%p) - no expired sessions found", this);
330
331         errorCode = MediaPlayer::KeySystemNotSupported;
332         systemCode = '!mor';
333         return nullptr;
334     }
335
336     LOG(Media, "CDMSessionAVStreamSession::generateKeyReleaseMessage(%p) - found %d expired sessions", this, [expiredSessions count]);
337
338     errorCode = 0;
339     systemCode = 0;
340     m_expiredSession = [expiredSessions firstObject];
341     return Uint8Array::create(static_cast<const uint8_t*>([m_expiredSession bytes]), [m_expiredSession length]);
342 }
343
344 }
345
346 #endif