[EME] Batch multiple key requests into one request and response
[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 "InitDataRegistry.h"
35 #import "NotImplemented.h"
36 #import "SharedBuffer.h"
37 #import "TextDecoder.h"
38 #import <AVFoundation/AVContentKeySession.h>
39 #import <objc/runtime.h>
40 #import <pal/spi/mac/AVFoundationSPI.h>
41 #import <wtf/Algorithms.h>
42 #import <wtf/FileSystem.h>
43 #import <wtf/JSONValues.h>
44 #import <wtf/text/Base64.h>
45 #import <wtf/text/StringHash.h>
46
47 #import <pal/cocoa/AVFoundationSoftLink.h>
48
49 static const NSString *PlaybackSessionIdKey = @"PlaybackSessionID";
50
51 @interface WebCoreFPSContentKeySessionDelegate : NSObject<AVContentKeySessionDelegate> {
52     WebCore::CDMInstanceSessionFairPlayStreamingAVFObjC* _parent;
53 }
54 @end
55
56 @implementation WebCoreFPSContentKeySessionDelegate
57 - (id)initWithParent:(WebCore::CDMInstanceSessionFairPlayStreamingAVFObjC *)parent
58 {
59     if (!(self = [super init]))
60         return nil;
61
62     _parent = parent;
63     return self;
64 }
65
66 - (void)invalidate
67 {
68     _parent = nil;
69 }
70
71 - (void)contentKeySession:(AVContentKeySession *)session didProvideContentKeyRequest:(AVContentKeyRequest *)keyRequest
72 {
73     UNUSED_PARAM(session);
74     if (_parent)
75         _parent->didProvideRequest(keyRequest);
76 }
77
78 - (void)contentKeySession:(AVContentKeySession *)session didProvideRenewingContentKeyRequest:(AVContentKeyRequest *)keyRequest
79 {
80     UNUSED_PARAM(session);
81     if (_parent)
82         _parent->didProvideRenewingRequest(keyRequest);
83 }
84
85 #if PLATFORM(IOS_FAMILY)
86 - (void)contentKeySession:(AVContentKeySession *)session didProvidePersistableContentKeyRequest:(AVPersistableContentKeyRequest *)keyRequest
87 {
88     UNUSED_PARAM(session);
89     if (_parent)
90         _parent->didProvidePersistableRequest(keyRequest);
91 }
92
93 - (void)contentKeySession:(AVContentKeySession *)session didUpdatePersistableContentKey:(NSData *)persistableContentKey forContentKeyIdentifier:(id)keyIdentifier
94 {
95     UNUSED_PARAM(session);
96     UNUSED_PARAM(persistableContentKey);
97     UNUSED_PARAM(keyIdentifier);
98     notImplemented();
99 }
100 #endif
101
102 - (void)contentKeySession:(AVContentKeySession *)session didProvideContentKeyRequests:(NSArray<AVContentKeyRequest *> *)keyRequests forInitializationData:(nullable NSData *)initializationData
103 {
104     UNUSED_PARAM(session);
105     UNUSED_PARAM(initializationData);
106     if (!_parent)
107         return;
108
109     Vector<RetainPtr<AVContentKeyRequest>> requests;
110     requests.reserveInitialCapacity(keyRequests.count);
111     [keyRequests enumerateObjectsUsingBlock:[&](AVContentKeyRequest* request, NSUInteger, BOOL*) {
112         requests.uncheckedAppend(request);
113     }];
114     _parent->didProvideRequests(WTFMove(requests));
115 }
116
117 - (void)contentKeySession:(AVContentKeySession *)session contentKeyRequest:(AVContentKeyRequest *)keyRequest didFailWithError:(NSError *)err
118 {
119     UNUSED_PARAM(session);
120     if (_parent)
121         _parent->didFailToProvideRequest(keyRequest, err);
122 }
123
124 - (void)contentKeySession:(AVContentKeySession *)session contentKeyRequestDidSucceed:(AVContentKeyRequest *)keyRequest
125 {
126     UNUSED_PARAM(session);
127     if (_parent)
128         _parent->requestDidSucceed(keyRequest);
129 }
130
131 - (BOOL)contentKeySession:(AVContentKeySession *)session shouldRetryContentKeyRequest:(AVContentKeyRequest *)keyRequest reason:(AVContentKeyRequestRetryReason)retryReason
132 {
133     UNUSED_PARAM(session);
134     return _parent ? _parent->shouldRetryRequestForReason(keyRequest, retryReason) : false;
135 }
136
137 - (void)contentKeySessionContentProtectionSessionIdentifierDidChange:(AVContentKeySession *)session
138 {
139     UNUSED_PARAM(session);
140     if (_parent)
141         _parent->sessionIdentifierChanged(session.contentProtectionSessionIdentifier);
142 }
143
144 @end
145
146 namespace WebCore {
147
148 class CDMInstanceSessionFairPlayStreamingAVFObjC::UpdateResponseCollector {
149     WTF_MAKE_FAST_ALLOCATED;
150 public:
151     using KeyType = RetainPtr<AVContentKeyRequest>;
152     using ValueType = RetainPtr<NSData>;
153     using ResponseMap = HashMap<KeyType, ValueType>;
154     using UpdateCallback = WTF::Function<void(Optional<ResponseMap>&&)>;
155     UpdateResponseCollector(size_t numberOfExpectedResponses, UpdateCallback&& callback)
156         : m_numberOfExpectedResponses(numberOfExpectedResponses)
157         , m_callback(WTFMove(callback))
158     {
159     }
160
161     void addSuccessfulResponse(AVContentKeyRequest* request, NSData* data)
162     {
163         m_responses.set(request, data);
164         if (!--m_numberOfExpectedResponses)
165             m_callback(WTFMove(m_responses));
166     }
167     void addErrorResponse(AVContentKeyRequest* request, NSError* error)
168     {
169         UNUSED_PARAM(error);
170         m_responses.set(request, nullptr);
171         if (!--m_numberOfExpectedResponses)
172             m_callback(WTFMove(m_responses));
173     }
174     void fail()
175     {
176         m_callback(WTF::nullopt);
177     }
178
179 private:
180     size_t m_numberOfExpectedResponses;
181     UpdateCallback m_callback;
182     ResponseMap m_responses;
183 };
184
185 static RefPtr<JSON::Value> parseJSONValue(const SharedBuffer& buffer)
186 {
187     // Fail on large buffers whose size doesn't fit into a 32-bit unsigned integer.
188     size_t size = buffer.size();
189     if (size > std::numeric_limits<unsigned>::max())
190         return nullptr;
191
192     // Parse the buffer contents as JSON, returning the root object (if any).
193     String json { buffer.data(), static_cast<unsigned>(size) };
194     RefPtr<JSON::Value> value;
195     if (!JSON::Value::parseJSON(json, value))
196         return nullptr;
197
198     return value;
199 }
200
201 bool CDMInstanceFairPlayStreamingAVFObjC::supportsPersistableState()
202 {
203     return [PAL::getAVContentKeySessionClass() respondsToSelector:@selector(pendingExpiredSessionReportsWithAppIdentifier:storageDirectoryAtURL:)];
204 }
205
206 bool CDMInstanceFairPlayStreamingAVFObjC::supportsPersistentKeys()
207 {
208 #if PLATFORM(IOS_FAMILY)
209     return PAL::getAVPersistableContentKeyRequestClass();
210 #else
211     return false;
212 #endif
213 }
214
215 bool CDMInstanceFairPlayStreamingAVFObjC::supportsMediaCapability(const CDMMediaCapability& capability)
216 {
217     if (![PAL::getAVURLAssetClass() isPlayableExtendedMIMEType:capability.contentType])
218         return false;
219
220     // FairPlay only supports 'cbcs' encryption:
221     if (capability.encryptionScheme && capability.encryptionScheme.value() != CDMEncryptionScheme::cbcs)
222         return false;
223
224     return true;
225 }
226
227 CDMInstance::SuccessValue CDMInstanceFairPlayStreamingAVFObjC::initializeWithConfiguration(const CDMKeySystemConfiguration& configuration)
228 {
229     // FIXME: verify that FairPlayStreaming does not (and cannot) expose a distinctive identifier to the client
230     if (configuration.distinctiveIdentifier == CDMRequirement::Required)
231         return Failed;
232
233     if (configuration.persistentState != CDMRequirement::Required && (configuration.sessionTypes.contains(CDMSessionType::PersistentUsageRecord) || configuration.sessionTypes.contains(CDMSessionType::PersistentLicense)))
234         return Failed;
235
236     if (configuration.persistentState == CDMRequirement::Required && !m_storageURL)
237         return Failed;
238
239     if (configuration.sessionTypes.contains(CDMSessionType::PersistentLicense) && !supportsPersistentKeys())
240         return Failed;
241
242     if (!PAL::canLoad_AVFoundation_AVContentKeySystemFairPlayStreaming())
243         return Failed;
244
245     return Succeeded;
246 }
247
248 CDMInstance::SuccessValue CDMInstanceFairPlayStreamingAVFObjC::setDistinctiveIdentifiersAllowed(bool)
249 {
250     // FIXME: verify that FairPlayStreaming does not (and cannot) expose a distinctive identifier to the client
251     return Succeeded;
252 }
253
254 CDMInstance::SuccessValue CDMInstanceFairPlayStreamingAVFObjC::setPersistentStateAllowed(bool persistentStateAllowed)
255 {
256     m_persistentStateAllowed = persistentStateAllowed;
257     return Succeeded;
258 }
259
260 CDMInstance::SuccessValue CDMInstanceFairPlayStreamingAVFObjC::setServerCertificate(Ref<SharedBuffer>&& serverCertificate)
261 {
262     m_serverCertificate = WTFMove(serverCertificate);
263     return Succeeded;
264 }
265
266 CDMInstance::SuccessValue CDMInstanceFairPlayStreamingAVFObjC::setStorageDirectory(const String& storageDirectory)
267 {
268     if (storageDirectory.isEmpty()) {
269         m_storageURL = nil;
270         return Succeeded;
271     }
272
273     auto storagePath = FileSystem::pathByAppendingComponent(storageDirectory, "SecureStop.plist");
274
275     if (!FileSystem::fileExists(storageDirectory)) {
276         if (!FileSystem::makeAllDirectories(storageDirectory))
277             return Failed;
278     } else if (!FileSystem::fileIsDirectory(storageDirectory, FileSystem::ShouldFollowSymbolicLinks::Yes)) {
279         auto tempDirectory = FileSystem::createTemporaryDirectory(@"MediaKeys");
280         if (!tempDirectory)
281             return Failed;
282
283         auto tempStoragePath = FileSystem::pathByAppendingComponent(tempDirectory, FileSystem::pathGetFileName(storagePath));
284         if (!FileSystem::moveFile(storageDirectory, tempStoragePath))
285             return Failed;
286
287         if (!FileSystem::moveFile(tempDirectory, storageDirectory))
288             return Failed;
289     }
290
291     m_storageURL = adoptNS([[NSURL alloc] initFileURLWithPath:storagePath isDirectory:NO]);
292     return Succeeded;
293 }
294
295 RefPtr<CDMInstanceSession> CDMInstanceFairPlayStreamingAVFObjC::createSession()
296 {
297     auto session = adoptRef(*new CDMInstanceSessionFairPlayStreamingAVFObjC(*this));
298     m_sessions.append(makeWeakPtr(session.get()));
299     return session;
300 }
301
302 const String& CDMInstanceFairPlayStreamingAVFObjC::keySystem() const
303 {
304     static NeverDestroyed<String> keySystem { "com.apple.fps"_s };
305     return keySystem;
306 }
307
308 void CDMInstanceFairPlayStreamingAVFObjC::outputObscuredDueToInsufficientExternalProtectionChanged(bool obscured)
309 {
310     for (auto& sessionInterface : m_sessions) {
311         if (sessionInterface)
312             sessionInterface->outputObscuredDueToInsufficientExternalProtectionChanged(obscured);
313     }
314 }
315
316 CDMInstanceSessionFairPlayStreamingAVFObjC* CDMInstanceFairPlayStreamingAVFObjC::sessionForKeyIDs(const Keys& keyIDs) const
317 {
318     for (auto& sessionInterface : m_sessions) {
319         if (!sessionInterface)
320             continue;
321
322         auto sessionKeys = sessionInterface->keyIDs();
323         if (anyOf(sessionKeys, [&](auto& sessionKey) {
324             return keyIDs.contains(sessionKey);
325         }))
326             return sessionInterface.get();
327     }
328     return nullptr;
329 }
330
331 CDMInstanceSessionFairPlayStreamingAVFObjC::CDMInstanceSessionFairPlayStreamingAVFObjC(Ref<CDMInstanceFairPlayStreamingAVFObjC>&& instance)
332     : m_instance(WTFMove(instance))
333     , m_delegate([[WebCoreFPSContentKeySessionDelegate alloc] initWithParent:this])
334 {
335 }
336
337 CDMInstanceSessionFairPlayStreamingAVFObjC::~CDMInstanceSessionFairPlayStreamingAVFObjC()
338 {
339     [m_delegate invalidate];
340 }
341
342 using Keys = CDMInstanceSessionFairPlayStreamingAVFObjC::Keys;
343 static Keys keyIDsForRequest(AVContentKeyRequest* request)
344 {
345     if ([request.identifier isKindOfClass:[NSString class]])
346         return Keys::from(SharedBuffer::create([(NSString *)request.identifier dataUsingEncoding:NSUTF8StringEncoding]));
347     if ([request.identifier isKindOfClass:[NSData class]])
348         return Keys::from(SharedBuffer::create((NSData *)request.identifier));
349     if (request.initializationData) {
350         if (auto sinfKeyIDs = CDMPrivateFairPlayStreaming::extractKeyIDsSinf(SharedBuffer::create(request.initializationData)))
351             return WTFMove(sinfKeyIDs.value());
352     }
353     return { };
354 }
355
356 using Request = CDMInstanceSessionFairPlayStreamingAVFObjC::Request;
357 static Keys keyIDsForRequest(const Request& requests)
358 {
359     Keys keyIDs;
360     for (auto& request : requests)
361         keyIDs.appendVector(keyIDsForRequest(request.get()));
362     return keyIDs;
363 }
364
365 Keys CDMInstanceSessionFairPlayStreamingAVFObjC::keyIDs()
366 {
367     // FIXME(rdar://problem/35597141): use the future AVContentKeyRequest keyID property, rather than parsing it out of the init
368     // data, to get the keyID.
369     Keys keyIDs;
370     for (auto& request : m_requests) {
371         for (auto& key : keyIDsForRequest(request))
372             keyIDs.append(WTFMove(key));
373     }
374
375     return keyIDs;
376 }
377
378 void CDMInstanceSessionFairPlayStreamingAVFObjC::requestLicense(LicenseType licenseType, const AtomString& initDataType, Ref<SharedBuffer>&& initData, LicenseCallback&& callback)
379 {
380     if (!isLicenseTypeSupported(licenseType)) {
381         callback(SharedBuffer::create(), emptyString(), false, Failed);
382         return;
383     }
384
385     if (!m_instance->serverCertificate()) {
386         callback(SharedBuffer::create(), emptyString(), false, Failed);
387         return;
388     }
389
390     if (!ensureSession()) {
391         callback(SharedBuffer::create(), emptyString(), false, Failed);
392         return;
393     }
394
395     RetainPtr<NSString> identifier;
396     RetainPtr<NSData> initializationData;
397
398     if (initDataType == CDMPrivateFairPlayStreaming::sinfName())
399         initializationData = initData->createNSData();
400     else if (initDataType == CDMPrivateFairPlayStreaming::skdName())
401         identifier = adoptNS([[NSString alloc] initWithData:initData->createNSData().get() encoding:NSUTF8StringEncoding]);
402 #if HAVE(FAIRPLAYSTREAMING_CENC_INITDATA)
403     else if (initDataType == InitDataRegistry::cencName()) {
404         String psshString = base64Encode(initData->data(), initData->size());
405         initializationData = [NSJSONSerialization dataWithJSONObject:@{ @"pssh": (NSString*)psshString } options:NSJSONWritingPrettyPrinted error:nil];
406     }
407 #endif
408     else {
409         callback(SharedBuffer::create(), emptyString(), false, Failed);
410         return;
411     }
412
413     m_requestLicenseCallback = WTFMove(callback);
414     [m_session processContentKeyRequestWithIdentifier:identifier.get() initializationData:initializationData.get() options:nil];
415 }
416
417 static bool isEqual(const SharedBuffer& data, const String& value)
418 {
419     auto arrayBuffer = data.tryCreateArrayBuffer();
420     if (!arrayBuffer)
421         return false;
422
423     auto exceptionOrDecoder = TextDecoder::create("utf8"_s, TextDecoder::Options());
424     if (exceptionOrDecoder.hasException())
425         return false;
426
427     Ref<TextDecoder> decoder = exceptionOrDecoder.releaseReturnValue();
428     auto stringOrException = decoder->decode(BufferSource::VariantType(WTFMove(arrayBuffer)), TextDecoder::DecodeOptions());
429     if (stringOrException.hasException())
430         return false;
431
432     return stringOrException.returnValue() == value;
433 }
434
435 void CDMInstanceSessionFairPlayStreamingAVFObjC::updateLicense(const String&, LicenseType, const SharedBuffer& responseData, LicenseUpdateCallback&& callback)
436 {
437     if (!m_expiredSessions.isEmpty() && isEqual(responseData, "acknowledged"_s)) {
438         auto expiredSessions = adoptNS([[NSMutableArray alloc] init]);
439         for (auto& session : m_expiredSessions)
440             [expiredSessions addObject:session.get()];
441
442         auto* certificate = m_instance->serverCertificate();
443         auto* storageURL = m_instance->storageURL();
444
445         if (!certificate || !storageURL) {
446             callback(false, WTF::nullopt, WTF::nullopt, WTF::nullopt, Failed);
447             return;
448         }
449
450         RetainPtr<NSData> appIdentifier = certificate->createNSData();
451         [PAL::getAVContentKeySessionClass() removePendingExpiredSessionReports:expiredSessions.get() withAppIdentifier:appIdentifier.get() storageDirectoryAtURL:storageURL];
452         callback(false, { }, WTF::nullopt, WTF::nullopt, Succeeded);
453         return;
454     }
455
456     if (!m_requests.isEmpty() && isEqual(responseData, "renew"_s)) {
457         auto request = lastKeyRequest();
458         if (!request) {
459             callback(false, WTF::nullopt, WTF::nullopt, WTF::nullopt, Failed);
460             return;
461         }
462         [m_session renewExpiringResponseDataForContentKeyRequest:request];
463         m_updateLicenseCallback = WTFMove(callback);
464         return;
465     }
466
467     if (m_currentRequest.isEmpty()) {
468         callback(false, WTF::nullopt, WTF::nullopt, WTF::nullopt, Failed);
469         return;
470     }
471     Keys keyIDs = keyIDsForRequest(m_currentRequest);
472     if (keyIDs.isEmpty()) {
473         callback(false, WTF::nullopt, WTF::nullopt, WTF::nullopt, Failed);
474         return;
475     }
476
477     if (m_currentRequest.size() > 1) {
478         if (m_updateResponseCollector) {
479             m_updateResponseCollector->fail();
480             m_updateResponseCollector = nullptr;
481         }
482
483         m_updateResponseCollector = WTF::makeUnique<UpdateResponseCollector>(m_currentRequest.size(), [weakThis = makeWeakPtr(*this), this] (Optional<UpdateResponseCollector::ResponseMap>&& responses) {
484             if (!weakThis)
485                 return;
486
487             if (!m_updateLicenseCallback)
488                 return;
489
490             if (!responses || responses.value().isEmpty()) {
491                 m_updateLicenseCallback(true, WTF::nullopt, WTF::nullopt, WTF::nullopt, Failed);
492                 return;
493             }
494
495             m_updateLicenseCallback(false, keyStatuses(), WTF::nullopt, WTF::nullopt, Succeeded);
496             m_updateResponseCollector = nullptr;
497             m_currentRequest.clear();
498             nextRequest();
499         });
500
501         auto root = parseJSONValue(responseData);
502         RefPtr<JSON::Array> array;
503         if (!root || !root->asArray(array)) {
504             callback(false, WTF::nullopt, WTF::nullopt, WTF::nullopt, Failed);
505             return;
506         }
507
508         auto parseResponse = [&](RefPtr<JSON::Value>& value) -> bool {
509             RefPtr<JSON::Object> object;
510             if (!value->asObject(object))
511                 return false;
512
513             String keyIDString;
514             if (!object->getString("keyID", keyIDString))
515                 return false;
516
517             Vector<uint8_t> keyIDVector;
518             if (!base64Decode(keyIDString, keyIDVector))
519                 return false;
520
521             auto keyID = SharedBuffer::create(WTFMove(keyIDVector));
522             auto foundIndex = m_currentRequest.findMatching([&] (auto& request) {
523                 auto keyIDs = keyIDsForRequest(request.get());
524                 return keyIDs.contains(keyID);
525             });
526             if (foundIndex == notFound)
527                 return false;
528
529             auto& request = m_currentRequest[foundIndex];
530
531             auto payloadFindResults = object->find("payload");
532             auto errorFindResults = object->find("error");
533             bool hasPayload = payloadFindResults != object->end();
534             bool hasError = errorFindResults != object->end();
535
536             // Either "payload" or "error" are present, but not both
537             if (hasPayload == hasError)
538                 return false;
539
540             if (hasError) {
541                 NSInteger errorCode;
542                 if (!errorFindResults->value->asInteger(errorCode))
543                     return false;
544                 auto error = adoptNS([[NSError alloc] initWithDomain:@"org.webkit.eme" code:errorCode userInfo:nil]);
545                 [request processContentKeyResponseError:error.get()];
546             } else if (hasPayload) {
547                 String payloadString;
548                 if (!payloadFindResults->value->asString(payloadString))
549                     return false;
550                 Vector<uint8_t> payloadVector;
551                 if (!base64Decode(payloadString, payloadVector))
552                     return false;
553                 auto payloadData = SharedBuffer::create(WTFMove(payloadVector));
554                 [request processContentKeyResponse:[PAL::getAVContentKeyResponseClass() contentKeyResponseWithFairPlayStreamingKeyResponseData:payloadData->createNSData().get()]];
555             }
556             return true;
557         };
558         for (auto value : *array) {
559             if (!parseResponse(value)) {
560                 callback(false, WTF::nullopt, WTF::nullopt, WTF::nullopt, Failed);
561                 return;
562             }
563         }
564     } else
565         [m_currentRequest.first() processContentKeyResponse:[PAL::getAVContentKeyResponseClass() contentKeyResponseWithFairPlayStreamingKeyResponseData:responseData.createNSData().get()]];
566
567     // FIXME(rdar://problem/35592277): stash the callback and call it once AVContentKeyResponse supports a success callback.
568     struct objc_method_description method = protocol_getMethodDescription(@protocol(AVContentKeySessionDelegate), @selector(contentKeySession:contentKeyRequestDidSucceed:), NO, YES);
569     if (!method.name) {
570         KeyStatusVector keyStatuses;
571         keyStatuses.reserveInitialCapacity(1);
572         keyStatuses.uncheckedAppend(std::make_pair(WTFMove(keyIDs.first()), KeyStatus::Usable));
573         callback(false, makeOptional(WTFMove(keyStatuses)), WTF::nullopt, WTF::nullopt, Succeeded);
574         return;
575     }
576
577     m_updateLicenseCallback = WTFMove(callback);
578 }
579
580 void CDMInstanceSessionFairPlayStreamingAVFObjC::loadSession(LicenseType licenseType, const String& sessionId, const String& origin, LoadSessionCallback&& callback)
581 {
582     UNUSED_PARAM(origin);
583     if (licenseType == LicenseType::PersistentUsageRecord) {
584         auto* storageURL = m_instance->storageURL();
585         if (!m_instance->persistentStateAllowed() || storageURL) {
586             callback(WTF::nullopt, WTF::nullopt, WTF::nullopt, Failed, SessionLoadFailure::MismatchedSessionType);
587             return;
588         }
589         auto* certificate = m_instance->serverCertificate();
590         if (!certificate) {
591             callback(WTF::nullopt, WTF::nullopt, WTF::nullopt, Failed, SessionLoadFailure::NoSessionData);
592             return;
593         }
594
595         RetainPtr<NSData> appIdentifier = certificate->createNSData();
596         KeyStatusVector changedKeys;
597         for (NSData* expiredSessionData in [PAL::getAVContentKeySessionClass() pendingExpiredSessionReportsWithAppIdentifier:appIdentifier.get() storageDirectoryAtURL:storageURL]) {
598             NSDictionary *expiredSession = [NSPropertyListSerialization propertyListWithData:expiredSessionData options:kCFPropertyListImmutable format:nullptr error:nullptr];
599             NSString *playbackSessionIdValue = (NSString *)[expiredSession objectForKey:PlaybackSessionIdKey];
600             if (![playbackSessionIdValue isKindOfClass:[NSString class]])
601                 continue;
602
603             if (sessionId == String(playbackSessionIdValue)) {
604                 // FIXME(rdar://problem/35934922): use key values stored in expired session report once available
605                 changedKeys.append((KeyStatusVector::ValueType){ SharedBuffer::create(), KeyStatus::Released });
606                 m_expiredSessions.append(expiredSessionData);
607             }
608         }
609
610         if (changedKeys.isEmpty()) {
611             callback(WTF::nullopt, WTF::nullopt, WTF::nullopt, Failed, SessionLoadFailure::NoSessionData);
612             return;
613         }
614
615         callback(WTFMove(changedKeys), WTF::nullopt, WTF::nullopt, Succeeded, SessionLoadFailure::None);
616     }
617 }
618
619 void CDMInstanceSessionFairPlayStreamingAVFObjC::closeSession(const String&, CloseSessionCallback&& callback)
620 {
621     if (m_requestLicenseCallback) {
622         m_requestLicenseCallback(SharedBuffer::create(), m_sessionId, false, Failed);
623         ASSERT(!m_requestLicenseCallback);
624     }
625     if (m_updateLicenseCallback) {
626         m_updateLicenseCallback(true, WTF::nullopt, WTF::nullopt, WTF::nullopt, Failed);
627         ASSERT(!m_updateLicenseCallback);
628     }
629     if (m_removeSessionDataCallback) {
630         m_removeSessionDataCallback({ }, WTF::nullopt, Failed);
631         ASSERT(!m_removeSessionDataCallback);
632     }
633     m_currentRequest.clear();
634     m_pendingRequests.clear();
635     m_requests.clear();
636     callback();
637 }
638
639 void CDMInstanceSessionFairPlayStreamingAVFObjC::removeSessionData(const String& sessionId, LicenseType licenseType, RemoveSessionDataCallback&& callback)
640 {
641     // FIXME: We should be able to expire individual AVContentKeyRequests rather than the entire AVContentKeySession.
642     [m_session expire];
643
644     if (licenseType == LicenseType::PersistentUsageRecord) {
645         auto* storageURL = m_instance->storageURL();
646         auto* certificate = m_instance->serverCertificate();
647
648         if (!m_instance->persistentStateAllowed() || !storageURL || !certificate) {
649             callback({ }, WTF::nullopt, Failed);
650             return;
651         }
652
653         RetainPtr<NSData> appIdentifier = certificate->createNSData();
654         RetainPtr<NSMutableArray> expiredSessionsArray = adoptNS([[NSMutableArray alloc] init]);
655         KeyStatusVector changedKeys;
656         for (NSData* expiredSessionData in [PAL::getAVContentKeySessionClass() pendingExpiredSessionReportsWithAppIdentifier:appIdentifier.get() storageDirectoryAtURL:storageURL]) {
657             NSDictionary *expiredSession = [NSPropertyListSerialization propertyListWithData:expiredSessionData options:kCFPropertyListImmutable format:nullptr error:nullptr];
658             NSString *playbackSessionIdValue = (NSString *)[expiredSession objectForKey:PlaybackSessionIdKey];
659             if (![playbackSessionIdValue isKindOfClass:[NSString class]])
660                 continue;
661
662             if (sessionId == String(playbackSessionIdValue)) {
663                 // FIXME(rdar://problem/35934922): use key values stored in expired session report once available
664                 changedKeys.append((KeyStatusVector::ValueType){ SharedBuffer::create(), KeyStatus::Released });
665                 m_expiredSessions.append(expiredSessionData);
666                 [expiredSessionsArray addObject:expiredSession];
667             }
668         }
669
670         RetainPtr<NSData> expiredSessionsData = [NSPropertyListSerialization dataWithPropertyList:expiredSessionsArray.get() format:NSPropertyListBinaryFormat_v1_0 options:kCFPropertyListImmutable error:nullptr];
671
672         callback(WTFMove(changedKeys), SharedBuffer::create(expiredSessionsData.get()), Succeeded);
673     }
674 }
675
676 void CDMInstanceSessionFairPlayStreamingAVFObjC::storeRecordOfKeyUsage(const String&)
677 {
678     // no-op; key usage data is stored automatically.
679 }
680
681 void CDMInstanceSessionFairPlayStreamingAVFObjC::setClient(WeakPtr<CDMInstanceSessionClient>&& client)
682 {
683     m_client = WTFMove(client);
684 }
685
686 void CDMInstanceSessionFairPlayStreamingAVFObjC::clearClient()
687 {
688     m_client = nullptr;
689 }
690
691 void CDMInstanceSessionFairPlayStreamingAVFObjC::didProvideRequest(AVContentKeyRequest *request)
692 {
693     if (!m_currentRequest.isEmpty()) {
694         m_pendingRequests.append({ retainPtr(request) });
695         return;
696     }
697
698     m_currentRequest = { retainPtr(request) };
699
700     m_requests.append(m_currentRequest);
701
702     RetainPtr<NSData> appIdentifier;
703     if (auto* certificate = m_instance->serverCertificate())
704         appIdentifier = certificate->createNSData();
705
706     auto keyIDs = keyIDsForRequest(request);
707     if (keyIDs.isEmpty()) {
708         if (m_requestLicenseCallback) {
709             m_requestLicenseCallback(SharedBuffer::create(), m_sessionId, false, Failed);
710             ASSERT(!m_requestLicenseCallback);
711         }
712         return;
713     }
714
715     RetainPtr<NSData> contentIdentifier = keyIDs.first()->createNSData();
716     [request makeStreamingContentKeyRequestDataForApp:appIdentifier.get() contentIdentifier:contentIdentifier.get() options:nil completionHandler:[this, weakThis = makeWeakPtr(*this)] (NSData *contentKeyRequestData, NSError *error) mutable {
717         callOnMainThread([this, weakThis = WTFMove(weakThis), error = retainPtr(error), contentKeyRequestData = retainPtr(contentKeyRequestData)] {
718             if (!weakThis)
719                 return;
720
721             sessionIdentifierChanged(m_session.get().contentProtectionSessionIdentifier);
722
723             if (error && m_requestLicenseCallback)
724                 m_requestLicenseCallback(SharedBuffer::create(), m_sessionId, false, Failed);
725             else if (m_requestLicenseCallback)
726                 m_requestLicenseCallback(SharedBuffer::create(contentKeyRequestData.get()), m_sessionId, false, Succeeded);
727             else if (m_client)
728                 m_client->sendMessage(CDMMessageType::LicenseRequest, SharedBuffer::create(contentKeyRequestData.get()));
729             ASSERT(!m_requestLicenseCallback);
730         });
731     }];
732 }
733
734 void CDMInstanceSessionFairPlayStreamingAVFObjC::didProvideRequests(Vector<RetainPtr<AVContentKeyRequest>>&& requests)
735 {
736     if (!m_currentRequest.isEmpty()) {
737         m_pendingRequests.append(WTFMove(requests));
738         return;
739     }
740
741     m_currentRequest = requests;
742     m_requests.append(WTFMove(requests));
743
744     RetainPtr<NSData> appIdentifier;
745     if (auto* certificate = m_instance->serverCertificate())
746         appIdentifier = certificate->createNSData();
747
748     using RequestsData = Vector<std::pair<RefPtr<SharedBuffer>, RetainPtr<NSData>>>;
749     struct CallbackAggregator final : public ThreadSafeRefCounted<CallbackAggregator> {
750         using CallbackFunction = Function<void(RequestsData&&)>;
751         static RefPtr<CallbackAggregator> create(CallbackFunction&& completionHandler)
752         {
753             return adoptRef(new CallbackAggregator(WTFMove(completionHandler)));
754         }
755
756         explicit CallbackAggregator(Function<void(RequestsData&&)>&& completionHandler)
757             : m_completionHandler(WTFMove(completionHandler))
758         {
759         }
760
761         ~CallbackAggregator()
762         {
763             callOnMainThread([completionHandler = WTFMove(m_completionHandler), requestsData = WTFMove(requestsData)] () mutable {
764                 completionHandler(WTFMove(requestsData));
765             });
766         }
767
768         CompletionHandler<void(RequestsData&&)> m_completionHandler;
769         RequestsData requestsData;
770     };
771
772     auto aggregator = CallbackAggregator::create([this, weakThis = makeWeakPtr(*this)] (RequestsData&& requestsData) {
773         if (!weakThis)
774             return;
775
776         if (!m_requestLicenseCallback)
777             return;
778
779         if (requestsData.isEmpty()) {
780             m_requestLicenseCallback(SharedBuffer::create(), m_sessionId, false, Failed);
781             return;
782         }
783
784         auto requestJSON = JSON::Array::create();
785         for (auto& requestData : requestsData) {
786             auto entry = JSON::Object::create();
787             auto& keyID = requestData.first;
788             auto& payload = requestData.second;
789             entry->setString("keyID", base64Encode(keyID->data(), keyID->size()));
790             entry->setString("payload", base64Encode(payload.get().bytes, payload.get().length));
791             requestJSON->pushObject(WTFMove(entry));
792         }
793         auto requestBuffer = utf8Buffer(requestJSON->toJSONString());
794         if (!requestBuffer) {
795             m_requestLicenseCallback(SharedBuffer::create(), m_sessionId, false, Failed);
796             return;
797         }
798         m_requestLicenseCallback(requestBuffer.releaseNonNull(), m_sessionId, false, Succeeded);
799     });
800
801     for (auto request : m_currentRequest) {
802         auto keyIDs = keyIDsForRequest(request.get());
803         RefPtr<SharedBuffer> keyID = WTFMove(keyIDs.first());
804         auto contentIdentifier = keyID->createNSData();
805         [request makeStreamingContentKeyRequestDataForApp:appIdentifier.get() contentIdentifier:contentIdentifier.get() options:nil completionHandler:[keyID = WTFMove(keyID), aggregator = aggregator.copyRef()] (NSData *contentKeyRequestData, NSError *error) mutable {
806             UNUSED_PARAM(error);
807             callOnMainThread([keyID = WTFMove(keyID), aggregator = WTFMove(aggregator), contentKeyRequestData = retainPtr(contentKeyRequestData)] () mutable {
808                 aggregator->requestsData.append({ WTFMove(keyID), WTFMove(contentKeyRequestData) });
809             });
810         }];
811     }
812 }
813
814 void CDMInstanceSessionFairPlayStreamingAVFObjC::didProvideRenewingRequest(AVContentKeyRequest *request)
815 {
816     ASSERT(!m_requestLicenseCallback);
817     if (!m_currentRequest.isEmpty()) {
818         m_pendingRequests.append({ retainPtr(request) });
819         return;
820     }
821
822     m_currentRequest = { retainPtr(request) };
823
824     // The assumption here is that AVContentKeyRequest will only ever notify us of a renewing request as a result of calling
825     // -renewExpiringResponseDataForContentKeyRequest: with an existing request.
826     ASSERT(m_requests.contains(m_currentRequest));
827
828     RetainPtr<NSData> appIdentifier;
829     if (auto* certificate = m_instance->serverCertificate())
830         appIdentifier = certificate->createNSData();
831     auto keyIDs = keyIDsForRequest(m_currentRequest);
832
833     RetainPtr<NSData> contentIdentifier = keyIDs.first()->createNSData();
834     [request makeStreamingContentKeyRequestDataForApp:appIdentifier.get() contentIdentifier:contentIdentifier.get() options:nil completionHandler:[this, weakThis = makeWeakPtr(*this)] (NSData *contentKeyRequestData, NSError *error) mutable {
835         callOnMainThread([this, weakThis = WTFMove(weakThis), error = retainPtr(error), contentKeyRequestData = retainPtr(contentKeyRequestData)] {
836             if (!weakThis || !m_client || error)
837                 return;
838
839             if (error && m_updateLicenseCallback)
840                 m_updateLicenseCallback(false, WTF::nullopt, WTF::nullopt, WTF::nullopt, Failed);
841             else if (m_updateLicenseCallback)
842                 m_updateLicenseCallback(false, WTF::nullopt, WTF::nullopt, Message(MessageType::LicenseRenewal, SharedBuffer::create(contentKeyRequestData.get())), Succeeded);
843             else if (m_client)
844                 m_client->sendMessage(CDMMessageType::LicenseRenewal, SharedBuffer::create(contentKeyRequestData.get()));
845             ASSERT(!m_updateLicenseCallback);
846         });
847     }];
848 }
849
850 void CDMInstanceSessionFairPlayStreamingAVFObjC::didProvidePersistableRequest(AVContentKeyRequest *request)
851 {
852     UNUSED_PARAM(request);
853 }
854
855 void CDMInstanceSessionFairPlayStreamingAVFObjC::didFailToProvideRequest(AVContentKeyRequest *request, NSError *error)
856 {
857     UNUSED_PARAM(request);
858     UNUSED_PARAM(error);
859
860     if (m_updateResponseCollector) {
861         m_updateResponseCollector->addErrorResponse(request, error);
862         return;
863     }
864
865     if (m_updateLicenseCallback) {
866         m_updateLicenseCallback(false, WTF::nullopt, WTF::nullopt, WTF::nullopt, Failed);
867         ASSERT(!m_updateLicenseCallback);
868     }
869
870     m_currentRequest.clear();
871
872     nextRequest();
873 }
874
875 void CDMInstanceSessionFairPlayStreamingAVFObjC::requestDidSucceed(AVContentKeyRequest *request)
876 {
877     UNUSED_PARAM(request);
878     if (m_updateResponseCollector) {
879         m_updateResponseCollector->addSuccessfulResponse(request, nullptr);
880         return;
881     }
882
883     if (m_updateLicenseCallback) {
884         m_updateLicenseCallback(false, makeOptional(keyStatuses()), WTF::nullopt, WTF::nullopt, Succeeded);
885         ASSERT(!m_updateLicenseCallback);
886     }
887
888     m_currentRequest.clear();
889
890     nextRequest();
891 }
892
893 void CDMInstanceSessionFairPlayStreamingAVFObjC::nextRequest()
894 {
895     if (m_pendingRequests.isEmpty())
896         return;
897
898     Request nextRequest = WTFMove(m_pendingRequests.first());
899     m_pendingRequests.remove(0);
900
901     if (nextRequest.isEmpty())
902         return;
903
904     if (nextRequest.size() > 1) {
905         didProvideRequests(WTFMove(nextRequest));
906         return;
907     }
908
909     auto* oneRequest = nextRequest.first().get();
910     if (oneRequest.renewsExpiringResponseData)
911         didProvideRenewingRequest(oneRequest);
912     else
913         didProvideRequest(oneRequest);
914 }
915
916 AVContentKeyRequest* CDMInstanceSessionFairPlayStreamingAVFObjC::lastKeyRequest() const
917 {
918     if (m_requests.isEmpty())
919         return nil;
920     auto& lastRequest = m_requests.last();
921     if (lastRequest.isEmpty())
922         return nil;
923     return lastRequest.last().get();
924 }
925
926 bool CDMInstanceSessionFairPlayStreamingAVFObjC::shouldRetryRequestForReason(AVContentKeyRequest *request, NSString *reason)
927 {
928     UNUSED_PARAM(request);
929     UNUSED_PARAM(reason);
930     notImplemented();
931     return false;
932 }
933
934 void CDMInstanceSessionFairPlayStreamingAVFObjC::sessionIdentifierChanged(NSData *sessionIdentifier)
935 {
936     String sessionId = emptyString();
937     if (sessionIdentifier)
938         sessionId = adoptNS([[NSString alloc] initWithData:sessionIdentifier encoding:NSUTF8StringEncoding]).get();
939
940     if (m_sessionId == sessionId)
941         return;
942
943     m_sessionId = sessionId;
944     m_client->sessionIdChanged(m_sessionId);
945 }
946
947 static auto requestStatusToCDMStatus(AVContentKeyRequestStatus status)
948 {
949     switch (status) {
950         case AVContentKeyRequestStatusRequestingResponse:
951         case AVContentKeyRequestStatusRetried:
952             return CDMKeyStatus::StatusPending;
953         case AVContentKeyRequestStatusReceivedResponse:
954         case AVContentKeyRequestStatusRenewed:
955             return CDMKeyStatus::Usable;
956         case AVContentKeyRequestStatusCancelled:
957             return CDMKeyStatus::Released;
958         case AVContentKeyRequestStatusFailed:
959             return CDMKeyStatus::InternalError;
960     }
961 }
962
963 CDMInstanceSession::KeyStatusVector CDMInstanceSessionFairPlayStreamingAVFObjC::keyStatuses() const
964 {
965     KeyStatusVector keyStatuses;
966
967     for (auto& request : m_requests) {
968         for (auto& oneRequest : request) {
969             auto keyIDs = keyIDsForRequest(oneRequest.get());
970             auto status = requestStatusToCDMStatus(oneRequest.get().status);
971             if (m_outputObscured)
972                 status = CDMKeyStatus::OutputRestricted;
973
974             for (auto& keyID : keyIDs)
975                 keyStatuses.append({ WTFMove(keyID), status });
976         }
977     }
978
979     return keyStatuses;
980 }
981
982 void CDMInstanceSessionFairPlayStreamingAVFObjC::outputObscuredDueToInsufficientExternalProtectionChanged(bool obscured)
983 {
984     if (obscured == m_outputObscured)
985         return;
986
987     m_outputObscured = obscured;
988
989     if (m_client)
990         m_client->updateKeyStatuses(keyStatuses());
991 }
992
993 AVContentKeySession* CDMInstanceSessionFairPlayStreamingAVFObjC::ensureSession()
994 {
995     if (m_session)
996         return m_session.get();
997
998     auto storageURL = m_instance->storageURL();
999     if (!m_instance->persistentStateAllowed() || !storageURL)
1000         m_session = [PAL::getAVContentKeySessionClass() contentKeySessionWithKeySystem:AVContentKeySystemFairPlayStreaming];
1001     else
1002         m_session = [PAL::getAVContentKeySessionClass() contentKeySessionWithKeySystem:AVContentKeySystemFairPlayStreaming storageDirectoryAtURL:storageURL];
1003
1004     if (!m_session)
1005         return nullptr;
1006
1007     [m_session setDelegate:m_delegate.get() queue:dispatch_get_main_queue()];
1008     return m_session.get();
1009 }
1010
1011 bool CDMInstanceSessionFairPlayStreamingAVFObjC::isLicenseTypeSupported(LicenseType licenseType) const
1012 {
1013     switch (licenseType) {
1014     case CDMSessionType::PersistentLicense:
1015         return m_instance->persistentStateAllowed() && m_instance->supportsPersistentKeys();
1016     case CDMSessionType::PersistentUsageRecord:
1017         return m_instance->persistentStateAllowed() && m_instance->supportsPersistableState();
1018     case CDMSessionType::Temporary:
1019         return true;
1020     }
1021 }
1022
1023 }
1024
1025 #endif // ENABLE(ENCRYPTED_MEDIA) && HAVE(AVCONTENTKEYSESSION)