Rename AtomicString to AtomString
[WebKit-https.git] / Source / WebCore / platform / graphics / avfoundation / objc / CDMInstanceFairPlayStreamingAVFObjC.mm
1 /*
2  * Copyright (C) 2017 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 "CDMInstanceFairPlayStreamingAVFObjC.h"
28
29 #if ENABLE(ENCRYPTED_MEDIA) && HAVE(AVCONTENTKEYSESSION)
30
31 #import "CDMFairPlayStreaming.h"
32 #import "CDMKeySystemConfiguration.h"
33 #import "CDMMediaCapability.h"
34 #import "NotImplemented.h"
35 #import "SharedBuffer.h"
36 #import "TextDecoder.h"
37 #import <AVFoundation/AVContentKeySession.h>
38 #import <objc/runtime.h>
39 #import <pal/spi/mac/AVFoundationSPI.h>
40 #import <wtf/Algorithms.h>
41 #import <wtf/FileSystem.h>
42 #import <wtf/text/StringHash.h>
43
44 #import <pal/cocoa/AVFoundationSoftLink.h>
45
46 static const NSString *PlaybackSessionIdKey = @"PlaybackSessionID";
47
48 @interface WebCoreFPSContentKeySessionDelegate : NSObject<AVContentKeySessionDelegate> {
49     WebCore::CDMInstanceSessionFairPlayStreamingAVFObjC* _parent;
50 }
51 @end
52
53 @implementation WebCoreFPSContentKeySessionDelegate
54 - (id)initWithParent:(WebCore::CDMInstanceSessionFairPlayStreamingAVFObjC *)parent
55 {
56     if (!(self = [super init]))
57         return nil;
58
59     _parent = parent;
60     return self;
61 }
62
63 - (void)invalidate
64 {
65     _parent = nil;
66 }
67
68 - (void)contentKeySession:(AVContentKeySession *)session didProvideContentKeyRequest:(AVContentKeyRequest *)keyRequest
69 {
70     UNUSED_PARAM(session);
71     if (_parent)
72         _parent->didProvideRequest(keyRequest);
73 }
74
75 - (void)contentKeySession:(AVContentKeySession *)session didProvideRenewingContentKeyRequest:(AVContentKeyRequest *)keyRequest
76 {
77     UNUSED_PARAM(session);
78     if (_parent)
79         _parent->didProvideRenewingRequest(keyRequest);
80 }
81
82 #if PLATFORM(IOS_FAMILY)
83 - (void)contentKeySession:(AVContentKeySession *)session didProvidePersistableContentKeyRequest:(AVPersistableContentKeyRequest *)keyRequest
84 {
85     UNUSED_PARAM(session);
86     if (_parent)
87         _parent->didProvidePersistableRequest(keyRequest);
88 }
89
90 - (void)contentKeySession:(AVContentKeySession *)session didUpdatePersistableContentKey:(NSData *)persistableContentKey forContentKeyIdentifier:(id)keyIdentifier
91 {
92     UNUSED_PARAM(session);
93     UNUSED_PARAM(persistableContentKey);
94     UNUSED_PARAM(keyIdentifier);
95     notImplemented();
96 }
97 #endif
98
99 - (void)contentKeySession:(AVContentKeySession *)session contentKeyRequest:(AVContentKeyRequest *)keyRequest didFailWithError:(NSError *)err
100 {
101     UNUSED_PARAM(session);
102     if (_parent)
103         _parent->didFailToProvideRequest(keyRequest, err);
104 }
105
106 - (void)contentKeySession:(AVContentKeySession *)session contentKeyRequestDidSucceed:(AVContentKeyRequest *)keyRequest
107 {
108     UNUSED_PARAM(session);
109     if (_parent)
110         _parent->requestDidSucceed(keyRequest);
111 }
112
113 - (BOOL)contentKeySession:(AVContentKeySession *)session shouldRetryContentKeyRequest:(AVContentKeyRequest *)keyRequest reason:(AVContentKeyRequestRetryReason)retryReason
114 {
115     UNUSED_PARAM(session);
116     return _parent ? _parent->shouldRetryRequestForReason(keyRequest, retryReason) : false;
117 }
118
119 - (void)contentKeySessionContentProtectionSessionIdentifierDidChange:(AVContentKeySession *)session
120 {
121     UNUSED_PARAM(session);
122     if (_parent)
123         _parent->sessionIdentifierChanged(session.contentProtectionSessionIdentifier);
124 }
125
126 @end
127
128 namespace WebCore {
129
130 bool CDMInstanceFairPlayStreamingAVFObjC::supportsPersistableState()
131 {
132     return [PAL::getAVContentKeySessionClass() respondsToSelector:@selector(pendingExpiredSessionReportsWithAppIdentifier:storageDirectoryAtURL:)];
133 }
134
135 bool CDMInstanceFairPlayStreamingAVFObjC::supportsPersistentKeys()
136 {
137 #if PLATFORM(IOS_FAMILY)
138     return PAL::getAVPersistableContentKeyRequestClass();
139 #else
140     return false;
141 #endif
142 }
143
144 bool CDMInstanceFairPlayStreamingAVFObjC::supportsMediaCapability(const CDMMediaCapability& capability)
145 {
146     if (![PAL::getAVURLAssetClass() isPlayableExtendedMIMEType:capability.contentType])
147         return false;
148
149     // FairPlay only supports 'cbcs' encryption:
150     if (capability.encryptionScheme && capability.encryptionScheme.value() != CDMEncryptionScheme::cbcs)
151         return false;
152
153     return true;
154 }
155
156 CDMInstance::SuccessValue CDMInstanceFairPlayStreamingAVFObjC::initializeWithConfiguration(const CDMKeySystemConfiguration& configuration)
157 {
158     // FIXME: verify that FairPlayStreaming does not (and cannot) expose a distinctive identifier to the client
159     if (configuration.distinctiveIdentifier == CDMRequirement::Required)
160         return Failed;
161
162     if (configuration.persistentState != CDMRequirement::Required && (configuration.sessionTypes.contains(CDMSessionType::PersistentUsageRecord) || configuration.sessionTypes.contains(CDMSessionType::PersistentLicense)))
163         return Failed;
164
165     if (configuration.persistentState == CDMRequirement::Required && !m_storageURL)
166         return Failed;
167
168     if (configuration.sessionTypes.contains(CDMSessionType::PersistentLicense) && !supportsPersistentKeys())
169         return Failed;
170
171     if (!PAL::canLoad_AVFoundation_AVContentKeySystemFairPlayStreaming())
172         return Failed;
173
174     return Succeeded;
175 }
176
177 CDMInstance::SuccessValue CDMInstanceFairPlayStreamingAVFObjC::setDistinctiveIdentifiersAllowed(bool)
178 {
179     // FIXME: verify that FairPlayStreaming does not (and cannot) expose a distinctive identifier to the client
180     return Succeeded;
181 }
182
183 CDMInstance::SuccessValue CDMInstanceFairPlayStreamingAVFObjC::setPersistentStateAllowed(bool persistentStateAllowed)
184 {
185     m_persistentStateAllowed = persistentStateAllowed;
186     return Succeeded;
187 }
188
189 CDMInstance::SuccessValue CDMInstanceFairPlayStreamingAVFObjC::setServerCertificate(Ref<SharedBuffer>&& serverCertificate)
190 {
191     m_serverCertificate = WTFMove(serverCertificate);
192     return Succeeded;
193 }
194
195 CDMInstance::SuccessValue CDMInstanceFairPlayStreamingAVFObjC::setStorageDirectory(const String& storageDirectory)
196 {
197     if (storageDirectory.isEmpty()) {
198         m_storageURL = nil;
199         return Succeeded;
200     }
201
202     auto storagePath = FileSystem::pathByAppendingComponent(storageDirectory, "SecureStop.plist");
203
204     if (!FileSystem::fileExists(storageDirectory)) {
205         if (!FileSystem::makeAllDirectories(storageDirectory))
206             return Failed;
207     } else if (!FileSystem::fileIsDirectory(storageDirectory, FileSystem::ShouldFollowSymbolicLinks::Yes)) {
208         auto tempDirectory = FileSystem::createTemporaryDirectory(@"MediaKeys");
209         if (!tempDirectory)
210             return Failed;
211
212         auto tempStoragePath = FileSystem::pathByAppendingComponent(tempDirectory, FileSystem::pathGetFileName(storagePath));
213         if (!FileSystem::moveFile(storageDirectory, tempStoragePath))
214             return Failed;
215
216         if (!FileSystem::moveFile(tempDirectory, storageDirectory))
217             return Failed;
218     }
219
220     m_storageURL = adoptNS([[NSURL alloc] initFileURLWithPath:storagePath isDirectory:NO]);
221     return Succeeded;
222 }
223
224 RefPtr<CDMInstanceSession> CDMInstanceFairPlayStreamingAVFObjC::createSession()
225 {
226     auto session = adoptRef(*new CDMInstanceSessionFairPlayStreamingAVFObjC(*this));
227     m_sessions.append(makeWeakPtr(session.get()));
228     return session;
229 }
230
231 const String& CDMInstanceFairPlayStreamingAVFObjC::keySystem() const
232 {
233     static NeverDestroyed<String> keySystem { "com.apple.fps"_s };
234     return keySystem;
235 }
236
237 void CDMInstanceFairPlayStreamingAVFObjC::outputObscuredDueToInsufficientExternalProtectionChanged(bool obscured)
238 {
239     for (auto& sessionInterface : m_sessions) {
240         if (sessionInterface)
241             sessionInterface->outputObscuredDueToInsufficientExternalProtectionChanged(obscured);
242     }
243 }
244
245 CDMInstanceSessionFairPlayStreamingAVFObjC* CDMInstanceFairPlayStreamingAVFObjC::sessionForKeyIDs(const Vector<Ref<SharedBuffer>>& keyIDs) const
246 {
247     for (auto& sessionInterface : m_sessions) {
248         if (!sessionInterface)
249             continue;
250
251         auto sessionKeys = sessionInterface->keyIDs();
252         if (anyOf(sessionKeys, [&](auto& sessionKey) {
253             return keyIDs.contains(sessionKey);
254         }))
255             return sessionInterface.get();
256     }
257     return nullptr;
258 }
259
260 CDMInstanceSessionFairPlayStreamingAVFObjC::CDMInstanceSessionFairPlayStreamingAVFObjC(Ref<CDMInstanceFairPlayStreamingAVFObjC>&& instance)
261     : m_instance(WTFMove(instance))
262     , m_delegate([[WebCoreFPSContentKeySessionDelegate alloc] initWithParent:this])
263 {
264 }
265
266 CDMInstanceSessionFairPlayStreamingAVFObjC::~CDMInstanceSessionFairPlayStreamingAVFObjC()
267 {
268     [m_delegate invalidate];
269 }
270
271 static Vector<Ref<SharedBuffer>> keyIDsForRequest(AVContentKeyRequest* request)
272 {
273     if ([request.identifier isKindOfClass:[NSString class]])
274         return Vector<Ref<SharedBuffer>>::from(SharedBuffer::create([(NSString *)request.identifier dataUsingEncoding:NSUTF8StringEncoding]));
275     if ([request.identifier isKindOfClass:[NSData class]])
276         return Vector<Ref<SharedBuffer>>::from(SharedBuffer::create((NSData *)request.identifier));
277     if (request.initializationData) {
278         if (auto sinfKeyIDs = CDMPrivateFairPlayStreaming::extractKeyIDsSinf(SharedBuffer::create(request.initializationData)))
279             return WTFMove(sinfKeyIDs.value());
280     }
281     return { };
282 }
283
284 Vector<Ref<SharedBuffer>> CDMInstanceSessionFairPlayStreamingAVFObjC::keyIDs()
285 {
286     // FIXME(rdar://problem/35597141): use the future AVContentKeyRequest keyID property, rather than parsing it out of the init
287     // data, to get the keyID.
288     Vector<Ref<SharedBuffer>> keyIDs;
289     for (auto& request : m_requests) {
290         for (auto& key : keyIDsForRequest(request.get()))
291             keyIDs.append(WTFMove(key));
292     }
293
294     return keyIDs;
295 }
296
297 void CDMInstanceSessionFairPlayStreamingAVFObjC::requestLicense(LicenseType licenseType, const AtomString& initDataType, Ref<SharedBuffer>&& initData, LicenseCallback&& callback)
298 {
299     if (!isLicenseTypeSupported(licenseType)) {
300         callback(SharedBuffer::create(), emptyString(), false, Failed);
301         return;
302     }
303
304     if (!m_instance->serverCertificate()) {
305         callback(SharedBuffer::create(), emptyString(), false, Failed);
306         return;
307     }
308
309     if (!ensureSession()) {
310         callback(SharedBuffer::create(), emptyString(), false, Failed);
311         return;
312     }
313
314     RetainPtr<NSString> identifier;
315     RetainPtr<NSData> initializationData;
316
317     if (initDataType == CDMPrivateFairPlayStreaming::sinfName())
318         initializationData = initData->createNSData();
319     else if (initDataType == CDMPrivateFairPlayStreaming::skdName())
320         identifier = adoptNS([[NSString alloc] initWithData:initData->createNSData().get() encoding:NSUTF8StringEncoding]);
321     else {
322         callback(SharedBuffer::create(), emptyString(), false, Failed);
323         return;
324     }
325
326     m_requestLicenseCallback = WTFMove(callback);
327     [m_session processContentKeyRequestWithIdentifier:identifier.get() initializationData:initializationData.get() options:nil];
328 }
329
330 static bool isEqual(const SharedBuffer& data, const String& value)
331 {
332     auto arrayBuffer = data.tryCreateArrayBuffer();
333     if (!arrayBuffer)
334         return false;
335
336     auto exceptionOrDecoder = TextDecoder::create("utf8"_s, TextDecoder::Options());
337     if (exceptionOrDecoder.hasException())
338         return false;
339
340     Ref<TextDecoder> decoder = exceptionOrDecoder.releaseReturnValue();
341     auto stringOrException = decoder->decode(BufferSource::VariantType(WTFMove(arrayBuffer)), TextDecoder::DecodeOptions());
342     if (stringOrException.hasException())
343         return false;
344
345     return stringOrException.returnValue() == value;
346 }
347
348 void CDMInstanceSessionFairPlayStreamingAVFObjC::updateLicense(const String&, LicenseType, const SharedBuffer& responseData, LicenseUpdateCallback&& callback)
349 {
350     if (!m_expiredSessions.isEmpty() && isEqual(responseData, "acknowledged"_s)) {
351         auto expiredSessions = adoptNS([[NSMutableArray alloc] init]);
352         for (auto& session : m_expiredSessions)
353             [expiredSessions addObject:session.get()];
354
355         auto* certificate = m_instance->serverCertificate();
356         auto* storageURL = m_instance->storageURL();
357
358         if (!certificate || !storageURL) {
359             callback(false, WTF::nullopt, WTF::nullopt, WTF::nullopt, Failed);
360             return;
361         }
362
363         RetainPtr<NSData> appIdentifier = certificate->createNSData();
364         [PAL::getAVContentKeySessionClass() removePendingExpiredSessionReports:expiredSessions.get() withAppIdentifier:appIdentifier.get() storageDirectoryAtURL:storageURL];
365         callback(false, { }, WTF::nullopt, WTF::nullopt, Succeeded);
366         return;
367     }
368
369     if (!m_requests.isEmpty() && isEqual(responseData, "renew"_s)) {
370         [m_session renewExpiringResponseDataForContentKeyRequest:m_requests.last().get()];
371         m_updateLicenseCallback = WTFMove(callback);
372         return;
373     }
374
375     if (!m_currentRequest) {
376         callback(false, WTF::nullopt, WTF::nullopt, WTF::nullopt, Failed);
377         return;
378     }
379     Vector<Ref<SharedBuffer>> keyIDs = keyIDsForRequest(m_currentRequest.get());
380     if (keyIDs.isEmpty()) {
381         callback(false, WTF::nullopt, WTF::nullopt, WTF::nullopt, Failed);
382         return;
383     }
384
385     [m_currentRequest processContentKeyResponse:[PAL::getAVContentKeyResponseClass() contentKeyResponseWithFairPlayStreamingKeyResponseData:responseData.createNSData().get()]];
386
387     // FIXME(rdar://problem/35592277): stash the callback and call it once AVContentKeyResponse supports a success callback.
388     struct objc_method_description method = protocol_getMethodDescription(@protocol(AVContentKeySessionDelegate), @selector(contentKeySession:contentKeyRequestDidSucceed:), NO, YES);
389     if (!method.name) {
390         KeyStatusVector keyStatuses;
391         keyStatuses.reserveInitialCapacity(1);
392         keyStatuses.uncheckedAppend(std::make_pair(WTFMove(keyIDs.first()), KeyStatus::Usable));
393         callback(false, makeOptional(WTFMove(keyStatuses)), WTF::nullopt, WTF::nullopt, Succeeded);
394         return;
395     }
396
397     m_updateLicenseCallback = WTFMove(callback);
398 }
399
400 void CDMInstanceSessionFairPlayStreamingAVFObjC::loadSession(LicenseType licenseType, const String& sessionId, const String& origin, LoadSessionCallback&& callback)
401 {
402     UNUSED_PARAM(origin);
403     if (licenseType == LicenseType::PersistentUsageRecord) {
404         auto* storageURL = m_instance->storageURL();
405         if (!m_instance->persistentStateAllowed() || storageURL) {
406             callback(WTF::nullopt, WTF::nullopt, WTF::nullopt, Failed, SessionLoadFailure::MismatchedSessionType);
407             return;
408         }
409         auto* certificate = m_instance->serverCertificate();
410         if (!certificate) {
411             callback(WTF::nullopt, WTF::nullopt, WTF::nullopt, Failed, SessionLoadFailure::NoSessionData);
412             return;
413         }
414
415         RetainPtr<NSData> appIdentifier = certificate->createNSData();
416         KeyStatusVector changedKeys;
417         for (NSData* expiredSessionData in [PAL::getAVContentKeySessionClass() pendingExpiredSessionReportsWithAppIdentifier:appIdentifier.get() storageDirectoryAtURL:storageURL]) {
418             NSDictionary *expiredSession = [NSPropertyListSerialization propertyListWithData:expiredSessionData options:kCFPropertyListImmutable format:nullptr error:nullptr];
419             NSString *playbackSessionIdValue = (NSString *)[expiredSession objectForKey:PlaybackSessionIdKey];
420             if (![playbackSessionIdValue isKindOfClass:[NSString class]])
421                 continue;
422
423             if (sessionId == String(playbackSessionIdValue)) {
424                 // FIXME(rdar://problem/35934922): use key values stored in expired session report once available
425                 changedKeys.append((KeyStatusVector::ValueType){ SharedBuffer::create(), KeyStatus::Released });
426                 m_expiredSessions.append(expiredSessionData);
427             }
428         }
429
430         if (changedKeys.isEmpty()) {
431             callback(WTF::nullopt, WTF::nullopt, WTF::nullopt, Failed, SessionLoadFailure::NoSessionData);
432             return;
433         }
434
435         callback(WTFMove(changedKeys), WTF::nullopt, WTF::nullopt, Succeeded, SessionLoadFailure::None);
436     }
437 }
438
439 void CDMInstanceSessionFairPlayStreamingAVFObjC::closeSession(const String&, CloseSessionCallback&& callback)
440 {
441     if (m_requestLicenseCallback) {
442         m_requestLicenseCallback(SharedBuffer::create(), m_sessionId, false, Failed);
443         ASSERT(!m_requestLicenseCallback);
444     }
445     if (m_updateLicenseCallback) {
446         m_updateLicenseCallback(true, WTF::nullopt, WTF::nullopt, WTF::nullopt, Failed);
447         ASSERT(!m_updateLicenseCallback);
448     }
449     if (m_removeSessionDataCallback) {
450         m_removeSessionDataCallback({ }, WTF::nullopt, Failed);
451         ASSERT(!m_removeSessionDataCallback);
452     }
453     m_currentRequest = nullptr;
454     m_pendingRequests.clear();
455     m_requests.clear();
456     callback();
457 }
458
459 void CDMInstanceSessionFairPlayStreamingAVFObjC::removeSessionData(const String& sessionId, LicenseType licenseType, RemoveSessionDataCallback&& callback)
460 {
461     // FIXME: We should be able to expire individual AVContentKeyRequests rather than the entire AVContentKeySession.
462     [m_session expire];
463
464     if (licenseType == LicenseType::PersistentUsageRecord) {
465         auto* storageURL = m_instance->storageURL();
466         auto* certificate = m_instance->serverCertificate();
467
468         if (!m_instance->persistentStateAllowed() || !storageURL || !certificate) {
469             callback({ }, WTF::nullopt, Failed);
470             return;
471         }
472
473         RetainPtr<NSData> appIdentifier = certificate->createNSData();
474         RetainPtr<NSMutableArray> expiredSessionsArray = adoptNS([[NSMutableArray alloc] init]);
475         KeyStatusVector changedKeys;
476         for (NSData* expiredSessionData in [PAL::getAVContentKeySessionClass() pendingExpiredSessionReportsWithAppIdentifier:appIdentifier.get() storageDirectoryAtURL:storageURL]) {
477             NSDictionary *expiredSession = [NSPropertyListSerialization propertyListWithData:expiredSessionData options:kCFPropertyListImmutable format:nullptr error:nullptr];
478             NSString *playbackSessionIdValue = (NSString *)[expiredSession objectForKey:PlaybackSessionIdKey];
479             if (![playbackSessionIdValue isKindOfClass:[NSString class]])
480                 continue;
481
482             if (sessionId == String(playbackSessionIdValue)) {
483                 // FIXME(rdar://problem/35934922): use key values stored in expired session report once available
484                 changedKeys.append((KeyStatusVector::ValueType){ SharedBuffer::create(), KeyStatus::Released });
485                 m_expiredSessions.append(expiredSessionData);
486                 [expiredSessionsArray addObject:expiredSession];
487             }
488         }
489
490         RetainPtr<NSData> expiredSessionsData = [NSPropertyListSerialization dataWithPropertyList:expiredSessionsArray.get() format:NSPropertyListBinaryFormat_v1_0 options:kCFPropertyListImmutable error:nullptr];
491
492         callback(WTFMove(changedKeys), SharedBuffer::create(expiredSessionsData.get()), Succeeded);
493     }
494 }
495
496 void CDMInstanceSessionFairPlayStreamingAVFObjC::storeRecordOfKeyUsage(const String&)
497 {
498     // no-op; key usage data is stored automatically.
499 }
500
501 void CDMInstanceSessionFairPlayStreamingAVFObjC::setClient(WeakPtr<CDMInstanceSessionClient>&& client)
502 {
503     m_client = WTFMove(client);
504 }
505
506 void CDMInstanceSessionFairPlayStreamingAVFObjC::clearClient()
507 {
508     m_client = nullptr;
509 }
510
511 void CDMInstanceSessionFairPlayStreamingAVFObjC::didProvideRequest(AVContentKeyRequest *request)
512 {
513     if (m_currentRequest) {
514         m_pendingRequests.append(request);
515         return;
516     }
517
518     m_currentRequest = request;
519
520     ASSERT(!m_requests.contains(m_currentRequest));
521     m_requests.append(m_currentRequest);
522
523     RetainPtr<NSData> appIdentifier;
524     if (auto* certificate = m_instance->serverCertificate())
525         appIdentifier = certificate->createNSData();
526
527     auto keyIDs = keyIDsForRequest(request);
528     if (keyIDs.isEmpty()) {
529         if (m_requestLicenseCallback) {
530             m_requestLicenseCallback(SharedBuffer::create(), m_sessionId, false, Failed);
531             ASSERT(!m_requestLicenseCallback);
532         }
533         return;
534     }
535
536     RetainPtr<NSData> contentIdentifier = keyIDs.first()->createNSData();
537     [m_currentRequest makeStreamingContentKeyRequestDataForApp:appIdentifier.get() contentIdentifier:contentIdentifier.get() options:nil completionHandler:[this, weakThis = makeWeakPtr(*this)] (NSData *contentKeyRequestData, NSError *error) mutable {
538         callOnMainThread([this, weakThis = WTFMove(weakThis), error = retainPtr(error), contentKeyRequestData = retainPtr(contentKeyRequestData)] {
539             if (!weakThis)
540                 return;
541
542             sessionIdentifierChanged(m_session.get().contentProtectionSessionIdentifier);
543
544             if (error && m_requestLicenseCallback)
545                 m_requestLicenseCallback(SharedBuffer::create(), m_sessionId, false, Failed);
546             else if (m_requestLicenseCallback)
547                 m_requestLicenseCallback(SharedBuffer::create(contentKeyRequestData.get()), m_sessionId, false, Succeeded);
548             else if (m_client)
549                 m_client->sendMessage(CDMMessageType::LicenseRequest, SharedBuffer::create(contentKeyRequestData.get()));
550             ASSERT(!m_requestLicenseCallback);
551         });
552     }];
553 }
554
555 void CDMInstanceSessionFairPlayStreamingAVFObjC::didProvideRenewingRequest(AVContentKeyRequest *request)
556 {
557     ASSERT(!m_requestLicenseCallback);
558     if (m_currentRequest) {
559         m_pendingRequests.append(request);
560         return;
561     }
562
563     m_currentRequest = request;
564
565     // The assumption here is that AVContentKeyRequest will only ever notify us of a renewing request as a result of calling
566     // -renewExpiringResponseDataForContentKeyRequest: with an existing request.
567     ASSERT(m_requests.contains(m_currentRequest));
568
569     RetainPtr<NSData> appIdentifier;
570     if (auto* certificate = m_instance->serverCertificate())
571         appIdentifier = certificate->createNSData();
572     auto keyIDs = keyIDsForRequest(m_currentRequest.get());
573
574     RetainPtr<NSData> contentIdentifier = keyIDs.first()->createNSData();
575     [m_currentRequest makeStreamingContentKeyRequestDataForApp:appIdentifier.get() contentIdentifier:contentIdentifier.get() options:nil completionHandler:[this, weakThis = makeWeakPtr(*this)] (NSData *contentKeyRequestData, NSError *error) mutable {
576         callOnMainThread([this, weakThis = WTFMove(weakThis), error = retainPtr(error), contentKeyRequestData = retainPtr(contentKeyRequestData)] {
577             if (!weakThis || !m_client || error)
578                 return;
579
580             if (error && m_updateLicenseCallback)
581                 m_updateLicenseCallback(false, WTF::nullopt, WTF::nullopt, WTF::nullopt, Failed);
582             else if (m_updateLicenseCallback)
583                 m_updateLicenseCallback(false, WTF::nullopt, WTF::nullopt, Message(MessageType::LicenseRenewal, SharedBuffer::create(contentKeyRequestData.get())), Succeeded);
584             else if (m_client)
585                 m_client->sendMessage(CDMMessageType::LicenseRenewal, SharedBuffer::create(contentKeyRequestData.get()));
586             ASSERT(!m_updateLicenseCallback);
587         });
588     }];
589 }
590
591 void CDMInstanceSessionFairPlayStreamingAVFObjC::didProvidePersistableRequest(AVContentKeyRequest *request)
592 {
593     UNUSED_PARAM(request);
594 }
595
596 void CDMInstanceSessionFairPlayStreamingAVFObjC::didFailToProvideRequest(AVContentKeyRequest *request, NSError *error)
597 {
598     UNUSED_PARAM(request);
599     UNUSED_PARAM(error);
600     if (m_updateLicenseCallback) {
601         m_updateLicenseCallback(false, WTF::nullopt, WTF::nullopt, WTF::nullopt, Failed);
602         ASSERT(!m_updateLicenseCallback);
603     }
604
605     m_currentRequest = nullptr;
606
607     nextRequest();
608 }
609
610 void CDMInstanceSessionFairPlayStreamingAVFObjC::requestDidSucceed(AVContentKeyRequest *request)
611 {
612     UNUSED_PARAM(request);
613     if (m_updateLicenseCallback) {
614         m_updateLicenseCallback(false, makeOptional(keyStatuses()), WTF::nullopt, WTF::nullopt, Succeeded);
615         ASSERT(!m_updateLicenseCallback);
616     }
617
618     m_currentRequest = nullptr;
619
620     nextRequest();
621 }
622
623 void CDMInstanceSessionFairPlayStreamingAVFObjC::nextRequest()
624 {
625     if (m_pendingRequests.isEmpty())
626         return;
627
628     RetainPtr<AVContentKeyRequest> nextRequest = m_pendingRequests.first();
629     m_pendingRequests.remove(0);
630
631     if (nextRequest.get().renewsExpiringResponseData)
632         didProvideRenewingRequest(nextRequest.get());
633     else
634         didProvideRequest(nextRequest.get());
635 }
636
637 bool CDMInstanceSessionFairPlayStreamingAVFObjC::shouldRetryRequestForReason(AVContentKeyRequest *request, NSString *reason)
638 {
639     UNUSED_PARAM(request);
640     UNUSED_PARAM(reason);
641     notImplemented();
642     return false;
643 }
644
645 void CDMInstanceSessionFairPlayStreamingAVFObjC::sessionIdentifierChanged(NSData *sessionIdentifier)
646 {
647     String sessionId = emptyString();
648     if (sessionIdentifier)
649         sessionId = adoptNS([[NSString alloc] initWithData:sessionIdentifier encoding:NSUTF8StringEncoding]).get();
650
651     if (m_sessionId == sessionId)
652         return;
653
654     m_sessionId = sessionId;
655     m_client->sessionIdChanged(m_sessionId);
656 }
657
658 static auto requestStatusToCDMStatus(AVContentKeyRequestStatus status)
659 {
660     switch (status) {
661         case AVContentKeyRequestStatusRequestingResponse:
662         case AVContentKeyRequestStatusRetried:
663             return CDMKeyStatus::StatusPending;
664         case AVContentKeyRequestStatusReceivedResponse:
665         case AVContentKeyRequestStatusRenewed:
666             return CDMKeyStatus::Usable;
667         case AVContentKeyRequestStatusCancelled:
668             return CDMKeyStatus::Released;
669         case AVContentKeyRequestStatusFailed:
670             return CDMKeyStatus::InternalError;
671     }
672 }
673
674 CDMInstanceSession::KeyStatusVector CDMInstanceSessionFairPlayStreamingAVFObjC::keyStatuses() const
675 {
676     KeyStatusVector keyStatuses;
677
678     for (auto& request : m_requests) {
679         auto keyIDs = keyIDsForRequest(request.get());
680         auto status = requestStatusToCDMStatus(request.get().status);
681         if (m_outputObscured)
682             status = CDMKeyStatus::OutputRestricted;
683
684         for (auto& keyID : keyIDs)
685             keyStatuses.append({ WTFMove(keyID), status });
686     }
687
688     return keyStatuses;
689 }
690
691 void CDMInstanceSessionFairPlayStreamingAVFObjC::outputObscuredDueToInsufficientExternalProtectionChanged(bool obscured)
692 {
693     if (obscured == m_outputObscured)
694         return;
695
696     m_outputObscured = obscured;
697
698     if (m_client)
699         m_client->updateKeyStatuses(keyStatuses());
700 }
701
702 AVContentKeySession* CDMInstanceSessionFairPlayStreamingAVFObjC::ensureSession()
703 {
704     if (m_session)
705         return m_session.get();
706
707     auto storageURL = m_instance->storageURL();
708     if (!m_instance->persistentStateAllowed() || !storageURL)
709         m_session = [PAL::getAVContentKeySessionClass() contentKeySessionWithKeySystem:AVContentKeySystemFairPlayStreaming];
710     else
711         m_session = [PAL::getAVContentKeySessionClass() contentKeySessionWithKeySystem:AVContentKeySystemFairPlayStreaming storageDirectoryAtURL:storageURL];
712
713     if (!m_session)
714         return nullptr;
715
716     [m_session setDelegate:m_delegate.get() queue:dispatch_get_main_queue()];
717     return m_session.get();
718 }
719
720 bool CDMInstanceSessionFairPlayStreamingAVFObjC::isLicenseTypeSupported(LicenseType licenseType) const
721 {
722     switch (licenseType) {
723     case CDMSessionType::PersistentLicense:
724         return m_instance->persistentStateAllowed() && m_instance->supportsPersistentKeys();
725     case CDMSessionType::PersistentUsageRecord:
726         return m_instance->persistentStateAllowed() && m_instance->supportsPersistableState();
727     case CDMSessionType::Temporary:
728         return true;
729     }
730 }
731
732 }
733
734 #endif // ENABLE(ENCRYPTED_MEDIA) && HAVE(AVCONTENTKEYSESSION)