// Copyright 2015 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/cdm_adapter.h"

#include <stddef.h>

#include <iomanip>
#include <memory>
#include <utility>

#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/containers/span_reader.h"
#include "base/debug/crash_logging.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/numerics/clamped_math.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "base/types/pass_key.h"
#include "media/base/audio_decoder_config.h"
#include "media/base/callback_registry.h"
#include "media/base/cdm_initialized_promise.h"
#include "media/base/cdm_key_information.h"
#include "media/base/decoder_buffer.h"
#include "media/base/decrypt_config.h"
#include "media/base/key_systems.h"
#include "media/base/limits.h"
#include "media/base/video_decoder_config.h"
#include "media/base/video_frame.h"
#include "media/base/video_util.h"
#include "media/cdm/cdm_helpers.h"
#include "media/cdm/cdm_type_conversion.h"
#include "media/cdm/cdm_wrapper.h"
#include "media/media_buildflags.h"
#include "ui/gfx/color_space.h"
#include "ui/gfx/geometry/rect.h"
#include "url/origin.h"

namespace media {

namespace {

// Constants for UMA reporting of file size (in KB) via
// UMA_HISTOGRAM_CUSTOM_COUNTS. Note that the histogram is log-scaled (rather
// than linear).
constexpr int kSizeKBMin = 1;
constexpr int kSizeKBMax = 512 * 1024;  // 512MB
constexpr int kSizeKBBuckets = 100;

// Only support version 1 of Storage Id. However, the "latest" version can also
// be requested.
constexpr uint32_t kRequestLatestStorageIdVersion = 0;
constexpr uint32_t kCurrentStorageIdVersion = 1;
static_assert(kCurrentStorageIdVersion < 0x80000000,
              "Versions 0x80000000 and above are reserved.");

// Verify that OutputProtection types matches those in CDM interface.
// Cannot use conversion function because these are used in bit masks.
// See CdmAdapter::EnableOutputProtection and
// CdmAdapter::OnQueryOutputProtectionStatusDone() below.
#define ASSERT_ENUM_EQ(media_enum, cdm_enum)                              \
  static_assert(                                                          \
      static_cast<int32_t>(media_enum) == static_cast<int32_t>(cdm_enum), \
      "Mismatched enum: " #media_enum " != " #cdm_enum)

ASSERT_ENUM_EQ(OutputProtection::LinkTypes::NONE, cdm::kLinkTypeNone);
ASSERT_ENUM_EQ(OutputProtection::LinkTypes::UNKNOWN, cdm::kLinkTypeUnknown);
ASSERT_ENUM_EQ(OutputProtection::LinkTypes::INTERNAL, cdm::kLinkTypeInternal);
ASSERT_ENUM_EQ(OutputProtection::LinkTypes::VGA, cdm::kLinkTypeVGA);
ASSERT_ENUM_EQ(OutputProtection::LinkTypes::HDMI, cdm::kLinkTypeHDMI);
ASSERT_ENUM_EQ(OutputProtection::LinkTypes::DVI, cdm::kLinkTypeDVI);
ASSERT_ENUM_EQ(OutputProtection::LinkTypes::DISPLAYPORT,
               cdm::kLinkTypeDisplayPort);
ASSERT_ENUM_EQ(OutputProtection::LinkTypes::NETWORK, cdm::kLinkTypeNetwork);
ASSERT_ENUM_EQ(OutputProtection::ProtectionType::NONE, cdm::kProtectionNone);
ASSERT_ENUM_EQ(OutputProtection::ProtectionType::HDCP, cdm::kProtectionHDCP);

std::string CdmStatusToString(cdm::Status status) {
  switch (status) {
    case cdm::kSuccess:
      return "kSuccess";
    case cdm::kNoKey:
      return "kNoKey";
    case cdm::kNeedMoreData:
      return "kNeedMoreData";
    case cdm::kDecryptError:
      return "kDecryptError";
    case cdm::kDecodeError:
      return "kDecodeError";
    case cdm::kInitializationError:
      return "kInitializationError";
    case cdm::kDeferredInitialization:
      return "kDeferredInitialization";
  }

  NOTREACHED();
}

inline std::ostream& operator<<(std::ostream& out, cdm::Status status) {
  return out << CdmStatusToString(status);
}

std::string GetHexKeyId(const cdm::InputBuffer_2& buffer) {
  if (buffer.key_id_size == 0)
    return "N/A";

  return base::HexEncode(KeyIdFrom(&buffer));
}

std::string GetHexMask(uint32_t mask) {
  std::stringstream hex_string;
  hex_string << "0x" << std::setfill('0') << std::setw(8) << std::hex << mask;
  return hex_string.str();
}

void* GetCdmHost(int host_interface_version, void* user_data) {
  if (!host_interface_version || !user_data)
    return nullptr;

  static_assert(
      CheckSupportedCdmHostVersions(cdm::Host_10::kVersion,
                                    cdm::Host_12::kVersion),
      "Mismatch between GetCdmHost() and IsSupportedCdmHostVersion()");

  CHECK(IsSupportedCdmHostVersion(host_interface_version));

  CdmAdapter* cdm_adapter = static_cast<CdmAdapter*>(user_data);
  DVLOG(1) << "Create CDM Host with version " << host_interface_version;
  switch (host_interface_version) {
    case cdm::Host_10::kVersion:
      return static_cast<cdm::Host_10*>(cdm_adapter);
    case cdm::Host_11::kVersion:
      return static_cast<cdm::Host_11*>(cdm_adapter);
    case cdm::Host_12::kVersion:
      return static_cast<cdm::Host_12*>(cdm_adapter);
    // When future Host versions are used, update to include them over here.
    // Older Chrome versions that don't support new host versions would return
    // nullptr.
    default:
      return nullptr;
  }
}

void ReportSystemCodeUMA(const std::string& key_system, uint32_t system_code) {
  base::UmaHistogramSparse(
      "Media.EME." +
          GetKeySystemNameForUMA(key_system, /*use_hw_secure_codecs=*/false) +
          ".SystemCode",
      system_code);
}

// These are reported to UMA server. Do not renumber or reuse values.
enum OutputProtectionStatus {
  kQueried = 0,
  kNoExternalLink = 1,
  kAllExternalLinksProtected = 2,
  // Note: Only add new values immediately before this line.
  kStatusCount
};

void ReportOutputProtectionUMA(OutputProtectionStatus status) {
  UMA_HISTOGRAM_ENUMERATION("Media.EME.OutputProtection", status,
                            OutputProtectionStatus::kStatusCount);
}

void ReportDecoderBypassBlockCountUMA(uint64_t bypass_count,
                                      uint64_t total_frames) {
  // Keep track of the average number of decoder bypass blocks per frame. As
  // `bypass_count` is typically expected to be low, multiply by 100. So if
  // `bypass_count` == `total_frames`, then the reported value would be 100.
  // Need to round-up so that if `bypass_count` is small then the value reported
  // is at least 1. UMA has a maximum value of 10000.
  constexpr std::string_view kDecoderBypassBlockCountUMAName =
      "Media.EME.DecoderBypassBlockCount";
  int report_result = 0;

  if (bypass_count > 0u && total_frames > 0u) {
    double ratio = (bypass_count * 100.0) / total_frames;
    report_result = base::ClampRound<int>(ratio);
    if (report_result == 0) {
      // As at least 1 bypass was recorded, make sure we don't record 0.
      report_result = 1;
    }
  }
  base::UmaHistogramCounts10000(kDecoderBypassBlockCountUMAName, report_result);
}

std::string_view GetDebuggerStatusMessage(bool is_debugger_attached) {
  return is_debugger_attached ? "debugger attached" : "debugger not attached";
}

crash_reporter::CrashKeyString<256> g_origin_crash_key("cdm-origin");

crash_reporter::CrashKeyString<32> g_debugger_attached("debugger-attached");

}  // namespace

// static
void CdmAdapter::Create(
    const CdmConfig& cdm_config,
    CreateCdmFunc create_cdm_func,
    std::unique_ptr<CdmAuxiliaryHelper> helper,
    const SessionMessageCB& session_message_cb,
    const SessionClosedCB& session_closed_cb,
    const SessionKeysChangeCB& session_keys_change_cb,
    const SessionExpirationUpdateCB& session_expiration_update_cb,
    CdmCreatedCB cdm_created_cb,
    bool is_debugger_attached) {
  CHECK(!cdm_config.key_system.empty());
  CHECK(session_message_cb);
  CHECK(session_closed_cb);
  CHECK(session_keys_change_cb);
  CHECK(session_expiration_update_cb);

  auto cdm = base::MakeRefCounted<CdmAdapter>(
      base::PassKey<CdmAdapter>(), cdm_config, create_cdm_func,
      std::move(helper), session_message_cb, session_closed_cb,
      session_keys_change_cb, session_expiration_update_cb,
      is_debugger_attached);

  // |cdm| ownership passed to the promise.
  cdm->Initialize(
      std::make_unique<CdmInitializedPromise>(std::move(cdm_created_cb), cdm));
}

CdmAdapter::CdmAdapter(
    base::PassKey<CdmAdapter>,
    const CdmConfig& cdm_config,
    CreateCdmFunc create_cdm_func,
    std::unique_ptr<CdmAuxiliaryHelper> helper,
    const SessionMessageCB& session_message_cb,
    const SessionClosedCB& session_closed_cb,
    const SessionKeysChangeCB& session_keys_change_cb,
    const SessionExpirationUpdateCB& session_expiration_update_cb,
    bool is_debugger_attached)
    : cdm_config_(cdm_config),
      create_cdm_func_(create_cdm_func),
      helper_(std::move(helper)),
      session_message_cb_(session_message_cb),
      session_closed_cb_(session_closed_cb),
      session_keys_change_cb_(session_keys_change_cb),
      session_expiration_update_cb_(session_expiration_update_cb),
      cdm_origin_(helper_->GetCdmOrigin()),
      scoped_crash_key_(&g_origin_crash_key, cdm_origin_.Serialize()),
      debugger_attached_crash_key_(
          &g_debugger_attached,
          GetDebuggerStatusMessage(is_debugger_attached)),
      task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()),
      pool_(base::MakeRefCounted<AudioBufferMemoryPool>()) {
  DVLOG(1) << __func__;

  CHECK(!cdm_config.key_system.empty());
  CHECK(create_cdm_func_);
  CHECK(helper_);
  CHECK(session_message_cb_);
  CHECK(session_closed_cb_);
  CHECK(session_keys_change_cb_);
  CHECK(session_expiration_update_cb_);

  cdm_metrics_data_.cdm_origin = cdm_origin_;

  helper_->SetFileReadCB(
      base::BindRepeating(&CdmAdapter::OnFileRead, weak_factory_.GetWeakPtr()));
}

CdmAdapter::~CdmAdapter() {
  DVLOG(1) << __func__;

  // Only Cdms using an interface version greater than 10 have access to the
  // ReportMetrics function, so to prevent from reporting a lot of metrics that
  // are left unset, check in the cases where if at least one of the CDM
  // values are set. Otherwise, too many impractical values will be reported.
  if (cdm_metrics_data_.IsCdmValueSet()) {
    helper_->RecordUkm(cdm_metrics_data_);
  }

  // Reject any outstanding promises and close all the existing sessions.
  cdm_promise_adapter_.Clear(CdmPromiseAdapter::ClearReason::kDestruction);

  if (audio_init_cb_)
    std::move(audio_init_cb_).Run(false);
  if (video_init_cb_)
    std::move(video_init_cb_).Run(false);
}

CdmWrapper* CdmAdapter::CreateCdmInstance() {
  CHECK(task_runner_->BelongsToCurrentThread());
  TRACE_EVENT0("media", "CdmAdapter::CreateCdmInstance");

  CdmWrapper* cdm =
      CdmWrapper::Create(create_cdm_func_, cdm_config_, GetCdmHost, this);
  DVLOG(1) << "CDM instance for " + cdm_config_.key_system +
                  (cdm ? "" : " could not be") + " created.";

  if (cdm) {
    // The interface version is relatively small. So using normal histogram
    // instead of a sparse histogram is okay. The following CHECK asserts this.
    CHECK(cdm->GetInterfaceVersion() <= 30);
    UMA_HISTOGRAM_ENUMERATION("Media.EME.CdmInterfaceVersion",
                              cdm->GetInterfaceVersion(), 30);
  }

  return cdm;
}

void CdmAdapter::Initialize(std::unique_ptr<media::SimpleCdmPromise> promise) {
  DVLOG(1) << __func__;
  TRACE_EVENT0("media", "CdmAdapter::Initialize");

  cdm_.reset(CreateCdmInstance());
  if (!cdm_) {
    promise->reject(CdmPromise::Exception::INVALID_STATE_ERROR, 0,
                    "Unable to create CDM.");
    return;
  }

  init_promise_id_ =
      cdm_promise_adapter_.SavePromise(std::move(promise), __func__);

  if (!cdm_->Initialize(cdm_config_)) {
    // OnInitialized() will not be called by the CDM, which is the case for
    // CDM interfaces prior to CDM_10.
    OnInitialized(true);
    return;
  }

  // OnInitialized() will be called by the CDM.
}

int CdmAdapter::GetInterfaceVersion() {
  return cdm_->GetInterfaceVersion();
}

void CdmAdapter::SetServerCertificate(
    const std::vector<uint8_t>& certificate,
    std::unique_ptr<SimpleCdmPromise> promise) {
  DVLOG(2) << __func__;
  CHECK(task_runner_->BelongsToCurrentThread());
  TRACE_EVENT0("media", "CdmAdapter::SetServerCertificate");

  if (certificate.size() < limits::kMinCertificateLength ||
      certificate.size() > limits::kMaxCertificateLength) {
    promise->reject(CdmPromise::Exception::TYPE_ERROR, 0,
                    "Incorrect certificate.");
    return;
  }

  uint32_t promise_id =
      cdm_promise_adapter_.SavePromise(std::move(promise), __func__);
  cdm_->SetServerCertificate(promise_id, certificate.data(),
                             certificate.size());
}

void CdmAdapter::GetStatusForPolicy(
    HdcpVersion min_hdcp_version,
    std::unique_ptr<KeyStatusCdmPromise> promise) {
  CHECK(task_runner_->BelongsToCurrentThread());
  TRACE_EVENT0("media", "CdmAdapter::GetStatusForPolicy");

  uint32_t promise_id =
      cdm_promise_adapter_.SavePromise(std::move(promise), __func__);
  DVLOG(2) << __func__ << ": promise_id = " << promise_id;
  if (!cdm_->GetStatusForPolicy(promise_id,
                                ToCdmHdcpVersion(min_hdcp_version))) {
    DVLOG(1) << __func__ << ": GetStatusForPolicy not supported";
    cdm_promise_adapter_.RejectPromise(
        promise_id, CdmPromise::Exception::NOT_SUPPORTED_ERROR, 0,
        "GetStatusForPolicy not supported.");
  }
}

void CdmAdapter::CreateSessionAndGenerateRequest(
    CdmSessionType session_type,
    EmeInitDataType init_data_type,
    const std::vector<uint8_t>& init_data,
    std::unique_ptr<NewSessionCdmPromise> promise) {
  CHECK(task_runner_->BelongsToCurrentThread());
  TRACE_EVENT0("media", "CdmAdapter::CreateSessionAndGenerateRequest");

  uint32_t promise_id =
      cdm_promise_adapter_.SavePromise(std::move(promise), __func__);
  DVLOG(2) << __func__ << ": promise_id = " << promise_id;

  cdm_->CreateSessionAndGenerateRequest(
      promise_id, ToCdmSessionType(session_type),
      ToCdmInitDataType(init_data_type), init_data.data(), init_data.size());
}

void CdmAdapter::LoadSession(CdmSessionType session_type,
                             const std::string& session_id,
                             std::unique_ptr<NewSessionCdmPromise> promise) {
  CHECK(task_runner_->BelongsToCurrentThread());
  TRACE_EVENT1("media", "CdmAdapter::LoadSession", "session_id", session_id);

  uint32_t promise_id =
      cdm_promise_adapter_.SavePromise(std::move(promise), __func__);
  DVLOG(2) << __func__ << ": session_id = " << session_id
           << ", promise_id = " << promise_id;

  cdm_->LoadSession(promise_id, ToCdmSessionType(session_type),
                    session_id.data(), session_id.size());
}

void CdmAdapter::UpdateSession(const std::string& session_id,
                               const std::vector<uint8_t>& response,
                               std::unique_ptr<SimpleCdmPromise> promise) {
  CHECK(task_runner_->BelongsToCurrentThread());
  CHECK(!session_id.empty());
  CHECK(!response.empty());
  TRACE_EVENT1("media", "CdmAdapter::UpdateSession", "session_id", session_id);

  cdm_metrics_data_.number_of_update_calls++;

  uint32_t promise_id =
      cdm_promise_adapter_.SavePromise(std::move(promise), __func__);
  DVLOG(2) << __func__ << ": session_id = " << session_id
           << ", promise_id = " << promise_id;

  cdm_->UpdateSession(promise_id, session_id.data(), session_id.size(),
                      response.data(), response.size());
}

void CdmAdapter::CloseSession(const std::string& session_id,
                              std::unique_ptr<SimpleCdmPromise> promise) {
  CHECK(task_runner_->BelongsToCurrentThread());
  CHECK(!session_id.empty());
  TRACE_EVENT1("media", "CdmAdapter::CloseSession", "session_id", session_id);

  uint32_t promise_id =
      cdm_promise_adapter_.SavePromise(std::move(promise), __func__);
  DVLOG(2) << __func__ << ": session_id = " << session_id
           << ", promise_id = " << promise_id;

  cdm_->CloseSession(promise_id, session_id.data(), session_id.size());
}

void CdmAdapter::RemoveSession(const std::string& session_id,
                               std::unique_ptr<SimpleCdmPromise> promise) {
  CHECK(task_runner_->BelongsToCurrentThread());
  CHECK(!session_id.empty());
  TRACE_EVENT1("media", "CdmAdapter::RemoveSession", "session_id", session_id);

  uint32_t promise_id =
      cdm_promise_adapter_.SavePromise(std::move(promise), __func__);
  DVLOG(2) << __func__ << ": session_id = " << session_id
           << ", promise_id = " << promise_id;

  cdm_->RemoveSession(promise_id, session_id.data(), session_id.size());
}

CdmContext* CdmAdapter::GetCdmContext() {
  CHECK(task_runner_->BelongsToCurrentThread());
  return this;
}

std::unique_ptr<CallbackRegistration> CdmAdapter::RegisterEventCB(
    EventCB event_cb) {
  return event_callbacks_.Register(std::move(event_cb));
}

Decryptor* CdmAdapter::GetDecryptor() {
  CHECK(task_runner_->BelongsToCurrentThread());
  return this;
}

std::optional<base::UnguessableToken> CdmAdapter::GetCdmId() const {
  CHECK(task_runner_->BelongsToCurrentThread());
  return std::nullopt;
}

void CdmAdapter::Decrypt(StreamType stream_type,
                         scoped_refptr<DecoderBuffer> encrypted,
                         DecryptCB decrypt_cb) {
  DVLOG(3) << __func__ << ": "
           << encrypted->AsHumanReadableString(/*verbose=*/true);
  CHECK(task_runner_->BelongsToCurrentThread());

  cdm::InputBuffer_2 input_buffer = {};
  std::vector<cdm::SubsampleEntry> subsamples;
  auto decrypted_block = std::make_unique<DecryptedBlockImpl>();

  TRACE_EVENT_BEGIN("media", "CdmAdapter::Decrypt", "stream_type", stream_type);
  ToCdmInputBuffer(*encrypted, &subsamples, &input_buffer);
  cdm::Status status = cdm_->Decrypt(input_buffer, decrypted_block.get());
  TRACE_EVENT_END("media", "key ID", GetHexKeyId(input_buffer), "status",
                  CdmStatusToString(status));

  if (status != cdm::kSuccess) {
    DVLOG(1) << __func__ << ": status = " << status;
    std::move(decrypt_cb).Run(ToMediaDecryptorStatus(status), nullptr);
    return;
  }

  scoped_refptr<DecoderBuffer> decrypted_buffer(
      DecoderBuffer::CopyFrom(AsSpan(decrypted_block->DecryptedBuffer())));
  decrypted_buffer->set_timestamp(
      base::Microseconds(decrypted_block->Timestamp()));
  std::move(decrypt_cb).Run(Decryptor::kSuccess, std::move(decrypted_buffer));
}

void CdmAdapter::CancelDecrypt(StreamType stream_type) {
  // As the Decrypt methods are synchronous, nothing can be done here.
  CHECK(task_runner_->BelongsToCurrentThread());
}

void CdmAdapter::InitializeAudioDecoder(const AudioDecoderConfig& config,
                                        DecoderInitCB init_cb) {
  DVLOG(2) << __func__ << ": " << config.AsHumanReadableString();
  CHECK(task_runner_->BelongsToCurrentThread());
  CHECK(!audio_init_cb_);
  TRACE_EVENT0("media", "CdmAdapter::InitializeAudioDecode");

  auto cdm_config = ToCdmAudioDecoderConfig(config);
  if (cdm_config.codec == cdm::kUnknownAudioCodec) {
    DVLOG(1) << __func__
             << ": Unsupported config: " << config.AsHumanReadableString();
    std::move(init_cb).Run(false);
    return;
  }

  cdm::Status status = cdm_->InitializeAudioDecoder(cdm_config);
  if (status != cdm::kSuccess && status != cdm::kDeferredInitialization) {
    CHECK(status == cdm::kInitializationError);
    DVLOG(1) << __func__ << ": status = " << status;
    std::move(init_cb).Run(false);
    return;
  }

  audio_samples_per_second_ = config.samples_per_second();
  audio_channel_layout_ = config.channel_layout();

  if (status == cdm::kDeferredInitialization) {
    DVLOG(1) << "Deferred initialization in " << __func__;
    audio_init_cb_ = std::move(init_cb);
    return;
  }

  std::move(init_cb).Run(true);
}

void CdmAdapter::InitializeVideoDecoder(const VideoDecoderConfig& config,
                                        DecoderInitCB init_cb) {
  DVLOG(2) << __func__ << ": " << config.AsHumanReadableString();
  CHECK(task_runner_->BelongsToCurrentThread());
  // TODO(crbug.com/412213310): This is currently crashing on ChromeOS,
  // when converted to a CHECK.
  DCHECK(!video_init_cb_);
  TRACE_EVENT0("media", "CdmAdapter::InitializeVideoDecoder");

  // Alpha decoding is not supported by the CDM.
  if (config.alpha_mode() != VideoDecoderConfig::AlphaMode::kIsOpaque) {
    DVLOG(1) << __func__
             << ": Unsupported config: " << config.AsHumanReadableString();
    std::move(init_cb).Run(false);
    return;
  }

  // cdm::kUnknownVideoCodecProfile and cdm::kUnknownVideoFormat are not checked
  // because it's possible the container has wrong information or the demuxer
  // doesn't parse them correctly.
  auto cdm_config = ToCdmVideoDecoderConfig(config);
  if (cdm_config.codec == cdm::kUnknownVideoCodec) {
    DVLOG(1) << __func__
             << ": Unsupported config: " << config.AsHumanReadableString();
    std::move(init_cb).Run(false);
    return;
  }

  cdm::Status status = cdm_->InitializeVideoDecoder(cdm_config);
  if (status != cdm::kSuccess && status != cdm::kDeferredInitialization) {
    CHECK(status == cdm::kInitializationError);
    DVLOG(1) << __func__ << ": status = " << status;
    std::move(init_cb).Run(false);
    return;
  }

  aspect_ratio_ = config.aspect_ratio();
  is_video_encrypted_ = config.is_encrypted();
  frames_processed_ = 0;

  if (status == cdm::kDeferredInitialization) {
    DVLOG(1) << "Deferred initialization in " << __func__;
    video_init_cb_ = std::move(init_cb);
    return;
  }

  std::move(init_cb).Run(true);
}

void CdmAdapter::DecryptAndDecodeAudio(scoped_refptr<DecoderBuffer> encrypted,
                                       AudioDecodeCB audio_decode_cb) {
  DVLOG(3) << __func__ << ": "
           << encrypted->AsHumanReadableString(/*verbose=*/true);
  CHECK(task_runner_->BelongsToCurrentThread());

  cdm::InputBuffer_2 input_buffer = {};
  std::vector<cdm::SubsampleEntry> subsamples;
  auto audio_frames = std::make_unique<AudioFramesImpl>();

  TRACE_EVENT_BEGIN("media", "CdmAdapter::DecryptAndDecodeAudio");
  ToCdmInputBuffer(*encrypted, &subsamples, &input_buffer);
  cdm::Status status =
      cdm_->DecryptAndDecodeSamples(input_buffer, audio_frames.get());
  TRACE_EVENT_END("media", "key ID", GetHexKeyId(input_buffer), "status",
                  CdmStatusToString(status));

  const Decryptor::AudioFrames empty_frames;
  if (status != cdm::kSuccess) {
    DVLOG(1) << __func__ << ": status = " << status;
    std::move(audio_decode_cb)
        .Run(ToMediaDecryptorStatus(status), empty_frames);
    return;
  }

  Decryptor::AudioFrames audio_frame_list;
  CHECK(audio_frames->FrameBuffer());
  if (!AudioFramesDataToAudioFrames(std::move(audio_frames),
                                    &audio_frame_list)) {
    DVLOG(1) << __func__ << " unable to convert Audio Frames";
    std::move(audio_decode_cb).Run(Decryptor::kError, empty_frames);
    return;
  }

  std::move(audio_decode_cb).Run(Decryptor::kSuccess, audio_frame_list);
}

void CdmAdapter::DecryptAndDecodeVideo(scoped_refptr<DecoderBuffer> encrypted,
                                       VideoDecodeCB video_decode_cb) {
  DVLOG(3) << __func__ << ": "
           << encrypted->AsHumanReadableString(/*verbose=*/true);
  CHECK(task_runner_->BelongsToCurrentThread());

  cdm::InputBuffer_2 input_buffer = {};
  std::vector<cdm::SubsampleEntry> subsamples;
  std::unique_ptr<VideoFrameImpl> video_frame = helper_->CreateCdmVideoFrame();

  TRACE_EVENT_BEGIN(
      "media", "CdmAdapter::DecryptAndDecodeVideo", "buffer type",
      encrypted->end_of_stream()
          ? "end of stream"
          : (encrypted->is_key_frame() ? "key frame" : "non-key frame"));
  ToCdmInputBuffer(*encrypted, &subsamples, &input_buffer);
  cdm::Status status =
      cdm_->DecryptAndDecodeFrame(input_buffer, video_frame.get());
  TRACE_EVENT_END("media", "key ID", GetHexKeyId(input_buffer), "status",
                  CdmStatusToString(status));

  if (status != cdm::kSuccess) {
    DVLOG(1) << __func__ << ": status = " << status;
    std::move(video_decode_cb).Run(ToMediaDecryptorStatus(status), nullptr);
    return;
  }

  gfx::Rect visible_rect(video_frame->Size().width, video_frame->Size().height);
  scoped_refptr<VideoFrame> decoded_frame = video_frame->TransformToVideoFrame(
      aspect_ratio_.GetNaturalSize(visible_rect));
  if (!decoded_frame) {
    DLOG(ERROR) << __func__ << ": TransformToVideoFrame failed.";
    std::move(video_decode_cb).Run(Decryptor::kError, nullptr);
    return;
  }

  decoded_frame->metadata().protected_video = is_video_encrypted_;

  ++cdm_metrics_data_.video_frames_processed;
  ++frames_processed_;

  std::move(video_decode_cb).Run(Decryptor::kSuccess, decoded_frame);
}

void CdmAdapter::ResetDecoder(StreamType stream_type) {
  DVLOG(2) << __func__ << ": stream_type = " << stream_type;
  CHECK(task_runner_->BelongsToCurrentThread());
  TRACE_EVENT1("media", "CdmAdapter::ResetDecoder", "stream_type", stream_type);

  cdm_->ResetDecoder(ToCdmStreamType(stream_type));
}

void CdmAdapter::DeinitializeDecoder(StreamType stream_type) {
  DVLOG(2) << __func__ << ": stream_type = " << stream_type;
  CHECK(task_runner_->BelongsToCurrentThread());
  TRACE_EVENT1("media", "CdmAdapter::DeinitializeDecoder", "stream_type",
               stream_type);

  cdm_->DeinitializeDecoder(ToCdmStreamType(stream_type));

  // Reset the saved values from initializing the decoder.
  switch (stream_type) {
    case Decryptor::kAudio:
      audio_samples_per_second_ = 0;
      audio_channel_layout_ = CHANNEL_LAYOUT_NONE;
      break;
    case Decryptor::kVideo:
      aspect_ratio_ = VideoAspectRatio();
      break;
  }
}

void CdmAdapter::OnResolveKeyStatusPromise(uint32_t promise_id,
                                           cdm::KeyStatus_2 key_status) {
  DVLOG(2) << __func__ << ": promise_id = " << promise_id
           << ", key_status = " << static_cast<uint32_t>(key_status);
  CHECK(task_runner_->BelongsToCurrentThread());
  cdm_promise_adapter_.ResolvePromise(promise_id, ToMediaKeyStatus(key_status));
}

void CdmAdapter::OnSessionKeysChange(const char* session_id,
                                     uint32_t session_id_size,
                                     bool has_additional_usable_key,
                                     const cdm::KeyInformation_2* keys_info,
                                     uint32_t keys_info_count) {
  std::string session_id_str(session_id, session_id_size);
  DVLOG(2) << __func__ << ": session_id = " << session_id_str;
  CHECK(task_runner_->BelongsToCurrentThread());

  TRACE_EVENT2("media", "CdmAdapter::OnSessionKeysChange", "session_id",
               session_id_str, "has_additional_usable_key",
               has_additional_usable_key);

  CdmKeysInfo keys;
  keys.reserve(keys_info_count);
  for (uint32_t i = 0; i < keys_info_count; ++i) {
    const auto& info = UNSAFE_TODO(keys_info[i]);
    keys.push_back(std::make_unique<CdmKeyInformation>(
        info.key_id, info.key_id_size, ToMediaKeyStatus(info.status),
        info.system_code));
  }

  if (has_additional_usable_key) {
    event_callbacks_.Notify(Event::kHasAdditionalUsableKey);
  }

  session_keys_change_cb_.Run(session_id_str, has_additional_usable_key,
                              std::move(keys));
}

void CdmAdapter::OnResolveKeyStatusPromise(uint32_t promise_id,
                                           cdm::KeyStatus key_status) {
  DVLOG(2) << __func__ << ": promise_id = " << promise_id
           << ", key_status = " << key_status;
  CHECK(task_runner_->BelongsToCurrentThread());
  cdm_promise_adapter_.ResolvePromise(promise_id, ToMediaKeyStatus(key_status));
}

void CdmAdapter::OnSessionKeysChange(const char* session_id,
                                     uint32_t session_id_size,
                                     bool has_additional_usable_key,
                                     const cdm::KeyInformation* keys_info,
                                     uint32_t keys_info_count) {
  std::string session_id_str(session_id, session_id_size);
  DVLOG(2) << __func__ << ": session_id = " << session_id_str;
  CHECK(task_runner_->BelongsToCurrentThread());

  TRACE_EVENT2("media", "CdmAdapter::OnSessionKeysChange", "session_id",
               session_id_str, "has_additional_usable_key",
               has_additional_usable_key);

  CdmKeysInfo keys;
  keys.reserve(keys_info_count);
  for (uint32_t i = 0; i < keys_info_count; ++i) {
    const auto& info = UNSAFE_TODO(keys_info[i]);
    keys.push_back(std::make_unique<CdmKeyInformation>(
        info.key_id, info.key_id_size, ToMediaKeyStatus(info.status),
        info.system_code));
  }

  if (has_additional_usable_key) {
    event_callbacks_.Notify(Event::kHasAdditionalUsableKey);
  }

  session_keys_change_cb_.Run(session_id_str, has_additional_usable_key,
                              std::move(keys));
}

cdm::Buffer* CdmAdapter::Allocate(uint32_t capacity) {
  DVLOG(3) << __func__ << ": capacity = " << capacity;
  CHECK(task_runner_->BelongsToCurrentThread());
  TRACE_EVENT1("media", "CdmAdapter::Allocate", "capacity", capacity);

  return helper_->CreateCdmBuffer(capacity);
}

void CdmAdapter::SetTimer(int64_t delay_ms, void* context) {
  CHECK(task_runner_->BelongsToCurrentThread());

  auto delay = base::Milliseconds(delay_ms);
  DVLOG(3) << __func__ << ": delay = " << delay << ", context = " << context;
  TRACE_EVENT2("media", "CdmAdapter::SetTimer", "delay_ms", delay_ms, "context",
               context);

  task_runner_->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&CdmAdapter::TimerExpired, weak_factory_.GetWeakPtr(),
                     context),
      delay);
}

void CdmAdapter::TimerExpired(void* context) {
  DVLOG(3) << __func__ << ": context = " << context;
  CHECK(task_runner_->BelongsToCurrentThread());
  TRACE_EVENT1("media", "CdmAdapter::TimerExpired", "context", context);

  cdm_->TimerExpired(context);
}

cdm::Time CdmAdapter::GetCurrentWallTime() {
  CHECK(task_runner_->BelongsToCurrentThread());
  return base::Time::Now().InSecondsFSinceUnixEpoch();
}

void CdmAdapter::OnInitialized(bool success) {
  DVLOG(3) << __func__ << ": success = " << success;
  CHECK(task_runner_->BelongsToCurrentThread());
  DCHECK_NE(init_promise_id_, CdmPromiseAdapter::kInvalidPromiseId);

  if (!success) {
    cdm_promise_adapter_.RejectPromise(
        init_promise_id_, CdmPromise::Exception::INVALID_STATE_ERROR, 0,
        "Unable to create CDM.");
  } else {
    cdm_promise_adapter_.ResolvePromise(init_promise_id_);
  }

  init_promise_id_ = CdmPromiseAdapter::kInvalidPromiseId;
}

void CdmAdapter::OnResolvePromise(uint32_t promise_id) {
  DVLOG(2) << __func__ << ": promise_id = " << promise_id;
  CHECK(task_runner_->BelongsToCurrentThread());
  cdm_promise_adapter_.ResolvePromise(promise_id);
}

void CdmAdapter::OnResolveNewSessionPromise(uint32_t promise_id,
                                            const char* session_id,
                                            uint32_t session_id_size) {
  DVLOG(2) << __func__ << ": promise_id = " << promise_id;
  CHECK(task_runner_->BelongsToCurrentThread());
  cdm_promise_adapter_.ResolvePromise(promise_id,
                                      std::string(session_id, session_id_size));
}

void CdmAdapter::OnRejectPromise(uint32_t promise_id,
                                 cdm::Exception exception,
                                 uint32_t system_code,
                                 const char* error_message,
                                 uint32_t error_message_size) {
  std::string error_message_str(error_message, error_message_size);
  DVLOG(2) << __func__ << ": promise_id = " << promise_id
           << ", exception = " << exception << ", system_code = " << system_code
           << ", error_message = " << error_message_str;

  // This is the central place for library CDM promise rejection. Cannot report
  // this in more generic classes like CdmPromise or CdmPromiseAdapter because
  // they may be used multiple times in one promise chain that involves IPC.
  ReportSystemCodeUMA(cdm_config_.key_system, system_code);

  // UMA to help track file related errors. See http://crbug.com/410630
  if (system_code == 0x27) {
    UMA_HISTOGRAM_CUSTOM_COUNTS("Media.EME.CdmFileIO.FileSizeKBOnError",
                                last_read_file_size_kb_, kSizeKBMin, kSizeKBMax,
                                kSizeKBBuckets);
  }

  CHECK(task_runner_->BelongsToCurrentThread());
  cdm_promise_adapter_.RejectPromise(promise_id,
                                     ToMediaCdmPromiseException(exception),
                                     system_code, error_message_str);
}

void CdmAdapter::OnSessionMessage(const char* session_id,
                                  uint32_t session_id_size,
                                  cdm::MessageType message_type,
                                  const char* message,
                                  uint32_t message_size) {
  std::string session_id_str(session_id, session_id_size);
  DVLOG(2) << __func__ << ": session_id = " << session_id_str;
  CHECK(task_runner_->BelongsToCurrentThread());

  cdm_metrics_data_.number_of_on_message_events++;

  TRACE_EVENT2("media", "CdmAdapter::OnSessionMessage", "session_id",
               session_id_str, "message_type", message_type);

  const uint8_t* message_ptr = reinterpret_cast<const uint8_t*>(message);
  session_message_cb_.Run(
      session_id_str, ToMediaMessageType(message_type),
      std::vector<uint8_t>(message_ptr,
                           UNSAFE_TODO(message_ptr + message_size)));
}

void CdmAdapter::OnExpirationChange(const char* session_id,
                                    uint32_t session_id_size,
                                    cdm::Time new_expiry_time) {
  std::string session_id_str(session_id, session_id_size);
  DVLOG(2) << __func__ << ": session_id = " << session_id_str
           << ", new_expiry_time = " << new_expiry_time;
  CHECK(task_runner_->BelongsToCurrentThread());

  base::Time expiration =
      base::Time::FromSecondsSinceUnixEpoch(new_expiry_time);
  TRACE_EVENT2("media", "CdmAdapter::OnExpirationChange", "session_id",
               session_id_str, "new_expiry_time", expiration);
  session_expiration_update_cb_.Run(session_id_str, expiration);
}

void CdmAdapter::OnSessionClosed(const char* session_id,
                                 uint32_t session_id_size) {
  CHECK(task_runner_->BelongsToCurrentThread());

  std::string session_id_str(session_id, session_id_size);
  TRACE_EVENT1("media", "CdmAdapter::OnSessionClosed", "session_id",
               session_id_str);
  // Library CDMs typically only close sessions as a result of `CloseSession()`.
  session_closed_cb_.Run(session_id_str, CdmSessionClosedReason::kClose);
}

void CdmAdapter::SendPlatformChallenge(const char* service_id,
                                       uint32_t service_id_size,
                                       const char* challenge,
                                       uint32_t challenge_size) {
  CHECK(task_runner_->BelongsToCurrentThread());

  if (!cdm_config_.allow_distinctive_identifier) {
    task_runner_->PostTask(
        FROM_HERE,
        base::BindOnce(&CdmAdapter::OnChallengePlatformDone,
                       weak_factory_.GetWeakPtr(), false, "", "", ""));
    return;
  }

  helper_->ChallengePlatform(
      std::string(service_id, service_id_size),
      std::string(challenge, challenge_size),
      base::BindOnce(&CdmAdapter::OnChallengePlatformDone,
                     weak_factory_.GetWeakPtr()));
}

void CdmAdapter::OnChallengePlatformDone(
    bool success,
    const std::string& signed_data,
    const std::string& signed_data_signature,
    const std::string& platform_key_certificate) {
  DVLOG(2) << __func__ << ": success = " << success;
  TRACE_EVENT1("media", "CdmAdapter::OnChallengePlatformDone", "success",
               success);

  cdm::PlatformChallengeResponse platform_challenge_response = {};
  if (success) {
    platform_challenge_response.signed_data =
        reinterpret_cast<const uint8_t*>(signed_data.data());
    platform_challenge_response.signed_data_length = signed_data.length();
    platform_challenge_response.signed_data_signature =
        reinterpret_cast<const uint8_t*>(signed_data_signature.data());
    platform_challenge_response.signed_data_signature_length =
        signed_data_signature.length();
    platform_challenge_response.platform_key_certificate =
        reinterpret_cast<const uint8_t*>(platform_key_certificate.data());
    platform_challenge_response.platform_key_certificate_length =
        platform_key_certificate.length();
  }

  cdm_->OnPlatformChallengeResponse(platform_challenge_response);
}

void CdmAdapter::EnableOutputProtection(uint32_t desired_protection_mask) {
  DVLOG(1) << __func__;
  CHECK(task_runner_->BelongsToCurrentThread());
  TRACE_EVENT1("media", "CdmAdapter::EnableOutputProtection",
               "desired_protection_mask", GetHexMask(desired_protection_mask));

  helper_->EnableProtection(
      desired_protection_mask,
      base::BindOnce(&CdmAdapter::OnEnableOutputProtectionDone,
                     weak_factory_.GetWeakPtr()));
}

void CdmAdapter::OnEnableOutputProtectionDone(bool success) {
  // CDM needs to call QueryOutputProtectionStatus() to see if it took effect
  // or not.
  DVLOG(1) << __func__ << ": success = " << success;
  TRACE_EVENT1("media", "CdmAdapter::OnEnableOutputProtectionDone", "success",
               success);
}

void CdmAdapter::QueryOutputProtectionStatus() {
  CHECK(task_runner_->BelongsToCurrentThread());
  TRACE_EVENT0("media", "CdmAdapter::QueryOutputProtectionStatus");

  ReportOutputProtectionQuery();
  helper_->QueryStatus(
      base::BindOnce(&CdmAdapter::OnQueryOutputProtectionStatusDone,
                     weak_factory_.GetWeakPtr()));
}

void CdmAdapter::OnQueryOutputProtectionStatusDone(bool success,
                                                   uint32_t link_mask,
                                                   uint32_t protection_mask) {
  DVLOG(2) << __func__ << ": success = " << success;
  // Combining |link_mask| and |protection_mask| since there's no TRACE_EVENT3.
  TRACE_EVENT2("media", "CdmAdapter::OnQueryOutputProtectionStatusDone",
               "success", success, "link_mask, protection_mask",
               GetHexMask(link_mask) + ", " + GetHexMask(protection_mask));

  // The bit mask definition must be consistent between media::OutputProtection
  // and cdm::ContentDecryptionModule* interfaces. This is statically asserted
  // by ASSERT_ENUM_EQs above.

  // Return a query status of failure on error.
  cdm::QueryResult query_result;
  if (success) {
    query_result = cdm::kQuerySucceeded;
    ReportOutputProtectionQueryResult(link_mask, protection_mask);
  } else {
    DVLOG(1) << __func__ << ": query output protection status failed";
    query_result = cdm::kQueryFailed;
  }

  cdm_->OnQueryOutputProtectionStatus(query_result, link_mask, protection_mask);
}

void CdmAdapter::ReportOutputProtectionQuery() {
  if (uma_for_output_protection_query_reported_)
    return;

  ReportOutputProtectionUMA(OutputProtectionStatus::kQueried);
  uma_for_output_protection_query_reported_ = true;
}

void CdmAdapter::ReportOutputProtectionQueryResult(uint32_t link_mask,
                                                   uint32_t protection_mask) {
  CHECK(uma_for_output_protection_query_reported_);

  if (uma_for_output_protection_positive_result_reported_)
    return;

  // Report UMAs for output protection query result.

  uint32_t external_links = (link_mask & ~cdm::kLinkTypeInternal);

  if (!external_links) {
    ReportOutputProtectionUMA(OutputProtectionStatus::kNoExternalLink);
    uma_for_output_protection_positive_result_reported_ = true;
    return;
  }

  const uint32_t kProtectableLinks =
      cdm::kLinkTypeHDMI | cdm::kLinkTypeDVI | cdm::kLinkTypeDisplayPort;
  bool is_unprotectable_link_connected =
      (external_links & ~kProtectableLinks) != 0;
  bool is_hdcp_enabled_on_all_protectable_links =
      (protection_mask & cdm::kProtectionHDCP) != 0;

  if (!is_unprotectable_link_connected &&
      is_hdcp_enabled_on_all_protectable_links) {
    ReportOutputProtectionUMA(
        OutputProtectionStatus::kAllExternalLinksProtected);
    uma_for_output_protection_positive_result_reported_ = true;
    return;
  }

  // Do not report a negative result because it could be a false negative.
  // Instead, we will calculate number of negatives using the total number of
  // queries and positive results.
}

void CdmAdapter::OnDeferredInitializationDone(cdm::StreamType stream_type,
                                              cdm::Status decoder_status) {
  DVLOG(1) << __func__ << ": stream_type = " << stream_type
           << ", decoder_status = " << decoder_status;
  CHECK(task_runner_->BelongsToCurrentThread());

  switch (stream_type) {
    case cdm::kStreamTypeAudio:
      std::move(audio_init_cb_).Run(decoder_status == cdm::kSuccess);
      return;
    case cdm::kStreamTypeVideo:
      std::move(video_init_cb_).Run(decoder_status == cdm::kSuccess);
      return;
  }

  NOTREACHED() << "Unexpected cdm::StreamType " << stream_type;
}

cdm::FileIO* CdmAdapter::CreateFileIO(cdm::FileIOClient* client) {
  DVLOG(3) << __func__;
  CHECK(task_runner_->BelongsToCurrentThread());

  if (!cdm_config_.allow_persistent_state) {
    DVLOG(1) << __func__ << ": Persistent state not allowed.";
    return nullptr;
  }

  return helper_->CreateCdmFileIO(client);
}

void CdmAdapter::RequestStorageId(uint32_t version) {
  DVLOG(2) << __func__ << ": version = " << version;
  CHECK(task_runner_->BelongsToCurrentThread());

  if (!cdm_config_.allow_persistent_state ||
      !(version == kCurrentStorageIdVersion ||
        version == kRequestLatestStorageIdVersion)) {
    DVLOG(1) << __func__ << ": Persistent state not allowed ("
             << cdm_config_.allow_persistent_state
             << ") or invalid storage ID version (" << version << ").";
    task_runner_->PostTask(FROM_HERE,
                           base::BindOnce(&CdmAdapter::OnStorageIdObtained,
                                          weak_factory_.GetWeakPtr(), version,
                                          std::vector<uint8_t>()));
    return;
  }

  helper_->GetStorageId(version,
                        base::BindOnce(&CdmAdapter::OnStorageIdObtained,
                                       weak_factory_.GetWeakPtr()));
}

void CdmAdapter::ReportMetrics(cdm::MetricName metric_name, uint64_t value) {
  switch (metric_name) {
    case cdm::kSdkVersion:
      cdm_metrics_data_.license_sdk_version = value;
      return;
    case cdm::kCertificateSerialNumber:
      cdm_metrics_data_.certificate_serial_number = value;
      return;
    case cdm::kDecoderBypassBlockCount:
      cdm_metrics_data_.decoder_bypass_block_count =
          cdm_metrics_data_.decoder_bypass_block_count.value_or(0) + value;
      ReportDecoderBypassBlockCountUMA(value, frames_processed_);
      return;
    case cdm::kDecoderCheck1SuccessCount:
      cdm_metrics_data_.decoder_check1_success_count =
          cdm_metrics_data_.decoder_check1_success_count.value_or(0) + value;
      return;
    case cdm::kDecoderCheck1WarningCount:
      cdm_metrics_data_.decoder_check1_warning_count =
          cdm_metrics_data_.decoder_check1_warning_count.value_or(0) + value;
      return;
    case cdm::kDecoderCheck1ErrorCount:
      cdm_metrics_data_.decoder_check1_error_count =
          cdm_metrics_data_.decoder_check1_error_count.value_or(0) + value;
      return;
    case cdm::kKeySystemDataTime1:
      cdm_metrics_data_.key_system_data_time1 = value;
      return;
    case cdm::kKeySystemDataTime2:
      cdm_metrics_data_.key_system_data_time2 = value;
      return;
    case cdm::kKeySystemDataTime3:
      cdm_metrics_data_.key_system_data_time3 = value;
      return;
    case cdm::kKeySystemDataBool1:
      cdm_metrics_data_.key_system_data_bool1 = value != 0;
      return;
    case cdm::kSessionInitDataType:
      cdm_metrics_data_.session_init_data_type = value;
      return;
  }
}

void CdmAdapter::OnStorageIdObtained(uint32_t version,
                                     const std::vector<uint8_t>& storage_id) {
  DVLOG(2) << __func__ << ": version = " << version;
  CHECK(task_runner_->BelongsToCurrentThread());
  TRACE_EVENT1("media", "CdmAdapter::OnStorageIdObtained", "version", version);

  cdm_->OnStorageId(version, storage_id.data(), storage_id.size());
}

bool CdmAdapter::AudioFramesDataToAudioFrames(
    std::unique_ptr<AudioFramesImpl> audio_frames,
    Decryptor::AudioFrames* result_frames) {
  base::span<uint8_t> data = AsSpan(audio_frames->FrameBuffer());
  const SampleFormat sample_format =
      ToMediaSampleFormat(audio_frames->Format());
  const int audio_channel_count =
      ChannelLayoutToChannelCount(audio_channel_layout_);
  const int audio_bytes_per_sample =
      SampleFormatToBytesPerChannel(sample_format);
  if (audio_bytes_per_sample <= 0 || audio_channel_count <= 0) {
    return false;
  }

  base::SpanReader<const uint8_t> frame_buffer_reader(data);
  while (frame_buffer_reader.remaining() > 0) {
    // AudioFrames can contain multiple audio output buffers, which are
    // serialized into this format:
    // |<------------------- serialized audio buffer ------------------->|
    // | int64_t timestamp | int64_t length | length bytes of audio data |
    int64_t timestamp = 0;
    int64_t frame_size = -1;

    if (!frame_buffer_reader.ReadI64NativeEndian(timestamp) ||
        !frame_buffer_reader.ReadI64NativeEndian(frame_size) ||
        frame_size <= 0) {
      return false;
    }

    // Read the frame payload.
    base::span<const uint8_t> frame_data =
        frame_buffer_reader.Read(base::checked_cast<size_t>(frame_size))
            .value_or(base::span<const uint8_t>());
    if (frame_data.empty()) {
      return false;
    }

    // `frame_size` covers all channels. Validate the total sample count before
    // converting it to the per-channel frame count expected by AudioBuffer.
    if (frame_size % audio_bytes_per_sample != 0) {
      return false;
    }
    const int64_t sample_count = frame_size / audio_bytes_per_sample;
    if (sample_count % audio_channel_count != 0) {
      return false;
    }

    const int frame_count =
        base::checked_cast<int>(sample_count / audio_channel_count);

    // Setup channel spans. AudioBuffer::CopyFrom() will only use the first one
    // in the case of interleaved data, so the first span must cover the full
    // interleaved payload.
    std::vector<base::span<const uint8_t>> channel_spans;
    if (IsInterleaved(sample_format)) {
      channel_spans.push_back(frame_data);
    } else {
      if (!IsPlanar(sample_format)) {
        return false;
      }

      channel_spans.resize(audio_channel_count);
      const size_t size_per_channel =
          base::checked_cast<size_t>(frame_size / audio_channel_count);
      for (int i = 0; i < audio_channel_count; ++i) {
        channel_spans[i] = frame_data.take_first(size_per_channel);
      }
      CHECK(frame_data.empty());
    }
    scoped_refptr<media::AudioBuffer> frame = media::AudioBuffer::CopyFrom(
        sample_format, audio_channel_layout_, audio_channel_count,
        audio_samples_per_second_, frame_count, channel_spans,
        base::Microseconds(timestamp), pool_);
    result_frames->push_back(frame);
  }

  return true;
}

void CdmAdapter::OnFileRead(int file_size_bytes) {
  DCHECK_GE(file_size_bytes, 0);
  last_read_file_size_kb_ = file_size_bytes / 1024;

  if (file_size_uma_reported_)
    return;

  UMA_HISTOGRAM_CUSTOM_COUNTS("Media.EME.CdmFileIO.FileSizeKBOnFirstRead",
                              last_read_file_size_kb_, kSizeKBMin, kSizeKBMax,
                              kSizeKBBuckets);
  file_size_uma_reported_ = true;
}

}  // namespace media
