tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }