ClearKeySessionManager.cpp (24743B)
1 /* 2 * Copyright 2015, Mozilla Foundation and contributors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include "ClearKeySessionManager.h" 18 19 #include <assert.h> 20 #include <stdint.h> 21 #include <stdio.h> 22 #include <string.h> 23 24 #include "ClearKeyDecryptionManager.h" 25 #include "ClearKeyPersistence.h" 26 #include "ClearKeyStorage.h" 27 #include "ClearKeyUtils.h" 28 #include "content_decryption_module.h" 29 #include "psshparser/PsshParser.h" 30 31 using namespace cdm; 32 33 using std::function; 34 using std::string; 35 using std::vector; 36 37 ClearKeySessionManager::ClearKeySessionManager(Host_11* aHost) 38 : mDecryptionManager(ClearKeyDecryptionManager::Get()) { 39 CK_LOGD("ClearKeySessionManager ctor %p", this); 40 AddRef(); 41 42 mHost = aHost; 43 mPersistence = new ClearKeyPersistence(mHost); 44 } 45 46 ClearKeySessionManager::~ClearKeySessionManager() { 47 CK_LOGD("ClearKeySessionManager dtor %p", this); 48 } 49 50 void ClearKeySessionManager::Init(bool aDistinctiveIdentifierAllowed, 51 bool aPersistentStateAllowed) { 52 CK_LOGD("ClearKeySessionManager::Init"); 53 54 RefPtr<ClearKeySessionManager> self(this); 55 function<void()> onPersistentStateLoaded = [self]() { 56 while (!self->mDeferredInitialize.empty()) { 57 function<void()> func = self->mDeferredInitialize.front(); 58 self->mDeferredInitialize.pop(); 59 60 func(); 61 } 62 if (self->mHost) { 63 // The session manager should be the last thing the ClearKey CDM is 64 // waiting on to be initialized. 65 self->mHost->OnInitialized(true); 66 } 67 }; 68 69 mPersistence->EnsureInitialized(aPersistentStateAllowed, 70 std::move(onPersistentStateLoaded)); 71 } 72 73 void ClearKeySessionManager::CreateSession(uint32_t aPromiseId, 74 InitDataType aInitDataType, 75 const uint8_t* aInitData, 76 uint32_t aInitDataSize, 77 SessionType aSessionType) { 78 CK_LOGD("ClearKeySessionManager::CreateSession type:%u", aInitDataType); 79 80 // Copy the init data so it is correctly captured by the lambda 81 vector<uint8_t> initData(aInitData, aInitData + aInitDataSize); 82 83 RefPtr<ClearKeySessionManager> self(this); 84 function<void()> deferrer = [self, aPromiseId, aInitDataType, initData, 85 aSessionType]() { 86 self->CreateSession(aPromiseId, aInitDataType, initData.data(), 87 initData.size(), aSessionType); 88 }; 89 90 // If we haven't loaded, don't do this yet 91 if (MaybeDeferTillInitialized(std::move(deferrer))) { 92 CK_LOGD("Deferring CreateSession"); 93 return; 94 } 95 96 CK_LOGARRAY("ClearKeySessionManager::CreateSession initdata: ", aInitData, 97 aInitDataSize); 98 99 // If 'DecryptingComplete' has been called mHost will be null so we can't 100 // won't be able to resolve our promise 101 if (!mHost) { 102 CK_LOGD("ClearKeySessionManager::CreateSession: mHost is nullptr"); 103 return; 104 } 105 106 // initDataType must be "cenc", "keyids", or "webm". 107 if (aInitDataType != InitDataType::kCenc && 108 aInitDataType != InitDataType::kKeyIds && 109 aInitDataType != InitDataType::kWebM) { 110 string message = "initDataType is not supported by ClearKey"; 111 mHost->OnRejectPromise(aPromiseId, Exception::kExceptionNotSupportedError, 112 0, message.c_str(), message.size()); 113 114 return; 115 } 116 117 string sessionId = mPersistence->GetNewSessionId(aSessionType); 118 assert(mSessions.find(sessionId) == mSessions.end()); 119 120 ClearKeySession* session = new ClearKeySession(sessionId, aSessionType); 121 122 if (!session->Init(aInitDataType, aInitData, aInitDataSize)) { 123 CK_LOGD("Failed to initialize session: %s", sessionId.c_str()); 124 125 const static char* message = "Failed to initialize session"; 126 mHost->OnRejectPromise(aPromiseId, Exception::kExceptionInvalidStateError, 127 0, message, strlen(message)); 128 delete session; 129 130 return; 131 } 132 133 mSessions[sessionId] = session; 134 mLastSessionId = sessionId; 135 136 const vector<KeyId>& sessionKeys = session->GetKeyIds(); 137 vector<KeyId> neededKeys; 138 139 for (auto it = sessionKeys.begin(); it != sessionKeys.end(); it++) { 140 // Need to request this key ID from the client. We always send a key 141 // request, whether or not another session has sent a request with the same 142 // key ID. Otherwise a script can end up waiting for another script to 143 // respond to the request (which may not necessarily happen). 144 CK_LOGARRAY("Key ID: ", it->data(), it->size()); 145 neededKeys.push_back(*it); 146 mDecryptionManager->ExpectKeyId(*it); 147 } 148 149 if (neededKeys.empty()) { 150 CK_LOGD("No keys needed from client."); 151 return; 152 } 153 154 // Send a request for needed key data. 155 string request; 156 ClearKeyUtils::MakeKeyRequest(neededKeys, request, aSessionType); 157 158 // Resolve the promise with the new session information. 159 mHost->OnResolveNewSessionPromise(aPromiseId, sessionId.c_str(), 160 sessionId.size()); 161 162 mHost->OnSessionMessage(sessionId.c_str(), sessionId.size(), 163 MessageType::kLicenseRequest, request.c_str(), 164 request.size()); 165 } 166 167 void ClearKeySessionManager::LoadSession(uint32_t aPromiseId, 168 const char* aSessionId, 169 uint32_t aSessionIdLength) { 170 CK_LOGD("ClearKeySessionManager::LoadSession"); 171 172 // Copy the sessionId into a string so the lambda captures it properly. 173 string sessionId(aSessionId, aSessionId + aSessionIdLength); 174 175 // Hold a reference to the SessionManager so that it isn't released before 176 // we try to use it. 177 RefPtr<ClearKeySessionManager> self(this); 178 function<void()> deferrer = [self, aPromiseId, sessionId]() { 179 self->LoadSession(aPromiseId, sessionId.data(), sessionId.size()); 180 }; 181 182 if (MaybeDeferTillInitialized(std::move(deferrer))) { 183 CK_LOGD("Deferring LoadSession"); 184 return; 185 } 186 187 // If the SessionManager has been shutdown mHost will be null and we won't 188 // be able to resolve the promise. 189 if (!mHost) { 190 return; 191 } 192 193 if (!ClearKeyUtils::IsValidSessionId(aSessionId, aSessionIdLength)) { 194 mHost->OnResolveNewSessionPromise(aPromiseId, nullptr, 0); 195 return; 196 } 197 198 if (!mPersistence->IsPersistentSessionId(sessionId)) { 199 mHost->OnResolveNewSessionPromise(aPromiseId, nullptr, 0); 200 return; 201 } 202 203 function<void(const uint8_t*, uint32_t)> success = 204 [self, sessionId, aPromiseId](const uint8_t* data, uint32_t size) { 205 self->PersistentSessionDataLoaded(aPromiseId, sessionId, data, size); 206 }; 207 208 function<void()> failure = [self, aPromiseId] { 209 if (!self->mHost) { 210 return; 211 } 212 // As per the API described in ContentDecryptionModule_8 213 self->mHost->OnResolveNewSessionPromise(aPromiseId, nullptr, 0); 214 }; 215 216 ReadData(mHost, sessionId, std::move(success), std::move(failure)); 217 } 218 219 void ClearKeySessionManager::PersistentSessionDataLoaded( 220 uint32_t aPromiseId, const string& aSessionId, const uint8_t* aKeyData, 221 uint32_t aKeyDataSize) { 222 CK_LOGD("ClearKeySessionManager::PersistentSessionDataLoaded"); 223 224 // Check that the SessionManager has not been shut down before we try and 225 // resolve any promises. 226 if (!mHost) { 227 return; 228 } 229 230 if (Contains(mSessions, aSessionId) || 231 (aKeyDataSize % (2 * CENC_KEY_LEN)) != 0) { 232 // As per the instructions in ContentDecryptionModule_8 233 mHost->OnResolveNewSessionPromise(aPromiseId, nullptr, 0); 234 return; 235 } 236 237 ClearKeySession* session = 238 new ClearKeySession(aSessionId, SessionType::kPersistentLicense); 239 240 mSessions[aSessionId] = session; 241 mLastSessionId = aSessionId; 242 243 uint32_t numKeys = aKeyDataSize / (2 * CENC_KEY_LEN); 244 245 vector<KeyInformation> keyInfos; 246 vector<KeyIdPair> keyPairs; 247 for (uint32_t i = 0; i < numKeys; i++) { 248 const uint8_t* base = aKeyData + 2 * CENC_KEY_LEN * i; 249 250 KeyIdPair keyPair; 251 252 keyPair.mKeyId = KeyId(base, base + CENC_KEY_LEN); 253 assert(keyPair.mKeyId.size() == CENC_KEY_LEN); 254 255 keyPair.mKey = Key(base + CENC_KEY_LEN, base + 2 * CENC_KEY_LEN); 256 assert(keyPair.mKey.size() == CENC_KEY_LEN); 257 258 session->AddKeyId(keyPair.mKeyId); 259 260 mDecryptionManager->ExpectKeyId(keyPair.mKeyId); 261 mDecryptionManager->InitKey(keyPair.mKeyId, keyPair.mKey); 262 mKeyIds.insert(keyPair.mKey); 263 keyPairs.push_back(keyPair); 264 265 KeyInformation keyInfo = {}; 266 keyInfo.key_id = keyPairs.back().mKeyId.data(); 267 keyInfo.key_id_size = keyPair.mKeyId.size(); 268 keyInfo.status = KeyStatus::kUsable; 269 270 keyInfos.push_back(keyInfo); 271 } 272 273 mHost->OnSessionKeysChange(aSessionId.data(), aSessionId.size(), true, 274 keyInfos.data(), keyInfos.size()); 275 276 mHost->OnResolveNewSessionPromise(aPromiseId, aSessionId.c_str(), 277 aSessionId.size()); 278 } 279 280 void ClearKeySessionManager::UpdateSession(uint32_t aPromiseId, 281 const char* aSessionId, 282 uint32_t aSessionIdLength, 283 const uint8_t* aResponse, 284 uint32_t aResponseSize) { 285 CK_LOGD("ClearKeySessionManager::UpdateSession"); 286 287 // Copy the method arguments so we can capture them in the lambda 288 string sessionId(aSessionId, aSessionId + aSessionIdLength); 289 vector<uint8_t> response(aResponse, aResponse + aResponseSize); 290 291 // Hold a reference to the SessionManager so it isn't released before we 292 // callback. 293 RefPtr<ClearKeySessionManager> self(this); 294 function<void()> deferrer = [self, aPromiseId, sessionId, response]() { 295 self->UpdateSession(aPromiseId, sessionId.data(), sessionId.size(), 296 response.data(), response.size()); 297 }; 298 299 // If we haven't fully loaded, defer calling this method 300 if (MaybeDeferTillInitialized(std::move(deferrer))) { 301 CK_LOGD("Deferring LoadSession"); 302 return; 303 } 304 305 // Make sure the SessionManager has not been shutdown before we try and 306 // resolve any promises. 307 if (!mHost) { 308 return; 309 } 310 311 CK_LOGD("Updating session: %s", sessionId.c_str()); 312 313 auto itr = mSessions.find(sessionId); 314 if (itr == mSessions.end() || !(itr->second)) { 315 CK_LOGW("ClearKey CDM couldn't resolve session ID in UpdateSession."); 316 CK_LOGD("Unable to find session: %s", sessionId.c_str()); 317 mHost->OnRejectPromise(aPromiseId, Exception::kExceptionTypeError, 0, 318 nullptr, 0); 319 320 return; 321 } 322 ClearKeySession* session = itr->second; 323 324 // Verify the size of session response. 325 if (aResponseSize >= kMaxSessionResponseLength) { 326 CK_LOGW("Session response size is not within a reasonable size."); 327 CK_LOGD("Failed to parse response for session %s", sessionId.c_str()); 328 329 mHost->OnRejectPromise(aPromiseId, Exception::kExceptionTypeError, 0, 330 nullptr, 0); 331 332 return; 333 } 334 335 // Parse the response for any (key ID, key) pairs. 336 vector<KeyIdPair> keyPairs; 337 if (!ClearKeyUtils::ParseJWK(aResponse, aResponseSize, keyPairs, 338 session->Type())) { 339 CK_LOGW("ClearKey CDM failed to parse JSON Web Key."); 340 341 mHost->OnRejectPromise(aPromiseId, Exception::kExceptionTypeError, 0, 342 nullptr, 0); 343 344 return; 345 } 346 347 vector<KeyInformation> keyInfos; 348 for (size_t i = 0; i < keyPairs.size(); i++) { 349 KeyIdPair& keyPair = keyPairs[i]; 350 mDecryptionManager->InitKey(keyPair.mKeyId, keyPair.mKey); 351 mKeyIds.insert(keyPair.mKeyId); 352 353 KeyInformation keyInfo = {}; 354 keyInfo.key_id = keyPair.mKeyId.data(); 355 keyInfo.key_id_size = keyPair.mKeyId.size(); 356 keyInfo.status = KeyStatus::kUsable; 357 358 keyInfos.push_back(keyInfo); 359 } 360 361 mHost->OnSessionKeysChange(aSessionId, aSessionIdLength, true, 362 keyInfos.data(), keyInfos.size()); 363 364 if (session->Type() != SessionType::kPersistentLicense) { 365 mHost->OnResolvePromise(aPromiseId); 366 return; 367 } 368 369 // Store the keys on disk. We store a record whose name is the sessionId, 370 // and simply append each keyId followed by its key. 371 vector<uint8_t> keydata; 372 Serialize(session, keydata); 373 374 function<void()> resolve = [self, aPromiseId]() { 375 if (!self->mHost) { 376 return; 377 } 378 self->mHost->OnResolvePromise(aPromiseId); 379 }; 380 381 function<void()> reject = [self, aPromiseId]() { 382 if (!self->mHost) { 383 return; 384 } 385 386 static const char* message = "Couldn't store cenc key init data"; 387 self->mHost->OnRejectPromise(aPromiseId, 388 Exception::kExceptionInvalidStateError, 0, 389 message, strlen(message)); 390 }; 391 392 WriteData(mHost, sessionId, keydata, std::move(resolve), std::move(reject)); 393 } 394 395 void ClearKeySessionManager::Serialize(const ClearKeySession* aSession, 396 std::vector<uint8_t>& aOutKeyData) { 397 const std::vector<KeyId>& keyIds = aSession->GetKeyIds(); 398 for (size_t i = 0; i < keyIds.size(); i++) { 399 const KeyId& keyId = keyIds[i]; 400 if (!mDecryptionManager->HasKeyForKeyId(keyId)) { 401 continue; 402 } 403 assert(keyId.size() == CENC_KEY_LEN); 404 aOutKeyData.insert(aOutKeyData.end(), keyId.begin(), keyId.end()); 405 const Key& key = mDecryptionManager->GetDecryptionKey(keyId); 406 assert(key.size() == CENC_KEY_LEN); 407 aOutKeyData.insert(aOutKeyData.end(), key.begin(), key.end()); 408 } 409 } 410 411 void ClearKeySessionManager::CloseSession(uint32_t aPromiseId, 412 const char* aSessionId, 413 uint32_t aSessionIdLength) { 414 CK_LOGD("ClearKeySessionManager::CloseSession"); 415 416 // Copy the sessionId into a string so we capture it properly. 417 string sessionId(aSessionId, aSessionId + aSessionIdLength); 418 // Hold a reference to the session manager, so it doesn't get deleted 419 // before we need to use it. 420 RefPtr<ClearKeySessionManager> self(this); 421 function<void()> deferrer = [self, aPromiseId, sessionId]() { 422 self->CloseSession(aPromiseId, sessionId.data(), sessionId.size()); 423 }; 424 425 // If we haven't loaded, call this method later. 426 if (MaybeDeferTillInitialized(std::move(deferrer))) { 427 CK_LOGD("Deferring CloseSession"); 428 return; 429 } 430 431 // If DecryptingComplete has been called mHost will be null and we won't 432 // be able to resolve our promise. 433 if (!mHost) { 434 return; 435 } 436 437 auto itr = mSessions.find(sessionId); 438 if (itr == mSessions.end()) { 439 CK_LOGW("ClearKey CDM couldn't close non-existent session."); 440 mHost->OnRejectPromise(aPromiseId, Exception::kExceptionTypeError, 0, 441 nullptr, 0); 442 443 return; 444 } 445 446 ClearKeySession* session = itr->second; 447 assert(session); 448 449 ClearInMemorySessionData(session); 450 451 mHost->OnSessionClosed(aSessionId, aSessionIdLength); 452 mHost->OnResolvePromise(aPromiseId); 453 } 454 455 void ClearKeySessionManager::ClearInMemorySessionData( 456 ClearKeySession* aSession) { 457 mSessions.erase(aSession->Id()); 458 delete aSession; 459 } 460 461 void ClearKeySessionManager::RemoveSession(uint32_t aPromiseId, 462 const char* aSessionId, 463 uint32_t aSessionIdLength) { 464 CK_LOGD("ClearKeySessionManager::RemoveSession"); 465 466 // Copy the sessionId into a string so it can be captured for the lambda. 467 string sessionId(aSessionId, aSessionId + aSessionIdLength); 468 469 // Hold a reference to the SessionManager, so it isn't released before we 470 // try and use it. 471 RefPtr<ClearKeySessionManager> self(this); 472 function<void()> deferrer = [self, aPromiseId, sessionId]() { 473 self->RemoveSession(aPromiseId, sessionId.data(), sessionId.size()); 474 }; 475 476 // If we haven't fully loaded, defer calling this method. 477 if (MaybeDeferTillInitialized(std::move(deferrer))) { 478 CK_LOGD("Deferring RemoveSession"); 479 return; 480 } 481 482 // Check that the SessionManager has not been shutdown before we try and 483 // resolve any promises. 484 if (!mHost) { 485 return; 486 } 487 488 auto itr = mSessions.find(sessionId); 489 if (itr == mSessions.end()) { 490 CK_LOGW("ClearKey CDM couldn't remove non-existent session."); 491 492 mHost->OnRejectPromise(aPromiseId, Exception::kExceptionTypeError, 0, 493 nullptr, 0); 494 495 return; 496 } 497 498 ClearKeySession* session = itr->second; 499 assert(session); 500 string sid = session->Id(); 501 bool isPersistent = session->Type() == SessionType::kPersistentLicense; 502 ClearInMemorySessionData(session); 503 504 if (!isPersistent) { 505 mHost->OnResolvePromise(aPromiseId); 506 return; 507 } 508 509 mPersistence->PersistentSessionRemoved(sid); 510 511 vector<uint8_t> emptyKeydata; 512 513 function<void()> resolve = [self, aPromiseId]() { 514 if (!self->mHost) { 515 return; 516 } 517 self->mHost->OnResolvePromise(aPromiseId); 518 }; 519 520 function<void()> reject = [self, aPromiseId]() { 521 if (!self->mHost) { 522 return; 523 } 524 static const char* message = "Could not remove session"; 525 self->mHost->OnRejectPromise(aPromiseId, Exception::kExceptionTypeError, 0, 526 message, strlen(message)); 527 }; 528 529 WriteData(mHost, sessionId, emptyKeydata, std::move(resolve), 530 std::move(reject)); 531 } 532 533 void ClearKeySessionManager::SetServerCertificate(uint32_t aPromiseId, 534 const uint8_t* aServerCert, 535 uint32_t aServerCertSize) { 536 // ClearKey CDM doesn't support this method by spec. 537 CK_LOGD("ClearKeySessionManager::SetServerCertificate"); 538 mHost->OnRejectPromise(aPromiseId, Exception::kExceptionNotSupportedError, 0, 539 nullptr /* message */, 0 /* messageLen */); 540 } 541 542 Status ClearKeySessionManager::Decrypt(const InputBuffer_2& aBuffer, 543 DecryptedBlock* aDecryptedBlock) { 544 CK_LOGD("ClearKeySessionManager::Decrypt"); 545 546 CK_LOGARRAY("Key: ", aBuffer.key_id, aBuffer.key_id_size); 547 548 Buffer* buffer = mHost->Allocate(aBuffer.data_size); 549 assert(buffer != nullptr); 550 assert(buffer->Data() != nullptr); 551 assert(buffer->Capacity() >= aBuffer.data_size); 552 553 memcpy(buffer->Data(), aBuffer.data, aBuffer.data_size); 554 555 Status status = Status::kSuccess; 556 // According to the comment `If |iv_size| = 0, the data is unencrypted.` 557 // Use iv_size to determine if the sample is encrypted. 558 if (aBuffer.iv_size != 0) { 559 status = mDecryptionManager->Decrypt(buffer->Data(), buffer->Size(), 560 CryptoMetaData(&aBuffer)); 561 } 562 563 aDecryptedBlock->SetDecryptedBuffer(buffer); 564 aDecryptedBlock->SetTimestamp(aBuffer.timestamp); 565 566 return status; 567 } 568 569 void ClearKeySessionManager::DecryptingComplete() { 570 CK_LOGD("ClearKeySessionManager::DecryptingComplete %p", this); 571 572 for (auto it = mSessions.begin(); it != mSessions.end(); it++) { 573 delete it->second; 574 } 575 mSessions.clear(); 576 mLastSessionId = std::nullopt; 577 578 mDecryptionManager = nullptr; 579 mHost = nullptr; 580 581 Release(); 582 } 583 584 bool ClearKeySessionManager::MaybeDeferTillInitialized( 585 function<void()>&& aMaybeDefer) { 586 if (mPersistence->IsLoaded()) { 587 return false; 588 } 589 590 mDeferredInitialize.emplace(std::move(aMaybeDefer)); 591 return true; 592 } 593 594 void ClearKeySessionManager::OnQueryOutputProtectionStatus( 595 QueryResult aResult, uint32_t aLinkMask, uint32_t aOutputProtectionMask) { 596 MOZ_ASSERT(mHasOutstandingOutputProtectionQuery, 597 "Should only be called if a query is outstanding"); 598 CK_LOGD("ClearKeySessionManager::OnQueryOutputProtectionStatus"); 599 mHasOutstandingOutputProtectionQuery = false; 600 601 if (aResult == QueryResult::kQueryFailed) { 602 // Indicate the query failed. This can happen if we're in shutdown. 603 NotifyOutputProtectionStatus(KeyStatus::kInternalError); 604 return; 605 } 606 607 if (aLinkMask & OutputLinkTypes::kLinkTypeNetwork) { 608 NotifyOutputProtectionStatus(KeyStatus::kOutputRestricted); 609 return; 610 } 611 612 NotifyOutputProtectionStatus(KeyStatus::kUsable); 613 } 614 615 void ClearKeySessionManager::QueryOutputProtectionStatusIfNeeded() { 616 MOZ_ASSERT( 617 mHost, 618 "Should not query protection status if we're shutdown (mHost == null)!"); 619 CK_LOGD( 620 "ClearKeySessionManager::UpdateOutputProtectionStatusAndQueryIfNeeded"); 621 if (mLastOutputProtectionQueryTime.IsNull()) { 622 // We haven't perfomed a check yet, get a query going. 623 MOZ_ASSERT( 624 !mHasOutstandingOutputProtectionQuery, 625 "Shouldn't have an outstanding query if we haven't recorded a time"); 626 QueryOutputProtectionStatusFromHost(); 627 return; 628 } 629 630 MOZ_ASSERT(!mLastOutputProtectionQueryTime.IsNull(), 631 "Should have already handled the case where we don't yet have a " 632 "previous check time"); 633 const mozilla::TimeStamp now = mozilla::TimeStamp::NowLoRes(); 634 const mozilla::TimeDuration timeSinceQuery = 635 now - mLastOutputProtectionQueryTime; 636 637 // The time between output protection checks to the host. I.e. if this amount 638 // of time has passed since the last check with the host, another should be 639 // performed (provided the first check has been handled). 640 static const mozilla::TimeDuration kOutputProtectionQueryInterval = 641 mozilla::TimeDuration::FromSeconds(0.2); 642 // The number of kOutputProtectionQueryInterval intervals we can miss before 643 // we decide a check has failed. I.e. if this value is 2, if we have not 644 // received a reply to a check after kOutputProtectionQueryInterval * 2 645 // time, we consider the check failed. 646 constexpr uint32_t kMissedIntervalsBeforeFailure = 2; 647 // The length of time after which we will restrict output until we get a 648 // query response. 649 static const mozilla::TimeDuration kTimeToWaitBeforeFailure = 650 kOutputProtectionQueryInterval * kMissedIntervalsBeforeFailure; 651 652 if ((timeSinceQuery > kOutputProtectionQueryInterval) && 653 !mHasOutstandingOutputProtectionQuery) { 654 // We don't have an outstanding query and enough time has passed we should 655 // query again. 656 QueryOutputProtectionStatusFromHost(); 657 return; 658 } 659 660 if ((timeSinceQuery > kTimeToWaitBeforeFailure) && 661 mHasOutstandingOutputProtectionQuery) { 662 // A reponse was not received fast enough, notify. 663 NotifyOutputProtectionStatus(KeyStatus::kInternalError); 664 } 665 } 666 667 void ClearKeySessionManager::QueryOutputProtectionStatusFromHost() { 668 MOZ_ASSERT( 669 mHost, 670 "Should not query protection status if we're shutdown (mHost == null)!"); 671 CK_LOGD("ClearKeySessionManager::QueryOutputProtectionStatusFromHost"); 672 if (mHost) { 673 mLastOutputProtectionQueryTime = mozilla::TimeStamp::NowLoRes(); 674 mHost->QueryOutputProtectionStatus(); 675 mHasOutstandingOutputProtectionQuery = true; 676 } 677 } 678 679 void ClearKeySessionManager::NotifyOutputProtectionStatus(KeyStatus aStatus) { 680 MOZ_ASSERT(aStatus == KeyStatus::kUsable || 681 aStatus == KeyStatus::kOutputRestricted || 682 aStatus == KeyStatus::kInternalError, 683 "aStatus should have an expected value"); 684 CK_LOGD("ClearKeySessionManager::NotifyOutputProtectionStatus"); 685 if (!mLastSessionId.has_value()) { 686 // If we don't have a session id, either because we're too early, or are 687 // shutting down, don't notify. 688 return; 689 } 690 691 string& lastSessionId = mLastSessionId.value(); 692 693 // Use 'output-protection' as the key ID. This helps tests disambiguate key 694 // status updates related to this. 695 const uint8_t kKeyId[] = {'o', 'u', 't', 'p', 'u', 't', '-', 'p', 'r', 696 'o', 't', 'e', 'c', 't', 'i', 'o', 'n'}; 697 KeyInformation keyInfo = {}; 698 keyInfo.key_id = kKeyId; 699 keyInfo.key_id_size = std::size(kKeyId); 700 keyInfo.status = aStatus; 701 702 vector<KeyInformation> keyInfos; 703 keyInfos.push_back(keyInfo); 704 705 // At time of writing, Gecko's higher level handling doesn't use this arg. 706 // However, we set it to false to mimic Chromium's similar case. Since 707 // Clearkey is used to test the Chromium CDM path, it doesn't hurt to try 708 // and mimic their behaviour. 709 bool hasAdditionalUseableKey = false; 710 mHost->OnSessionKeysChange(lastSessionId.c_str(), lastSessionId.size(), 711 hasAdditionalUseableKey, keyInfos.data(), 712 keyInfos.size()); 713 }