// Copyright 2014 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/mojo/common/media_type_converters.h"

#include <memory>
#include <variant>

#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/numerics/checked_math.h"
#include "base/numerics/safe_conversions.h"
#include "media/base/audio_buffer.h"
#include "media/base/decoder_buffer.h"
#include "media/base/decrypt_config.h"
#include "media/base/limits.h"
#include "media/base/sample_format.h"
#include "media/base/subsample_entry.h"
#include "mojo/public/cpp/system/buffer.h"

namespace mojo {

// TODO(crbug.com/40468949): Stop using TypeConverters.

// static
media::mojom::DecryptConfigPtr
TypeConverter<media::mojom::DecryptConfigPtr, media::DecryptConfig>::Convert(
    const media::DecryptConfig& input) {
  media::mojom::DecryptConfigPtr mojo_decrypt_config(
      media::mojom::DecryptConfig::New());
  mojo_decrypt_config->key_id = input.key_id();
  mojo_decrypt_config->iv = input.iv();
  mojo_decrypt_config->subsamples = input.subsamples();
  mojo_decrypt_config->encryption_scheme = input.encryption_scheme();
  mojo_decrypt_config->encryption_pattern = input.encryption_pattern();

  return mojo_decrypt_config;
}

// static
std::unique_ptr<media::DecryptConfig>
TypeConverter<std::unique_ptr<media::DecryptConfig>,
              media::mojom::DecryptConfigPtr>::
    Convert(const media::mojom::DecryptConfigPtr& input) {
  // Required invariants by the DecryptConfig constructor, to prevent a renderer
  // from crashing the GPU we check them here as well and gracefully return
  // nullptr instead.
  if (input->encryption_scheme == media::EncryptionScheme::kUnencrypted) {
    return nullptr;
  }
  // Pattern not allowed for non-'cbcs' schemes.
  if (input->encryption_scheme != media::EncryptionScheme::kCbcs &&
      input->encryption_pattern) {
    return nullptr;
  }
  // Enforce a hard upper bound on the number of subsamples to prevent
  // unbounded allocations or HAL vulnerabilities.
  if (input->subsamples.size() > media::limits::kMaxSubsamplesPerBuffer) {
    return nullptr;
  }
  return std::make_unique<media::DecryptConfig>(
      input->encryption_scheme, input->key_id, input->iv, input->subsamples,
      input->encryption_pattern);
}

// static
media::mojom::DecoderBufferSideDataPtr TypeConverter<
    media::mojom::DecoderBufferSideDataPtr,
    media::DecoderBufferSideData>::Convert(const media::DecoderBufferSideData&
                                               input) {
  media::mojom::DecoderBufferSideDataPtr mojo_side_data(
      media::mojom::DecoderBufferSideData::New());
  if (!input.alpha_data.empty()) {
    mojo_side_data->alpha_data.assign(input.alpha_data.begin(),
                                      input.alpha_data.end());
  }
  mojo_side_data->spatial_layers = input.spatial_layers;
  mojo_side_data->hdr_metadata = input.hdr_metadata;
  mojo_side_data->secure_handle = input.secure_handle;
  mojo_side_data->front_discard = input.discard_padding.first;
  mojo_side_data->back_discard = input.discard_padding.second;

  // Note: `next_audio_config` and `next_video_config` are intentionally not
  // serialized here since they are only set for EOS buffers.

  return mojo_side_data;
}

// static
std::unique_ptr<media::DecoderBufferSideData>
TypeConverter<std::unique_ptr<media::DecoderBufferSideData>,
              media::mojom::DecoderBufferSideDataPtr>::
    Convert(const media::mojom::DecoderBufferSideDataPtr& input) {
  if (!input) {
    return nullptr;
  }

  auto side_data = std::make_unique<media::DecoderBufferSideData>();
  side_data->spatial_layers = input->spatial_layers;
  if (!input->alpha_data.empty()) {
    side_data->alpha_data =
        base::HeapArray<uint8_t>::CopiedFrom(input->alpha_data);
  }
  side_data->hdr_metadata = input->hdr_metadata;
  side_data->secure_handle = input->secure_handle;
  side_data->discard_padding.first = input->front_discard;
  side_data->discard_padding.second = input->back_discard;

  // Note: `next_audio_config` and `next_video_config` are intentionally not
  // deserialized here since they are only set for EOS buffers.

  return side_data;
}

// static
media::mojom::DecoderBufferPtr
TypeConverter<media::mojom::DecoderBufferPtr, media::DecoderBuffer>::Convert(
    const media::DecoderBuffer& input) {
  if (input.end_of_stream()) {
    auto eos = media::mojom::EosDecoderBuffer::New();
    if (input.next_config()) {
      const auto next_config = *input.next_config();
      if (const auto* ac =
              std::get_if<media::AudioDecoderConfig>(&next_config)) {
        eos->next_config =
            media::mojom::DecoderBufferSideDataNextConfig::NewNextAudioConfig(
                *ac);
      } else {
        eos->next_config =
            media::mojom::DecoderBufferSideDataNextConfig::NewNextVideoConfig(
                std::get<media::VideoDecoderConfig>(next_config));
      }
    }
    return media::mojom::DecoderBuffer::NewEos(std::move(eos));
  }

  auto data_buffer = media::mojom::DataDecoderBuffer::New();
  data_buffer->timestamp = input.timestamp();
  data_buffer->duration = input.duration();
  data_buffer->is_key_frame = input.is_key_frame();
  data_buffer->data_size = base::checked_cast<uint32_t>(input.size());
  if (input.side_data()) {
    data_buffer->side_data =
        media::mojom::DecoderBufferSideData::From(*input.side_data());
  }

  if (input.decrypt_config()) {
    data_buffer->decrypt_config =
        media::mojom::DecryptConfig::From(*input.decrypt_config());
  }

  // TODO(dalecurtis): We intentionally do not serialize the data section of
  // the DecoderBuffer here; this must instead be done by clients via their
  // own DataPipe.  See http://crbug.com/432960

  return media::mojom::DecoderBuffer::NewData(std::move(data_buffer));
}

// static
scoped_refptr<media::DecoderBuffer>
TypeConverter<scoped_refptr<media::DecoderBuffer>,
              media::mojom::DecoderBufferPtr>::
    Convert(const media::mojom::DecoderBufferPtr& input) {
  if (input->is_eos()) {
    const auto& eos_buffer = input->get_eos();
    if (eos_buffer->next_config) {
      if (eos_buffer->next_config->is_next_audio_config()) {
        return media::DecoderBuffer::CreateEOSBuffer(
            eos_buffer->next_config->get_next_audio_config());
      } else if (eos_buffer->next_config->is_next_video_config()) {
        return media::DecoderBuffer::CreateEOSBuffer(
            eos_buffer->next_config->get_next_video_config());
      }
    }
    return media::DecoderBuffer::CreateEOSBuffer();
  }

  const auto& mojo_buffer = input->get_data();
  auto buffer = base::MakeRefCounted<media::DecoderBuffer>(
      base::strict_cast<size_t>(mojo_buffer->data_size));

  if (mojo_buffer->side_data) {
    buffer->set_side_data(
        mojo_buffer->side_data
            .To<std::unique_ptr<media::DecoderBufferSideData>>());
  }

  buffer->set_timestamp(mojo_buffer->timestamp);
  buffer->set_duration(mojo_buffer->duration);
  buffer->set_is_key_frame(mojo_buffer->is_key_frame);

  if (mojo_buffer->decrypt_config) {
    auto decrypt_config =
        mojo_buffer->decrypt_config.To<std::unique_ptr<media::DecryptConfig>>();
    if (!decrypt_config) {
      return nullptr;
    }
    buffer->set_decrypt_config(std::move(decrypt_config));

    if (!media::DecoderBuffer::DoSubsamplesMatch(*buffer)) {
      DVLOG(1) << __func__ << ": Subsamples do not match buffer size";
      return nullptr;
    }
  }

  // TODO(dalecurtis): We intentionally do not deserialize the data section of
  // the DecoderBuffer here; this must instead be done by clients via their
  // own DataPipe.  See http://crbug.com/432960

  return buffer;
}

// static
media::mojom::AudioBufferPtr
TypeConverter<media::mojom::AudioBufferPtr, media::AudioBuffer>::Convert(
    const media::AudioBuffer& input) {
  media::mojom::AudioBufferPtr buffer(media::mojom::AudioBuffer::New());
  buffer->sample_format = input.sample_format_;
  buffer->channel_layout = input.channel_layout();
  buffer->channel_count = input.channel_count();
  buffer->sample_rate = input.sample_rate();
  buffer->frame_count = input.frame_count();
  buffer->end_of_stream = input.end_of_stream();
  buffer->timestamp = input.timestamp();

  if (input.data_) {
    // `input.data_->span()` refers to the whole memory buffer given to the
    // `media::AudioBuffer`.
    // `data_size()` refers to the amount of memory really used by the audio
    // data. The rest is padding, which we don't need to copy.
    // Safe to CHECK here since this is into Mojo not From mojo (and thus not
    // untrusted input).
    CHECK_GT(input.data_size(), 0u);
    CHECK_LE(input.data_size(), input.data_->span().size());
    auto buffer_start = input.data_->span().begin();
    auto buffer_end = buffer_start + input.data_size();
    buffer->data.assign(buffer_start, buffer_end);
  }

  return buffer;
}

// static
scoped_refptr<media::AudioBuffer>
TypeConverter<scoped_refptr<media::AudioBuffer>, media::mojom::AudioBufferPtr>::
    Convert(const media::mojom::AudioBufferPtr& input) {
  if (input->end_of_stream)
    return media::AudioBuffer::CreateEOSBuffer();

  if (input->frame_count <= 0 ||
      static_cast<size_t>(input->sample_format) >
          media::SampleFormat::kMaxValue ||
      static_cast<size_t>(input->channel_layout) > media::CHANNEL_LAYOUT_MAX ||
      input->channel_count > media::limits::kMaxChannels ||
      (input->channel_layout != media::CHANNEL_LAYOUT_DISCRETE &&
       ChannelLayoutToChannelCount(input->channel_layout) !=
           input->channel_count)) {
    DLOG(ERROR) << "Receive an invalid audio buffer, replace it with EOS.";
    return media::AudioBuffer::CreateEOSBuffer();
  }

  if (IsBitstream(input->sample_format)) {
    if (input->data.empty()) {
      DLOG(ERROR)
          << "Received invalid bitstream AudioBuffer, replace it with EOS.";
      return media::AudioBuffer::CreateEOSBuffer();
    }
    return media::AudioBuffer::CopyBitstreamFrom(
        input->sample_format, input->channel_layout, input->channel_count,
        input->sample_rate, input->frame_count, input->data, input->timestamp);
  }

  // Safe to cast, since we already checked `sample_format` doesn't exceed
  // media::kSampleFormatMax above.
  const size_t bytes_per_channel = SampleFormatToBytesPerChannel(
      static_cast<media::SampleFormat>(input->sample_format));

  // `copy_size_per_channel` is the exact payload size expected by
  // AudioBuffer::CopyFrom().
  const size_t copy_size_per_channel =
      base::CheckMul(input->frame_count, bytes_per_channel).ValueOrDefault(0u);

  // `bytes_per_channel` could be 0 if we received a kUnknownFormat. In that
  // case, and in the case of a overflow below, `min_data_size` will be 0,
  // and we will return an EOS below.
  const size_t min_data_size =
      base::CheckMul(input->channel_count, copy_size_per_channel)
          .ValueOrDefault(0u);
  if (!copy_size_per_channel || !min_data_size ||
      input->data.size() < min_data_size ||
      input->data.size() % input->channel_count != 0) {
    DLOG(ERROR) << "Received invalid AudioBuffer, replace it with EOS.";
    return media::AudioBuffer::CreateEOSBuffer();
  }

  // Setup channel spans. AudioBuffer::CopyFrom() will only use the first
  // one in the case of interleaved data.
  const auto input_data = base::as_byte_span(input->data);
  std::vector<base::span<const uint8_t>> channel_spans;
  if (media::IsInterleaved(input->sample_format)) {
    channel_spans.push_back(input_data.first(min_data_size));
  } else {
    // `source_size_per_channel` is the stride in the
    // serialized buffer, which may include alignment padding.
    const size_t source_size_per_channel =
        input->data.size() / input->channel_count;
    channel_spans.resize(input->channel_count);

    for (int i = 0; i < input->channel_count; ++i) {
      channel_spans[i] =
          base::as_byte_span(input->data)
              .subspan(i * source_size_per_channel, copy_size_per_channel);
    }
  }
  return media::AudioBuffer::CopyFrom(
      input->sample_format, input->channel_layout, input->channel_count,
      input->sample_rate, input->frame_count, channel_spans, input->timestamp);
}

}  // namespace mojo
