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

#include "chromeos/components/cdm_factory_daemon/remote_cdm_context.h"

#include "base/functional/callback.h"
#include "base/sequence_checker_impl.h"
#include "base/task/sequenced_task_runner.h"
#include "media/base/callback_registry.h"
#include "media/cdm/cdm_context_ref_impl.h"
#include "media/mojo/common/media_type_converters.h"
#include "media/mojo/common/validation_utils.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"

namespace chromeos {

namespace {
class RemoteCdmContextRef final : public media::CdmContextRef {
 public:
  explicit RemoteCdmContextRef(scoped_refptr<RemoteCdmContext> cdm_context)
      : cdm_context_(std::move(cdm_context)) {}

  RemoteCdmContextRef(const RemoteCdmContextRef&) = delete;
  RemoteCdmContextRef& operator=(const RemoteCdmContextRef&) = delete;

  ~RemoteCdmContextRef() final = default;

  // CdmContextRef:
  media::CdmContext* GetCdmContext() final { return cdm_context_.get(); }

 private:
  scoped_refptr<RemoteCdmContext> cdm_context_;
};
}  // namespace

class RemoteCdmContext::MojoSequenceState
    : public media::mojom::CdmContextEventCallback {
 public:
  explicit MojoSequenceState(
      mojo::PendingRemote<media::mojom::CdmContextForOOPVD>
          pending_cdm_context_for_oopvd)
      : pending_cdm_context_for_oopvd_(
            std::move(pending_cdm_context_for_oopvd)) {
    sequence_checker_.DetachFromSequence();
  }

  ~MojoSequenceState() override {
    CHECK(sequence_checker_.CalledOnValidSequence());
  }

  mojo::Remote<media::mojom::CdmContextForOOPVD>& GetCdmContextForOOPVD() {
    CHECK(sequence_checker_.CalledOnValidSequence());
    if (!cdm_context_for_oopvd_) {
      cdm_context_for_oopvd_.Bind(std::move(pending_cdm_context_for_oopvd_));
      mojo_task_runner_ = base::SequencedTaskRunner::GetCurrentDefault();
    }
    return cdm_context_for_oopvd_;
  }

  std::unique_ptr<media::CallbackRegistration> RegisterEventCB(
      EventCB event_cb) {
    CHECK(sequence_checker_.CalledOnValidSequence());
    if (!event_callback_receiver_.is_bound()) {
      GetCdmContextForOOPVD()->RegisterEventCallback(
          event_callback_receiver_.BindNewPipeAndPassRemote());
    }
    auto registration = event_callbacks_.Register(std::move(event_cb));
    return registration;
  }

  static void DeleteOnCorrectSequence(MojoSequenceState* mojo_sequence_state) {
    scoped_refptr<base::SequencedTaskRunner> task_runner =
        mojo_sequence_state->mojo_task_runner_;
    if (task_runner && !task_runner->RunsTasksInCurrentSequence()) {
      // When DeleteSoon() returns false, |mojo_sequence_state| will be leaked,
      // which is okay.
      task_runner->DeleteSoon(FROM_HERE, mojo_sequence_state);
    } else {
      // We're either on the right sequence or the |mojo_sequence_state| was
      // never bound to a sequence (i.e., it was constructed but never used).
      DCHECK(task_runner ||
             mojo_sequence_state->pending_cdm_context_for_oopvd_);
      DCHECK(task_runner || !mojo_sequence_state->cdm_context_for_oopvd_);
      DCHECK(task_runner ||
             !mojo_sequence_state->event_callback_receiver_.is_bound());
      delete mojo_sequence_state;
    }
  }

 private:
  // media::mojom::CdmContextEventCallback:
  void EventCallback(media::CdmContext::Event event) override {
    CHECK(sequence_checker_.CalledOnValidSequence());
    event_callbacks_.Notify(std::move(event));
  }

  base::SequenceCheckerImpl sequence_checker_;
  mojo::PendingRemote<media::mojom::CdmContextForOOPVD>
      pending_cdm_context_for_oopvd_;
  mojo::Remote<media::mojom::CdmContextForOOPVD> cdm_context_for_oopvd_;
  mojo::Receiver<media::mojom::CdmContextEventCallback>
      event_callback_receiver_{this};
  media::CallbackRegistry<EventCB::RunType> event_callbacks_;
  scoped_refptr<base::SequencedTaskRunner> mojo_task_runner_;
};

RemoteCdmContext::RemoteCdmContext(
    mojo::PendingRemote<media::mojom::CdmContextForOOPVD> cdm_context_for_oopvd)
    : mojo_sequence_state_(
          new MojoSequenceState(std::move(cdm_context_for_oopvd)),
          &MojoSequenceState::DeleteOnCorrectSequence) {}

std::unique_ptr<media::CallbackRegistration> RemoteCdmContext::RegisterEventCB(
    EventCB event_cb) {
  return mojo_sequence_state_->RegisterEventCB(std::move(event_cb));
}

RemoteCdmContext::~RemoteCdmContext() {}

media::Decryptor* RemoteCdmContext::GetDecryptor() {
  return this;
}

ChromeOsCdmContext* RemoteCdmContext::GetChromeOsCdmContext() {
  return this;
}

void RemoteCdmContext::GetHwKeyData(const media::DecryptConfig* decrypt_config,
                                    const std::vector<uint8_t>& hw_identifier,
                                    GetHwKeyDataCB callback) {
  CHECK(decrypt_config);
  media::mojom::DecryptConfigPtr mojo_decrypt_config =
      media::mojom::DecryptConfig::From(*decrypt_config);
  CHECK(mojo_decrypt_config);
  mojo_sequence_state_->GetCdmContextForOOPVD()->GetHwKeyData(
      std::move(mojo_decrypt_config), hw_identifier, std::move(callback));
}

void RemoteCdmContext::GetHwConfigData(GetHwConfigDataCB callback) {
  mojo_sequence_state_->GetCdmContextForOOPVD()->GetHwConfigData(
      std::move(callback));
}

void RemoteCdmContext::GetScreenResolutions(GetScreenResolutionsCB callback) {
  mojo_sequence_state_->GetCdmContextForOOPVD()->GetScreenResolutions(
      std::move(callback));
}

void RemoteCdmContext::AllocateSecureBuffer(uint32_t size,
                                            AllocateSecureBufferCB callback) {
  mojo_sequence_state_->GetCdmContextForOOPVD()->AllocateSecureBuffer(
      size, std::move(callback));
}

void RemoteCdmContext::ParseEncryptedSliceHeader(
    uint64_t secure_handle,
    uint32_t offset,
    const std::vector<uint8_t>& stream_data,
    ParseEncryptedSliceHeaderCB callback) {
  mojo_sequence_state_->GetCdmContextForOOPVD()->ParseEncryptedSliceHeader(
      secure_handle, offset, stream_data, std::move(callback));
}

std::unique_ptr<media::CdmContextRef> RemoteCdmContext::GetCdmContextRef() {
  return std::make_unique<RemoteCdmContextRef>(base::WrapRefCounted(this));
}

bool RemoteCdmContext::UsingArcCdm() const {
  return false;
}

bool RemoteCdmContext::IsRemoteCdm() const {
  return true;
}

void RemoteCdmContext::Decrypt(StreamType stream_type,
                               scoped_refptr<media::DecoderBuffer> encrypted,
                               DecryptCB decrypt_cb) {
  DCHECK_EQ(stream_type, Decryptor::kVideo);
  CHECK(encrypted);
  media::mojom::DecoderBufferPtr encrypted_mojo_buffer =
      media::mojom::DecoderBuffer::From(*encrypted);
  CHECK(encrypted_mojo_buffer);
  mojo_sequence_state_->GetCdmContextForOOPVD()->DecryptVideoBuffer(
      std::move(encrypted_mojo_buffer),
      std::vector<uint8_t>(encrypted->begin(), encrypted->end()),
      base::BindOnce(&RemoteCdmContext::OnDecryptVideoBufferDone,
                     base::Unretained(this), std::move(decrypt_cb)));
}

void RemoteCdmContext::OnDecryptVideoBufferDone(
    DecryptCB decrypt_cb,
    media::Decryptor::Status status,
    media::mojom::DecoderBufferPtr decoder_buffer,
    const std::vector<uint8_t>& bytes) {
  scoped_refptr<media::DecoderBuffer> media_decoder_buffer;
  if (decoder_buffer) {
    media_decoder_buffer =
        media::ValidateAndConvertMojoDecoderBuffer(std::move(decoder_buffer));
    if (!media_decoder_buffer) {
      CHECK(mojo::IsInMessageDispatch());
      mojo::ReportBadMessage("Invalid DecoderBuffer received");
      return;
    }
    CHECK_EQ(bytes.size(), media_decoder_buffer->size());
    UNSAFE_TODO(memcpy(media_decoder_buffer->writable_data(), bytes.data(),
                       bytes.size()));
  }
  std::move(decrypt_cb).Run(status, media_decoder_buffer);
}

void RemoteCdmContext::CancelDecrypt(StreamType stream_type) {
  // This method is racey since decryption is on another thread, so don't do
  // anything special for cancellation since the caller needs to handle the case
  // where the normal callback occurs even after calling CancelDecrypt anyways.
}

void RemoteCdmContext::InitializeAudioDecoder(
    const media::AudioDecoderConfig& config,
    DecoderInitCB init_cb) {
  // RemoteCdmContext does not support audio decoding.
  std::move(init_cb).Run(false);
}

void RemoteCdmContext::InitializeVideoDecoder(
    const media::VideoDecoderConfig& config,
    DecoderInitCB init_cb) {
  // RemoteCdmContext does not support video decoding.
  std::move(init_cb).Run(false);
}

void RemoteCdmContext::DecryptAndDecodeAudio(
    scoped_refptr<media::DecoderBuffer> encrypted,
    AudioDecodeCB audio_decode_cb) {
  NOTREACHED() << "RemoteCdmContext does not support audio decoding";
}

void RemoteCdmContext::DecryptAndDecodeVideo(
    scoped_refptr<media::DecoderBuffer> encrypted,
    VideoDecodeCB video_decode_cb) {
  NOTREACHED() << "RemoteCdmContext does not support video decoding";
}

void RemoteCdmContext::ResetDecoder(StreamType stream_type) {
  NOTREACHED() << "RemoteCdmContext does not support decoding";
}

void RemoteCdmContext::DeinitializeDecoder(StreamType stream_type) {
  // We do not support audio/video decoding, but since this can be called any
  // time after InitializeAudioDecoder/InitializeVideoDecoder, nothing to be
  // done here.
}

bool RemoteCdmContext::CanAlwaysDecrypt() {
  return false;
}

}  // namespace chromeos
