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

#include "gpu/command_buffer/service/common_decoder.h"

#include <stddef.h>
#include <stdint.h>
#include <string.h>

#include <algorithm>
#include <string_view>

#include "base/compiler_specific.h"
#include "base/containers/span_reader.h"
#include "base/numerics/safe_math.h"
#include "base/pickle.h"
#include "base/strings/string_view_util.h"
#include "gpu/command_buffer/service/command_buffer_service.h"
#include "gpu/command_buffer/service/decoder_client.h"

namespace gpu {
namespace {
static const size_t kDefaultMaxBucketSize = 1u << 30;  // 1 GB

uint32_t LoadU32Unaligned(const void* ptr) {
  uint32_t ret;
  UNSAFE_TODO(memcpy(&ret, ptr, sizeof(uint32_t)));
  return ret;
}

void StoreU32Unaligned(uint32_t v, void* ptr) {
  UNSAFE_TODO(memcpy(ptr, &v, sizeof(uint32_t)));
}

}  // namespace

const CommonDecoder::CommandInfo CommonDecoder::command_info[] = {
#define COMMON_COMMAND_BUFFER_CMD_OP(name)                       \
  {                                                              \
    &CommonDecoder::Handle##name, cmd::name::kArgFlags,          \
        cmd::name::cmd_flags,                                    \
        sizeof(cmd::name) / sizeof(CommandBufferEntry) - 1,      \
  }                                                              \
  ,  /* NOLINT */
  COMMON_COMMAND_BUFFER_CMDS(COMMON_COMMAND_BUFFER_CMD_OP)
  #undef COMMON_COMMAND_BUFFER_CMD_OP
};


CommonDecoder::Bucket::Bucket() : size_(0) {}

CommonDecoder::Bucket::~Bucket() = default;

base::span<uint8_t> CommonDecoder::Bucket::GetDataAsByteSpan(size_t offset,
                                                             size_t size) {
  if (OffsetSizeValid(offset, size)) {
    return data_.subspan(offset, size);
  }
  return {};
}

void CommonDecoder::Bucket::SetSize(size_t size) {
  if (size != size_) {
    // WithSize() value-initializes the elements, i.e. the buffer will be
    // zero-initialized.
    data_ = base::HeapArray<uint8_t>::WithSize(size);
    size_ = size;
  }
}

bool CommonDecoder::Bucket::SetData(
    const volatile void* src, size_t offset, size_t size) {
  if (OffsetSizeValid(offset, size)) {
    auto src_span = UNSAFE_TODO(
        base::span(static_cast<const volatile uint8_t*>(src), size));
    data_.subspan(offset, size).copy_from(src_span);
    return true;
  }
  return false;
}

void CommonDecoder::Bucket::SetFromString(const char* str) {
  // Strings are passed nullptr terminated to distinguish between empty string
  // and no string.
  if (!str) {
    SetSize(0);
  } else {
    std::string_view str_view(str);
    size_t size = str_view.size() + 1;
    SetSize(size);
    SetData(str_view.data(), 0, size);
  }
}

bool CommonDecoder::Bucket::GetAsString(std::string* str) {
  DCHECK(str);
  if (size_ == 0) {
    return false;
  }
  base::span<const uint8_t> bytes = GetDataAsByteSpan(0, size_ - 1);
  str->assign(base::as_string_view(bytes));
  return true;
}

bool CommonDecoder::Bucket::GetAsStrings(
    GLsizei* _count, std::vector<char*>* _string, std::vector<GLint>* _length) {
  const size_t bucket_size = this->size();
  if (bucket_size < sizeof(GLint)) {
    return false;
  }
  base::SpanReader reader{GetDataAsByteSpan(0, bucket_size)};

  std::optional<int32_t> count32 = reader.ReadI32NativeEndian();
  if (!count32.has_value() || *count32 < 0) {
    return false;
  }
  const GLsizei count = static_cast<GLsizei>(*count32);

  // Don't pre-size the vectors from the untrusted `count`: a bogus value would
  // try to allocate a huge amount of memory. Reserve is bounded by the bucket
  // size instead, and SpanReader fails gracefully once the bucket runs out of
  // data.
  const size_t reserve = bucket_size / (sizeof(GLint) + 1u);
  std::vector<GLint> lengths;
  lengths.reserve(reserve);
  for (GLsizei ii = 0; ii < count; ++ii) {
    std::optional<int32_t> length32 = reader.ReadI32NativeEndian();
    if (!length32.has_value() || *length32 < 0) {
      return false;
    }
    lengths.push_back(static_cast<GLint>(*length32));
  }

  std::vector<char*> strs;
  strs.reserve(reserve);
  for (const GLint length : lengths) {
    std::optional<base::span<uint8_t>> str =
        reader.Read(static_cast<size_t>(length) + 1u);
    if (!str.has_value()) {
      return false;
    }
    if ((*str)[length] != 0) {
      return false;
    }
    strs.push_back(base::as_writable_chars(*str).data());
  }

  if (reader.remaining() != 0u) {
    return false;
  }

  DCHECK(_count && _string && _length);
  *_count = count;
  *_string = strs;
  *_length = lengths;
  return true;
}

bool CommonDecoder::Bucket::OffsetSizeValid(size_t offset, size_t size) const {
  size_t end = 0;
  if (!base::CheckAdd<size_t>(offset, size).AssignIfValid(&end))
    return false;
  return end <= size_;
}

CommonDecoder::CommonDecoder(DecoderClient* client,
                             CommandBufferServiceBase* command_buffer_service)
    : command_buffer_service_(command_buffer_service),
      client_(client),
      max_bucket_size_(kDefaultMaxBucketSize) {
  DCHECK(command_buffer_service_);
}

CommonDecoder::~CommonDecoder() = default;

std::optional<base::span<uint8_t>> CommonDecoder::GetSharedMemoryAsByteSpan(
    uint32_t shm_id,
    uint32_t offset,
    uint32_t size_in_bytes) {
  scoped_refptr<gpu::Buffer> buffer =
      command_buffer_service_->GetTransferBuffer(shm_id);
  if (!buffer) {
    return std::nullopt;
  }
  base::CheckedNumeric<uint32_t> end = offset;
  end += size_in_bytes;
  if (!end.IsValid() || end.ValueOrDie() > buffer->size()) {
    return std::nullopt;
  }
  return buffer->GetSpanData(offset, size_in_bytes);
}

void* CommonDecoder::GetAddressAndCheckSize(unsigned int shm_id,
                                            unsigned int data_offset,
                                            unsigned int data_size) {
  scoped_refptr<gpu::Buffer> buffer =
      command_buffer_service_->GetTransferBuffer(shm_id);
  if (!buffer.get())
    return nullptr;
  return buffer->GetDataAddress(data_offset, data_size);
}

void* CommonDecoder::GetAddressAndSize(unsigned int shm_id,
                                       unsigned int data_offset,
                                       unsigned int minimum_size,
                                       unsigned int* data_size) {
  scoped_refptr<gpu::Buffer> buffer =
      command_buffer_service_->GetTransferBuffer(shm_id);
  if (!buffer.get() || buffer->GetRemainingSize(data_offset) < minimum_size)
    return nullptr;
  return buffer->GetDataAddressAndSize(data_offset, data_size);
}

unsigned int CommonDecoder::GetSharedMemorySize(unsigned int shm_id,
                                                unsigned int offset) {
  scoped_refptr<gpu::Buffer> buffer =
      command_buffer_service_->GetTransferBuffer(shm_id);
  if (!buffer.get())
    return 0;
  return buffer->GetRemainingSize(offset);
}

scoped_refptr<gpu::Buffer> CommonDecoder::GetSharedMemoryBuffer(
    unsigned int shm_id) {
  return command_buffer_service_->GetTransferBuffer(shm_id);
}

const char* CommonDecoder::GetCommonCommandName(
    cmd::CommandId command_id) const {
  return cmd::GetCommandName(command_id);
}

CommonDecoder::Bucket* CommonDecoder::GetBucket(uint32_t bucket_id) const {
  BucketMap::const_iterator iter(buckets_.find(bucket_id));
  return iter != buckets_.end() ? &(*iter->second) : nullptr;
}

CommonDecoder::Bucket* CommonDecoder::CreateBucket(uint32_t bucket_id) {
  Bucket* bucket = GetBucket(bucket_id);
  if (!bucket) {
    bucket = new Bucket();
    buckets_[bucket_id] = std::unique_ptr<Bucket>(bucket);
  }
  return bucket;
}

namespace {

// Returns the address of the first byte after a struct.
template <typename T>
const volatile void* AddressAfterStruct(const volatile T& pod) {
  return UNSAFE_TODO(reinterpret_cast<const volatile uint8_t*>(&pod) +
                     sizeof(pod));
}

// Returns the address of the frst byte after the struct.
template <typename RETURN_TYPE, typename COMMAND_TYPE>
RETURN_TYPE GetImmediateDataAs(const volatile COMMAND_TYPE& pod) {
  return static_cast<RETURN_TYPE>(
      const_cast<volatile void*>(AddressAfterStruct(pod)));
}

}  // anonymous namespace.

// Decode command with its arguments, and call the corresponding method.
// Note: args is a pointer to the command buffer. As such, it could be changed
// by a (malicious) client at any time, so if validation has to happen, it
// should operate on a copy of them.
error::Error CommonDecoder::DoCommonCommand(unsigned int command,
                                            unsigned int arg_count,
                                            const volatile void* cmd_data) {
  if (command < std::size(command_info)) {
    const CommandInfo& info = UNSAFE_TODO(command_info[command]);
    unsigned int info_arg_count = static_cast<unsigned int>(info.arg_count);
    if ((info.arg_flags == cmd::kFixed && arg_count == info_arg_count) ||
        (info.arg_flags == cmd::kAtLeastN && arg_count >= info_arg_count)) {
      uint32_t immediate_data_size =
          (arg_count - info_arg_count) * sizeof(CommandBufferEntry);  // NOLINT
      return (this->*info.cmd_handler)(immediate_data_size, cmd_data);
    } else {
      return error::kInvalidArguments;
    }
  }
  return error::kUnknownCommand;
}

error::Error CommonDecoder::HandleNoop(uint32_t immediate_data_size,
                                       const volatile void* cmd_data) {
  return error::kNoError;
}

error::Error CommonDecoder::HandleSetToken(uint32_t immediate_data_size,
                                           const volatile void* cmd_data) {
  const volatile cmd::SetToken& args =
      *static_cast<const volatile cmd::SetToken*>(cmd_data);
  command_buffer_service_->SetToken(args.token);
  return error::kNoError;
}

error::Error CommonDecoder::HandleSetBucketSize(uint32_t immediate_data_size,
                                                const volatile void* cmd_data) {
  const volatile cmd::SetBucketSize& args =
      *static_cast<const volatile cmd::SetBucketSize*>(cmd_data);
  uint32_t bucket_id = args.bucket_id;
  uint32_t size = args.size;
  if (size > max_bucket_size_)
    return error::kOutOfBounds;

  Bucket* bucket = CreateBucket(bucket_id);
  bucket->SetSize(size);
  return error::kNoError;
}

error::Error CommonDecoder::HandleSetBucketData(uint32_t immediate_data_size,
                                                const volatile void* cmd_data) {
  const volatile cmd::SetBucketData& args =
      *static_cast<const volatile cmd::SetBucketData*>(cmd_data);
  uint32_t bucket_id = args.bucket_id;
  uint32_t offset = args.offset;
  uint32_t size = args.size;
  const void* data = GetSharedMemoryAs<const void*>(
      args.shared_memory_id, args.shared_memory_offset, size);
  if (!data) {
    return error::kInvalidArguments;
  }
  Bucket* bucket = GetBucket(bucket_id);
  if (!bucket) {
    return error::kInvalidArguments;
  }
  if (!bucket->SetData(data, offset, size)) {
    return error::kInvalidArguments;
  }

  return error::kNoError;
}

error::Error CommonDecoder::HandleSetBucketDataImmediate(
    uint32_t immediate_data_size,
    const volatile void* cmd_data) {
  const volatile cmd::SetBucketDataImmediate& args =
      *static_cast<const volatile cmd::SetBucketDataImmediate*>(cmd_data);
  const volatile void* data = GetImmediateDataAs<const volatile void*>(args);
  uint32_t bucket_id = args.bucket_id;
  uint32_t offset = args.offset;
  uint32_t size = args.size;
  if (size > immediate_data_size) {
    return error::kInvalidArguments;
  }
  Bucket* bucket = GetBucket(bucket_id);
  if (!bucket) {
    return error::kInvalidArguments;
  }
  if (!bucket->SetData(data, offset, size)) {
    return error::kInvalidArguments;
  }
  return error::kNoError;
}

error::Error CommonDecoder::HandleGetBucketStart(
    uint32_t immediate_data_size,
    const volatile void* cmd_data) {
  const volatile cmd::GetBucketStart& args =
      *static_cast<const volatile cmd::GetBucketStart*>(cmd_data);
  uint32_t bucket_id = args.bucket_id;
  // `result` may not be aligned, so cast to `void*` and use `memcpy` to load
  // and store.
  void* result = GetSharedMemoryAs<void*>(
      args.result_memory_id, args.result_memory_offset, sizeof(uint32_t));
  int32_t data_memory_id = args.data_memory_id;
  uint32_t data_memory_offset = args.data_memory_offset;
  uint32_t data_memory_size = args.data_memory_size;
  std::optional<base::span<uint8_t>> data;
  if (data_memory_size != 0 || data_memory_id != 0 || data_memory_offset != 0) {
    data = GetSharedMemoryAsByteSpan(data_memory_id, data_memory_offset,
                                     data_memory_size);
    if (!data.has_value()) {
      return error::kInvalidArguments;
    }
  }
  if (!result) {
    return error::kInvalidArguments;
  }
  // Check that the client initialized the result.
  if (LoadU32Unaligned(result) != 0) {
    return error::kInvalidArguments;
  }
  Bucket* bucket = GetBucket(bucket_id);
  if (!bucket) {
    return error::kInvalidArguments;
  }
  uint32_t bucket_size = bucket->size();
  StoreU32Unaligned(bucket_size, result);
  if (data.has_value()) {
    uint32_t size = std::min(data_memory_size, bucket_size);
    data->copy_prefix_from(bucket->GetDataAsByteSpan(0, size));
  }
  return error::kNoError;
}

error::Error CommonDecoder::HandleGetBucketData(uint32_t immediate_data_size,
                                                const volatile void* cmd_data) {
  const volatile cmd::GetBucketData& args =
      *static_cast<const volatile cmd::GetBucketData*>(cmd_data);
  uint32_t bucket_id = args.bucket_id;
  uint32_t offset = args.offset;
  uint32_t size = args.size;
  std::optional<base::span<uint8_t>> data = GetSharedMemoryAsByteSpan(
      args.shared_memory_id, args.shared_memory_offset, size);
  if (!data.has_value()) {
    return error::kInvalidArguments;
  }
  Bucket* bucket = GetBucket(bucket_id);
  if (!bucket) {
    return error::kInvalidArguments;
  }
  // Note that an empty span is returned both for a valid zero-sized request and
  // for an out-of-range one, so the range has to be validated explicitly.
  base::CheckedNumeric<size_t> end = offset;
  end += size;
  if (!end.IsValid() || end.ValueOrDie() > bucket->size()) {
    return error::kInvalidArguments;
  }
  data->copy_from(bucket->GetDataAsByteSpan(offset, size));
  return error::kNoError;
}

error::Error CommonDecoder::HandleInsertFenceSync(
    uint32_t immediate_data_size,
    const volatile void* cmd_data) {
  const volatile cmd::InsertFenceSync& c =
      *static_cast<const volatile cmd::InsertFenceSync*>(cmd_data);

  const uint64_t release_count = c.release_count();
  client_->OnFenceSyncRelease(release_count);
  // Exit inner command processing loop so that we check the scheduling state
  // and yield if necessary as we may have unblocked a higher priority
  // context.
  ExitCommandProcessingEarly();
  return error::kNoError;
}

}  // namespace gpu
