// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/cdm/library_cdm/clear_key_cdm/clear_key_cdm.h"

#include <algorithm>
#include <cstdint>
#include <cstring>
#include <memory>
#include <optional>
#include <sstream>
#include <utility>

#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/containers/to_vector.h"
#include "base/files/file.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/notreached.h"
#include "base/strings/string_number_conversions.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "media/base/cdm_callback_promise.h"
#include "media/base/cdm_key_information.h"
#include "media/base/decoder_buffer.h"
#include "media/base/decrypt_config.h"
#include "media/base/encryption_pattern.h"
#include "media/base/media.h"
#include "media/cdm/api/content_decryption_module_ext.h"
#include "media/cdm/cdm_type_conversion.h"
#include "media/cdm/clear_key_cdm_common.h"
#include "media/cdm/json_web_key.h"
#include "media/cdm/library_cdm/cdm_host_proxy.h"
#include "media/cdm/library_cdm/cdm_host_proxy_impl.h"
#include "media/cdm/library_cdm/clear_key_cdm/cdm_file_io_test.h"
#include "media/cdm/library_cdm/clear_key_cdm/cdm_video_decoder.h"
#include "media/media_buildflags.h"

#if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER)
#include "base/at_exit.h"
#include "base/files/file_path.h"
#include "base/path_service.h"
#include "media/cdm/library_cdm/clear_key_cdm/ffmpeg_cdm_audio_decoder.h"

#if !defined COMPONENT_BUILD
static base::AtExitManager g_at_exit_manager;
#endif
#endif  // CLEAR_KEY_CDM_USE_FFMPEG_DECODER

constexpr char kClearKeyCdmVersion[] = "0.1.0.1";

// Variants of External Clear Key key system to test different scenarios.

constexpr int64_t kMaxTimerDelayMs = base::Seconds(5).InMilliseconds();

// CDM unit test result header. Must be in sync with UNIT_TEST_RESULT_HEADER in
// media/test/data/eme_player_js/globals.js.
constexpr char kUnitTestResultHeader[] = "UNIT_TEST_RESULT";

constexpr char kDummyIndividualizationRequest[] =
    "dummy individualization request";

static bool g_is_cdm_module_initialized = false;

// Creates a DecoderBuffer from |input_buffer|. If the |input_buffer| is empty,
// an empty (end-of-stream) DecoderBuffer is returned.
static scoped_refptr<media::DecoderBuffer> DecoderBufferFrom(
    const cdm::InputBuffer_2& input_buffer) {
  if (!input_buffer.data) {
    CHECK(!input_buffer.data_size);
    return media::DecoderBuffer::CreateEOSBuffer();
  }
  auto input_buffer_span = media::AsSpan(&input_buffer);

  // Take |input_buffer|'s underlying memory and store it into |output_buffer|.
  // This is safe because this method is only used in Decrypt(). Decrypt() is
  // called synchronously and |input_buffer| and |output_buffer| will get
  // destroyed when Decrypt() goes out of scope.
  auto external_memory =
      std::make_unique<media::DecoderBuffer::UnownedExternalMemory>(
          input_buffer_span);
  scoped_refptr<media::DecoderBuffer> output_buffer =
      media::DecoderBuffer::FromExternalMemory(std::move(external_memory));
  output_buffer->set_timestamp(base::Microseconds(input_buffer.timestamp));

  if (input_buffer.encryption_scheme == cdm::EncryptionScheme::kUnencrypted)
    return output_buffer;

  DCHECK_GT(input_buffer.iv_size, 0u);
  DCHECK_GT(input_buffer.key_id_size, 0u);
  auto subsample_span = media::SubsamplesFrom(&input_buffer);
  std::vector<media::SubsampleEntry> subsamples;
  for (const cdm::SubsampleEntry& subsample : subsample_span) {
    subsamples.emplace_back(subsample.clear_bytes, subsample.cipher_bytes);
  }

  const std::string key_id_string(
      reinterpret_cast<const char*>(input_buffer.key_id),
      input_buffer.key_id_size);
  const std::string iv_string(reinterpret_cast<const char*>(input_buffer.iv),
                              input_buffer.iv_size);
  if (input_buffer.encryption_scheme == cdm::EncryptionScheme::kCenc) {
    output_buffer->set_decrypt_config(media::DecryptConfig::CreateCencConfig(
        key_id_string, iv_string, subsamples));
  } else {
    DCHECK_EQ(input_buffer.encryption_scheme, cdm::EncryptionScheme::kCbcs);
    auto pattern =
        media::EncryptionPattern::Create(input_buffer.pattern.crypt_byte_block,
                                         input_buffer.pattern.skip_byte_block);
    if (!pattern) {
      return nullptr;
    }
    output_buffer->set_decrypt_config(media::DecryptConfig::CreateCbcsConfig(
        key_id_string, iv_string, subsamples, *pattern));
  }

  return output_buffer;
}

static std::string GetUnitTestResultMessage(bool success) {
  std::string message(kUnitTestResultHeader);
  message += success ? '1' : '0';
  return message;
}

// Shallow copy all the key information from |keys_info| into |keys_vector|.
// |keys_vector| is only valid for the lifetime of |keys_info| because it
// contains pointers into the latter.
void ConvertCdmKeysInfo(const media::CdmKeysInfo& keys_info,
                        std::vector<cdm::KeyInformation_2>* keys_vector) {
  keys_vector->reserve(keys_info.size());
  for (const auto& key_info : keys_info) {
    cdm::KeyInformation_2 key = {};
    key.key_id = key_info->key_id.data();
    key.key_id_size = key_info->key_id.size();
    key.status = ToCdmKeyStatus_2(key_info->status);
    key.system_code = key_info->system_code;
    keys_vector->push_back(key);
  }
}

void INITIALIZE_CDM_MODULE() {
  DVLOG(1) << __func__;
  media::InitializeMediaLibrary();
  g_is_cdm_module_initialized = true;
}

void DeinitializeCdmModule() {
  DVLOG(1) << __func__;
}

void* CreateCdmInstance(int cdm_interface_version,
                        const char* key_system,
                        uint32_t key_system_size,
                        GetCdmHostFunc get_cdm_host_func,
                        void* user_data) {
  DVLOG(1) << "CreateCdmInstance()";

  if (!g_is_cdm_module_initialized) {
    DVLOG(1) << "CDM module not initialized.";
    return nullptr;
  }

  std::string key_system_string(key_system, key_system_size);
  if (key_system_string != media::kExternalClearKeyKeySystem &&
      key_system_string != media::kExternalClearKeyDecryptOnlyKeySystem &&
      key_system_string != media::kExternalClearKeyMessageTypeTestKeySystem &&
      key_system_string != media::kExternalClearKeyFileIOTestKeySystem &&
      key_system_string !=
          media::kExternalClearKeyOutputProtectionTestKeySystem &&
      key_system_string !=
          media::kExternalClearKeyPlatformVerificationTestKeySystem &&
      key_system_string != media::kExternalClearKeyCrashKeySystem &&
      key_system_string != media::kExternalClearKeyVerifyCdmHostTestKeySystem &&
      key_system_string != media::kExternalClearKeyStorageIdTestKeySystem &&
      key_system_string !=
          media::kExternalClearKeyDifferentCdmTypeTestKeySystem) {
    DVLOG(1) << "Unsupported key system:" << key_system_string;
    return nullptr;
  }

  // We support CDM_10, CDM_11, and CDM_12.
  using CDM_10 = cdm::ContentDecryptionModule_10;
  using CDM_11 = cdm::ContentDecryptionModule_11;
  using CDM_12 = cdm::ContentDecryptionModule_12;

  if (cdm_interface_version == CDM_10::kVersion) {
    static_assert(CDM_10::kVersion == CDM_10::Host::kVersion,
                  "CDM host version mismatch");
    CDM_10::Host* host = static_cast<CDM_10::Host*>(
        get_cdm_host_func(CDM_10::Host::kVersion, user_data));
    if (!host)
      return nullptr;

    DVLOG(1) << __func__ << ": Create ClearKeyCdm with CDM_10::Host.";
    return static_cast<CDM_10*>(
        new media::ClearKeyCdm(host, key_system_string));
  }

  if (cdm_interface_version == CDM_11::kVersion) {
    static_assert(CDM_11::kVersion == CDM_11::Host::kVersion,
                  "CDM host version mismatch");
    CDM_11::Host* host = static_cast<CDM_11::Host*>(
        get_cdm_host_func(CDM_11::Host::kVersion, user_data));
    if (!host)
      return nullptr;

    DVLOG(1) << __func__ << ": Create ClearKeyCdm with CDM_11::Host.";
    return static_cast<CDM_11*>(
        new media::ClearKeyCdm(host, key_system_string));
  }

  if (cdm_interface_version == CDM_12::kVersion) {
    CDM_12::Host* host = static_cast<CDM_12::Host*>(
        get_cdm_host_func(CDM_12::Host::kVersion, user_data));
    if (!host) {
      return nullptr;
    }

    DVLOG(1) << __func__ << ": Create ClearKeyCdm with CDM_12::Host.";
    return static_cast<CDM_12*>(
        new media::ClearKeyCdm(host, key_system_string));
  }

  return nullptr;
}

const char* GetCdmVersion() {
  return kClearKeyCdmVersion;
}

static bool g_verify_host_files_result = false;

// Makes sure files and corresponding signature files are readable but not
// writable.
bool VerifyCdmHost_0(const cdm::HostFile* host_files, uint32_t num_files) {
  LOG(WARNING) << __func__ << ": " << num_files;
  // SAFETY: This is a C API, can't have spans:
  // https://source.chromium.org/chromium/chromium/src/+/main:media/cdm/api/content_decryption_module_ext.h;l=56;drc=33685ef0a89c1eb9c61f1819dc0029594a53cefb.
  auto host_files_span = UNSAFE_BUFFERS(base::span(host_files, num_files));

  // We should always have the CDM and at least one common file.
  // The common CDM host file (e.g. chrome) might not exist since we are running
  // in browser_tests.
  const uint32_t kMinNumHostFiles = 2;

  // We should always have the CDM.
  const int kNumCdmFiles = 1;

  if (num_files < kMinNumHostFiles) {
    LOG(ERROR) << "Too few host files: " << num_files;
    g_verify_host_files_result = false;
    return true;
  }

  int num_opened_files = 0;
  for (const cdm::HostFile& host_file : host_files_span) {
    base::File file(static_cast<base::PlatformFile>(host_file.file));
    if (!file.IsValid()) {
      continue;
    }

    num_opened_files++;
    constexpr int kBytesToRead = 10;
    std::array<uint8_t, kBytesToRead> buffer;
    const std::optional<size_t> bytes_read = file.Read(0, buffer);
    if (!bytes_read.has_value() || (*bytes_read != kBytesToRead)) {
      LOG(ERROR) << "File bytes read: " << bytes_read.value_or(0);
      g_verify_host_files_result = false;
      return true;
    }

    // TODO(xhwang): Check that the files are not writable.
    // TODO(xhwang): Also verify the signature file when it's available.
  }

  // We should always have CDM files opened.
  if (num_opened_files < kNumCdmFiles) {
    LOG(ERROR) << "Too few opened files: " << num_opened_files;
    g_verify_host_files_result = false;
    return true;
  }

  g_verify_host_files_result = true;
  return true;
}

namespace media {

namespace {

// See ISO 23001-8:2016, section 7. Value 2 means "Unspecified".
constexpr cdm::ColorSpace kUnspecifiedColorSpace = {2, 2, 2,
                                                    cdm::ColorRange::kInvalid};

cdm::VideoDecoderConfig_3 ToVideoDecoderConfig_3(
    cdm::VideoDecoderConfig_2 config) {
  cdm::VideoDecoderConfig_3 result = {
      config.codec,           config.profile,          config.format,
      kUnspecifiedColorSpace, config.coded_size,       config.extra_data,
      config.extra_data_size, config.encryption_scheme};
  return result;
}

// Adapting a cdm::VideoFrame to a cdm::VideoFrame_2 interface. Simply pass all
// calls through except for SetColorSpace() and ColorSpace().
class CdmVideoFrameAdapter : public cdm::VideoFrame_2 {
 public:
  explicit CdmVideoFrameAdapter(cdm::VideoFrame* video_frame)
      : video_frame_(video_frame) {}

  // cdm::VideoFrame_2 implementation.
  void SetFormat(cdm::VideoFormat format) final {
    video_frame_->SetFormat(format);
  }
  void SetSize(cdm::Size size) final { video_frame_->SetSize(size); }
  void SetFrameBuffer(cdm::Buffer* frame_buffer) final {
    video_frame_->SetFrameBuffer(frame_buffer);
  }
  void SetPlaneOffset(cdm::VideoPlane plane, uint32_t offset) final {
    video_frame_->SetPlaneOffset(plane, offset);
  }
  void SetStride(cdm::VideoPlane plane, uint32_t stride) final {
    video_frame_->SetStride(plane, stride);
  }
  void SetTimestamp(int64_t timestamp) final {
    video_frame_->SetTimestamp(timestamp);
  }
  void SetColorSpace(cdm::ColorSpace color_space) final {
    // Do nothing since cdm::VideoFrame does not support colorspace.
  }

 private:
  const raw_ptr<cdm::VideoFrame> video_frame_ = nullptr;
};

}  // namespace

template <typename HostInterface>
ClearKeyCdm::ClearKeyCdm(HostInterface* host, const std::string& key_system)
    : cdm_host_proxy_(std::make_unique<CdmHostProxyImpl<HostInterface>>(host)),
      cdm_(base::MakeRefCounted<ClearKeyPersistentSessionCdm>(
          cdm_host_proxy_.get(),
          base::BindRepeating(&ClearKeyCdm::OnSessionMessage,
                              base::Unretained(this)),
          base::BindRepeating(&ClearKeyCdm::OnSessionClosed,
                              base::Unretained(this)),
          base::BindRepeating(&ClearKeyCdm::OnSessionKeysChange,
                              base::Unretained(this)),
          base::BindRepeating(&ClearKeyCdm::OnSessionExpirationUpdate,
                              base::Unretained(this)))),
      key_system_(key_system) {
  CHECK(g_is_cdm_module_initialized);
}

ClearKeyCdm::~ClearKeyCdm() = default;

void ClearKeyCdm::Initialize(bool allow_distinctive_identifier,
                             bool allow_persistent_state,
                             bool /* use_hw_secure_codecs */) {
  // Implementation doesn't use distinctive identifier and will only need
  // to check persistent state permission.
  allow_persistent_state_ = allow_persistent_state;

  cdm_host_proxy_->OnInitialized(true);
}

void ClearKeyCdm::GetStatusForPolicy(uint32_t promise_id,
                                     const cdm::Policy& policy) {
  // Pretend the device is HDCP 2.0 compliant.
  const cdm::HdcpVersion kDeviceHdcpVersion = cdm::kHdcpVersion2_0;

  if (policy.min_hdcp_version <= kDeviceHdcpVersion) {
    cdm_host_proxy_->OnResolveKeyStatusPromise(promise_id,
                                               cdm::KeyStatus_2::kUsable);
    return;
  }

  cdm_host_proxy_->OnResolveKeyStatusPromise(
      promise_id, cdm::KeyStatus_2::kOutputRestricted);
}

void ClearKeyCdm::CreateSessionAndGenerateRequest(
    uint32_t promise_id,
    cdm::SessionType session_type,
    cdm::InitDataType init_data_type,
    const uint8_t* init_data,
    uint32_t init_data_size) {
  DVLOG(1) << __func__;

  if (session_type != cdm::kTemporary && !allow_persistent_state_) {
    OnPromiseFailed(promise_id, CdmPromise::Exception::INVALID_STATE_ERROR, 0,
                    "Persistent state not allowed.");
    return;
  }
  // SAFETY: These are defined from a vector here:
  // https://source.chromium.org/chromium/chromium/src/+/main:media/cdm/cdm_adapter.cc;l=384;drc=8bd3d24cc3bdbffe564eeeca112a6744e6766b17
  auto init_data_vector = UNSAFE_BUFFERS(
      std::vector<uint8_t>(init_data, init_data + init_data_size));
  auto promise = std::make_unique<CdmCallbackPromise<std::string>>(
      base::BindOnce(&ClearKeyCdm::OnSessionCreated, base::Unretained(this),
                     promise_id),
      base::BindOnce(&ClearKeyCdm::OnPromiseFailed, base::Unretained(this),
                     promise_id));
  cdm_host_proxy_->ReportMetrics(cdm::kSdkVersion, 12345);
  cdm_->CreateSessionAndGenerateRequest(
      ToMediaSessionType(session_type), ToEmeInitDataType(init_data_type),
      std::move(init_data_vector), std::move(promise));

  // Run unit tests if applicable. Unit test results are reported in the form of
  // a session message. Therefore it can only be called after session creation.
  if (key_system_ == kExternalClearKeyFileIOTestKeySystem) {
    StartFileIOTest();
  } else if (key_system_ == kExternalClearKeyOutputProtectionTestKeySystem) {
    StartOutputProtectionTest();
  } else if (key_system_ ==
             kExternalClearKeyPlatformVerificationTestKeySystem) {
    StartPlatformVerificationTest();
  } else if (key_system_ == kExternalClearKeyVerifyCdmHostTestKeySystem) {
    ReportVerifyCdmHostTestResult();
  } else if (key_system_ == kExternalClearKeyStorageIdTestKeySystem) {
    StartStorageIdTest();
  } else if (key_system_ == kExternalClearKeyKeySystem) {
    // Normally, we would report these metrics after decoding or key system
    // initialization, but for clear key tests, we only want to verify plumbing.
    cdm_host_proxy_->ReportMetrics(cdm::kDecoderCheck1SuccessCount, 1);
    cdm_host_proxy_->ReportMetrics(cdm::kKeySystemDataTime1, 111);
    cdm_host_proxy_->ReportMetrics(cdm::kKeySystemDataTime2, 222);
    cdm_host_proxy_->ReportMetrics(cdm::kKeySystemDataTime3, 333);
    cdm_host_proxy_->ReportMetrics(cdm::kKeySystemDataBool1, 1);
  }
}

void ClearKeyCdm::LoadSession(uint32_t promise_id,
                              cdm::SessionType session_type,
                              const char* session_id,
                              uint32_t session_id_length) {
  DVLOG(1) << __func__;
  DCHECK_EQ(session_type, cdm::kPersistentLicense);
  CHECK(allow_persistent_state_);
  std::string web_session_str(session_id, session_id_length);

  auto promise = std::make_unique<CdmCallbackPromise<std::string>>(
      base::BindOnce(&ClearKeyCdm::OnSessionCreated, base::Unretained(this),
                     promise_id),
      base::BindOnce(&ClearKeyCdm::OnPromiseFailed, base::Unretained(this),
                     promise_id));
  cdm_->LoadSession(ToMediaSessionType(session_type),
                    std::move(web_session_str), std::move(promise));
}

void ClearKeyCdm::UpdateSession(uint32_t promise_id,
                                const char* session_id,
                                uint32_t session_id_length,
                                const uint8_t* response,
                                uint32_t response_size) {
  DVLOG(1) << __func__;
  std::string web_session_str(session_id, session_id_length);
  // SAFETY: `response` and `response_size` must be in conformance.
  auto response_copy =
      UNSAFE_BUFFERS(std::vector<uint8_t>(response, response + response_size));
  auto promise = std::make_unique<CdmCallbackPromise<>>(
      base::BindOnce(&ClearKeyCdm::OnUpdateSuccess, base::Unretained(this),
                     promise_id, web_session_str),
      base::BindOnce(&ClearKeyCdm::OnPromiseFailed, base::Unretained(this),
                     promise_id));

  cdm_->UpdateSession(session_id, std::move(response_copy), std::move(promise));
}

void ClearKeyCdm::OnUpdateSuccess(uint32_t promise_id,
                                  const std::string& session_id) {
  // Now create the expiration changed event.
  cdm::Time expiration = 0.0;  // Never expires.

  if (key_system_ == kExternalClearKeyMessageTypeTestKeySystem) {
    // For renewal key system, set a non-zero expiration that is approximately
    // 100 years after 01 January 1970 UTC.
    expiration = 3153600000.0;  // 100 * 365 * 24 * 60 * 60;

    if (!has_set_timer_) {
      // Make sure the CDM can get time and sleep if necessary.
      constexpr auto kSleepDuration = base::Seconds(1);
      auto start_time = base::Time::Now();
      base::PlatformThread::Sleep(kSleepDuration);
      auto time_elapsed = base::Time::Now() - start_time;
      CHECK_GE(time_elapsed, kSleepDuration);

      ScheduleNextTimer();
    }

    // Also send an individualization request if never sent before.
    if (!has_sent_individualization_request_) {
      has_sent_individualization_request_ = true;
      const std::string request = kDummyIndividualizationRequest;
      cdm_host_proxy_->OnSessionMessage(session_id.data(), session_id.length(),
                                        cdm::kIndividualizationRequest,
                                        request.data(), request.size());
    }
  }

  cdm_host_proxy_->OnExpirationChange(session_id.data(), session_id.length(),
                                      expiration);

  // Resolve the promise.
  OnPromiseResolved(promise_id);
}

void ClearKeyCdm::CloseSession(uint32_t promise_id,
                               const char* session_id,
                               uint32_t session_id_length) {
  DVLOG(1) << __func__;
  std::string web_session_str(session_id, session_id_length);

  auto promise = std::make_unique<CdmCallbackPromise<>>(
      base::BindOnce(&ClearKeyCdm::OnPromiseResolved, base::Unretained(this),
                     promise_id),
      base::BindOnce(&ClearKeyCdm::OnPromiseFailed, base::Unretained(this),
                     promise_id));
  cdm_->CloseSession(std::move(web_session_str), std::move(promise));
}

void ClearKeyCdm::RemoveSession(uint32_t promise_id,
                                const char* session_id,
                                uint32_t session_id_length) {
  DVLOG(1) << __func__;
  std::string web_session_str(session_id, session_id_length);

  auto promise = std::make_unique<CdmCallbackPromise<>>(
      base::BindOnce(&ClearKeyCdm::OnPromiseResolved, base::Unretained(this),
                     promise_id),
      base::BindOnce(&ClearKeyCdm::OnPromiseFailed, base::Unretained(this),
                     promise_id));
  cdm_->RemoveSession(std::move(web_session_str), std::move(promise));
}

void ClearKeyCdm::SetServerCertificate(uint32_t promise_id,
                                       const uint8_t* server_certificate_data,
                                       uint32_t server_certificate_data_size) {
  DVLOG(1) << __func__;
  auto promise = std::make_unique<CdmCallbackPromise<>>(
      base::BindOnce(&ClearKeyCdm::OnPromiseResolved, base::Unretained(this),
                     promise_id),
      base::BindOnce(&ClearKeyCdm::OnPromiseFailed, base::Unretained(this),
                     promise_id));
  // SAFETY: `server_certificate_data` must be in conformance with
  // `server_certificate_data_size`.
  cdm_->SetServerCertificate(
      UNSAFE_BUFFERS(std::vector<uint8_t>(
          server_certificate_data,
          server_certificate_data + server_certificate_data_size)),
      std::move(promise));
}

void ClearKeyCdm::TimerExpired(void* context) {
  DVLOG(1) << __func__;
  CHECK(has_set_timer_);
  std::string renewal_message;

  if (key_system_ == kExternalClearKeyMessageTypeTestKeySystem) {
    if (!next_renewal_message_.empty() &&
        context == &next_renewal_message_[0]) {
      renewal_message = next_renewal_message_;
    } else {
      renewal_message = "ERROR: Invalid timer context found!";
    }

    cdm_host_proxy_->OnSessionMessage(
        last_session_id_.data(), last_session_id_.length(),
        cdm::kLicenseRenewal, renewal_message.data(), renewal_message.length());
  } else if (key_system_ == kExternalClearKeyOutputProtectionTestKeySystem) {
    // Check output protection again.
    cdm_host_proxy_->QueryOutputProtectionStatus();
  }

  // Start the timer to schedule another timeout.
  ScheduleNextTimer();
}

static void CopyDecryptResults(Decryptor::Status* status_copy,
                               scoped_refptr<DecoderBuffer>* buffer_copy,
                               Decryptor::Status status,
                               scoped_refptr<DecoderBuffer> buffer) {
  *status_copy = status;
  *buffer_copy = std::move(buffer);
}

cdm::Status ClearKeyCdm::Decrypt(const cdm::InputBuffer_2& encrypted_buffer,
                                 cdm::DecryptedBlock* decrypted_block) {
  DVLOG(1) << __func__;
  CHECK(encrypted_buffer.data);

  scoped_refptr<DecoderBuffer> buffer;
  cdm::Status status = DecryptToMediaDecoderBuffer(encrypted_buffer, &buffer);

  if (status != cdm::kSuccess) {
    return status;
  }

  auto buffer_span = base::span(*buffer);
  CHECK(!buffer_span.empty());
  decrypted_block->SetDecryptedBuffer(
      cdm_host_proxy_->Allocate(buffer_span.size()));
  decrypted_block->DecryptedBuffer()->SetSize(buffer_span.size());
  base::span<uint8_t> decrypted_buffer_span =
      AsSpan(decrypted_block->DecryptedBuffer());
  decrypted_buffer_span.copy_from_nonoverlapping(buffer_span);
  decrypted_block->SetTimestamp(buffer->timestamp().InMicroseconds());

  return cdm::kSuccess;
}

cdm::Status ClearKeyCdm::InitializeAudioDecoder(
    const cdm::AudioDecoderConfig_2& audio_decoder_config) {
  if (key_system_ == kExternalClearKeyDecryptOnlyKeySystem) {
    return cdm::kInitializationError;
  }

#if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER)
  if (!audio_decoder_) {
    audio_decoder_ =
        std::make_unique<FFmpegCdmAudioDecoder>(cdm_host_proxy_.get());
  }

  if (!audio_decoder_->Initialize(audio_decoder_config))
    return cdm::kInitializationError;

  return cdm::kSuccess;
#else
  return cdm::kInitializationError;
#endif  // CLEAR_KEY_CDM_USE_FFMPEG_DECODER
}

cdm::Status ClearKeyCdm::InitializeVideoDecoder(
    const cdm::VideoDecoderConfig_2& video_decoder_config) {
  return InitializeVideoDecoder(ToVideoDecoderConfig_3(video_decoder_config));
}

cdm::Status ClearKeyCdm::InitializeVideoDecoder(
    const cdm::VideoDecoderConfig_3& video_decoder_config) {
  if (key_system_ == kExternalClearKeyDecryptOnlyKeySystem) {
    return cdm::kInitializationError;
  }

  if (!video_decoder_) {
    video_decoder_ = CreateVideoDecoder(cdm_host_proxy_.get(),
                                        video_decoder_config, &null_media_log_);
    if (!video_decoder_)
      return cdm::kInitializationError;
  }

  if (!video_decoder_->Initialize(video_decoder_config).is_ok())
    return cdm::kInitializationError;

  return cdm::kSuccess;
}

void ClearKeyCdm::ResetDecoder(cdm::StreamType decoder_type) {
  DVLOG(1) << __func__;
#if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER)
  switch (decoder_type) {
    case cdm::kStreamTypeVideo:
      video_decoder_->Reset();
      break;
    case cdm::kStreamTypeAudio:
      audio_decoder_->Reset();
      break;
    default:
      NOTREACHED() << "ResetDecoder(): invalid cdm::StreamType";
  }
#endif  // CLEAR_KEY_CDM_USE_FFMPEG_DECODER
}

void ClearKeyCdm::DeinitializeDecoder(cdm::StreamType decoder_type) {
  DVLOG(1) << __func__;
  switch (decoder_type) {
    case cdm::kStreamTypeVideo:
      video_decoder_->Deinitialize();
      break;
    case cdm::kStreamTypeAudio:
#if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER)
      audio_decoder_->Deinitialize();
#endif
      break;
    default:
      NOTREACHED() << "DeinitializeDecoder(): invalid cdm::StreamType";
  }
}

cdm::Status ClearKeyCdm::DecryptAndDecodeFrame(
    const cdm::InputBuffer_2& encrypted_buffer,
    cdm::VideoFrame* decoded_frame) {
  CdmVideoFrameAdapter adapted_frame(decoded_frame);
  return DecryptAndDecodeFrame(encrypted_buffer, &adapted_frame);
}

cdm::Status ClearKeyCdm::DecryptAndDecodeFrame(
    const cdm::InputBuffer_2& encrypted_buffer,
    cdm::VideoFrame_2* decoded_frame) {
  DVLOG(1) << __func__;
  TRACE_EVENT0("media", "ClearKeyCdm::DecryptAndDecodeFrame");

  scoped_refptr<DecoderBuffer> buffer;
  cdm::Status status = DecryptToMediaDecoderBuffer(encrypted_buffer, &buffer);

  if (status != cdm::kSuccess)
    return status;

  return video_decoder_->Decode(buffer, decoded_frame);
}

cdm::Status ClearKeyCdm::DecryptAndDecodeSamples(
    const cdm::InputBuffer_2& encrypted_buffer,
    cdm::AudioFrames* audio_frames) {
  DVLOG(1) << __func__;

  // Trigger a crash on purpose for testing purpose.
  // Only do this after a session has been created since the test also checks
  // that the session is properly closed.
  if (!last_session_id_.empty() &&
      key_system_ == kExternalClearKeyCrashKeySystem) {
    NOTREACHED() << "Crash in decrypt-and-decode with crash key system.";
  }

  scoped_refptr<DecoderBuffer> buffer;
  cdm::Status status = DecryptToMediaDecoderBuffer(encrypted_buffer, &buffer);

  if (status != cdm::kSuccess)
    return status;

#if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER)
  const uint8_t* data = nullptr;
  int32_t size = 0;
  int64_t timestamp = 0;
  if (!buffer->end_of_stream()) {
    auto buffer_span = base::span(*buffer);
    data = buffer_span.data();
    size = buffer_span.size();
    timestamp = encrypted_buffer.timestamp;
  }

  return audio_decoder_->DecodeBuffer(data, size, timestamp, audio_frames);
#else
  return cdm::kSuccess;
#endif  // CLEAR_KEY_CDM_USE_FFMPEG_DECODER
}

void ClearKeyCdm::Destroy() {
  DVLOG(1) << __func__;
  delete this;
}

void ClearKeyCdm::ScheduleNextTimer() {
  // Prepare the next renewal message and set timer. Renewal message is only
  // needed for the renewal test, and is ignored for other uses of the timer.
  std::ostringstream msg_stream;
  msg_stream << "Renewal from ClearKey CDM set at time "
             << base::Time::FromSecondsSinceUnixEpoch(
                    cdm_host_proxy_->GetCurrentWallTime())
             << ".";
  next_renewal_message_ = msg_stream.str();

  cdm_host_proxy_->SetTimer(timer_delay_ms_, &next_renewal_message_[0]);
  has_set_timer_ = true;

  // Use a smaller timer delay at start-up to facilitate testing. Increase the
  // timer delay up to a limit to avoid message spam.
  if (timer_delay_ms_ < kMaxTimerDelayMs)
    timer_delay_ms_ = std::min(2 * timer_delay_ms_, kMaxTimerDelayMs);
}

cdm::Status ClearKeyCdm::DecryptToMediaDecoderBuffer(
    const cdm::InputBuffer_2& encrypted_buffer,
    scoped_refptr<DecoderBuffer>* decrypted_buffer) {
  CHECK(decrypted_buffer);

  scoped_refptr<DecoderBuffer> buffer = DecoderBufferFrom(encrypted_buffer);

  // EOS and unencrypted streams can be returned as-is.
  if (buffer->end_of_stream() || !buffer->decrypt_config()) {
    *decrypted_buffer = std::move(buffer);
    return cdm::kSuccess;
  }

  // Callback is called synchronously, so we can use variables on the stack.
  Decryptor::Status status = Decryptor::kError;
  // The CDM does not care what the stream type is. Pass kVideo
  // for both audio and video decryption.
  cdm_->GetCdmContext()->GetDecryptor()->Decrypt(
      Decryptor::kVideo, std::move(buffer),
      base::BindOnce(&CopyDecryptResults, &status, decrypted_buffer));

  if (status == Decryptor::kError) {
    return cdm::kDecryptError;
  }

  if (status == Decryptor::kNoKey) {
    return cdm::kNoKey;
  }

  DCHECK_EQ(status, Decryptor::kSuccess);
  return cdm::kSuccess;
}

void ClearKeyCdm::OnPlatformChallengeResponse(
    const cdm::PlatformChallengeResponse& response) {
  DVLOG(1) << __func__;

  if (!is_running_platform_verification_test_) {
    NOTREACHED() << "OnPlatformChallengeResponse() called unexpectedly.";
  }

  is_running_platform_verification_test_ = false;

  // We are good as long as we get some response back. Ignore the challenge
  // response for now.
  // TODO(xhwang): Also test host challenge here.
  OnUnitTestComplete(true);
}

void ClearKeyCdm::OnQueryOutputProtectionStatus(
    cdm::QueryResult result,
    uint32_t link_mask,
    uint32_t output_protection_mask) {
  DVLOG(1) << __func__ << " result:" << result << ", link_mask:" << link_mask
           << ", output_protection_mask:" << output_protection_mask;

  if (!is_running_output_protection_test_) {
    NOTREACHED() << "OnQueryOutputProtectionStatus() called unexpectedly.";
  }

  // A session ID is needed, so use |last_session_id_|. However, if this is
  // called before a session has been created, we have no session to send this
  // to, so no event is generated. Note that this only works with a single
  // session, the same as renewal messages.
  if (last_session_id_.empty())
    return;

  // If the query succeeds and link mask contains kLinkTypeNetwork, send a
  // 'keystatuschange' event with a key marked as output-restricted. If the
  // query succeeds and link mask does not contain kLinkTypeNetwork, send a
  // 'keystatuschange' event with a key marked as usable. If the query failed,
  // send a 'keystatuschange' event with a key marked as internal-error. As
  // the JavaScript test doesn't check key IDs, use a dummy key ID.
  //
  // Note that QueryOutputProtectionStatus() is known to fail on Linux Chrome
  // OS builds, so the key status returned will be 'internal-error'.
  //
  // Note that this does not modify any keys, so if the caller does not check
  // the 'keystatuschange' event, nothing will happen as decoding will continue
  // to work.
  cdm::KeyStatus_2 key_status = cdm::KeyStatus_2::kInternalError;
  if (result == cdm::kQuerySucceeded) {
    key_status = (link_mask & cdm::kLinkTypeNetwork)
                     ? cdm::KeyStatus_2::kOutputRestricted
                     : cdm::KeyStatus_2::kUsable;
  }

  const uint8_t kDummyKeyId[] = {'d', 'u', 'm', 'm', 'y'};
  std::vector<cdm::KeyInformation_2> keys_vector = {
      {kDummyKeyId, std::size(kDummyKeyId), key_status, 0}};
  cdm_host_proxy_->OnSessionKeysChange(last_session_id_.data(),
                                       last_session_id_.length(), false,
                                       keys_vector.data(), keys_vector.size());
}

void ClearKeyCdm::OnStorageId(uint32_t version,
                              const uint8_t* storage_id,
                              uint32_t storage_id_size) {
  if (!is_running_storage_id_test_) {
    NOTREACHED() << "OnStorageId() called unexpectedly.";
  }

  is_running_storage_id_test_ = false;

  // SAFETY: CdmAdapter::OnStorageIdObtained guarantees that `storage_id` has a
  // size of `storage_id_size`.
  auto storage_id_span =
      UNSAFE_BUFFERS(base::span<const uint8_t>(storage_id, storage_id_size));
  DVLOG(1) << __func__ << ": storage_id (hex encoded) = "
           << (storage_id_size ? base::HexEncode(storage_id_span) : "<empty>");

#if BUILDFLAG(ENABLE_CDM_STORAGE_ID)
  // Storage Id is enabled, so something should be returned. It should be the
  // length of a SHA-256 hash (256 bits).
  constexpr uint32_t kExpectedStorageIdSizeInBytes = 256 / 8;
  OnUnitTestComplete(storage_id_size == kExpectedStorageIdSizeInBytes);
#else
  // Storage Id not available, so an empty vector should be returned.
  OnUnitTestComplete(storage_id_size == 0);
#endif
}

void ClearKeyCdm::OnSessionMessage(const std::string& session_id,
                                   CdmMessageType message_type,
                                   const std::vector<uint8_t>& message) {
  DVLOG(1) << __func__ << ": size = " << message.size();

  cdm_host_proxy_->OnSessionMessage(
      session_id.data(), session_id.length(), ToCdmMessageType(message_type),
      reinterpret_cast<const char*>(message.data()), message.size());
}

void ClearKeyCdm::OnSessionKeysChange(const std::string& session_id,
                                      bool has_additional_usable_key,
                                      CdmKeysInfo keys_info) {
  DVLOG(1) << __func__ << ": size = " << keys_info.size();

  // Crash if the special key ID "crash" is present.
  const std::vector<uint8_t> kCrashKeyId{'c', 'r', 'a', 's', 'h'};
  for (const auto& key_info : keys_info) {
    if (key_info->key_id == kCrashKeyId) {
      DVLOG(1) << __func__ << "Crash on special crash key ID.";
      base::ImmediateCrash();
    }
  }

  std::vector<cdm::KeyInformation_2> keys_vector;
  ConvertCdmKeysInfo(keys_info, &keys_vector);
  cdm_host_proxy_->OnSessionKeysChange(session_id.data(), session_id.length(),
                                       has_additional_usable_key,
                                       keys_vector.data(), keys_vector.size());
}

void ClearKeyCdm::OnSessionClosed(const std::string& session_id,
                                  CdmSessionClosedReason /*reason*/) {
  cdm_host_proxy_->OnSessionClosed(session_id.data(), session_id.length());
}

void ClearKeyCdm::OnSessionExpirationUpdate(const std::string& session_id,
                                            base::Time new_expiry_time) {
  DVLOG(1) << __func__ << ": expiry_time = " << new_expiry_time;
  cdm_host_proxy_->OnExpirationChange(
      session_id.data(), session_id.length(),
      new_expiry_time.InSecondsFSinceUnixEpoch());
}

void ClearKeyCdm::OnSessionCreated(uint32_t promise_id,
                                   const std::string& session_id) {
  // Save the latest session ID for renewal and file IO test messages.
  last_session_id_ = session_id;

  cdm_host_proxy_->OnResolveNewSessionPromise(promise_id, session_id.data(),
                                              session_id.length());
}

void ClearKeyCdm::OnPromiseResolved(uint32_t promise_id) {
  cdm_host_proxy_->OnResolvePromise(promise_id);
}

void ClearKeyCdm::OnPromiseFailed(uint32_t promise_id,
                                  CdmPromise::Exception exception_code,
                                  uint32_t system_code,
                                  const std::string& error_message) {
  DVLOG(1) << __func__ << ": error = " << error_message;
  cdm_host_proxy_->OnRejectPromise(promise_id, ToCdmException(exception_code),
                                   system_code, error_message.data(),
                                   error_message.length());
}

void ClearKeyCdm::OnUnitTestComplete(bool success) {
  std::string message = GetUnitTestResultMessage(success);
  cdm_host_proxy_->OnSessionMessage(
      last_session_id_.data(), last_session_id_.length(), cdm::kLicenseRequest,
      message.data(), message.length());
}

void ClearKeyCdm::StartFileIOTest() {
  file_io_test_runner_ = std::make_unique<FileIOTestRunner>(base::BindRepeating(
      &CdmHostProxy::CreateFileIO, base::Unretained(cdm_host_proxy_.get())));
  file_io_test_runner_->RunAllTests(base::BindOnce(
      &ClearKeyCdm::OnFileIOTestComplete, base::Unretained(this)));
}

void ClearKeyCdm::OnFileIOTestComplete(bool success) {
  DVLOG(1) << __func__ << ": " << success;
  OnUnitTestComplete(success);
  file_io_test_runner_.reset();
}

void ClearKeyCdm::StartOutputProtectionTest() {
  DVLOG(1) << __func__;
  is_running_output_protection_test_ = true;
  cdm_host_proxy_->QueryOutputProtectionStatus();

  // Also start the timer to run this periodically.
  ScheduleNextTimer();
}

void ClearKeyCdm::StartPlatformVerificationTest() {
  DVLOG(1) << __func__;
  is_running_platform_verification_test_ = true;

  std::string service_id = "test_service_id";
  std::string challenge = "test_challenge";

  cdm_host_proxy_->SendPlatformChallenge(service_id.data(), service_id.size(),
                                         challenge.data(), challenge.size());
}

void ClearKeyCdm::ReportVerifyCdmHostTestResult() {
  // VerifyCdmHost() should have already been called and test result stored
  // in |g_verify_host_files_result|.
  OnUnitTestComplete(g_verify_host_files_result);
}

void ClearKeyCdm::StartStorageIdTest() {
  DVLOG(1) << __func__;
  is_running_storage_id_test_ = true;

  // Request the latest available version.
  cdm_host_proxy_->RequestStorageId(0);
}

}  // namespace media
