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

#include "mojo/public/tools/fuzzers/mojolpm.h"

#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/no_destructor.h"
#include "mojo/public/c/system/data_pipe.h"
#include "mojo/public/cpp/system/data_pipe.h"

namespace mojolpm {

const uint32_t kPipeElementMaxSize = 0x1000u;
const uint32_t kPipeCapacityMaxSize = 0x100000u;
const uint32_t kPipeActionMaxSize = 0x100000u;

const uint64_t kSharedBufferMaxSize = 0x100000u;
const uint32_t kSharedBufferActionMaxSize = 0x100000u;

Context::Context() = default;

Context::~Context() = default;

Context::Storage::Storage() = default;

Context::Storage::~Storage() = default;

void Context::StartTestcase() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

void Context::EndTestcase() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // Some fuzzers need to destroy the fuzzer thread along with their testcase,
  // so we need to detach the sequence checker here so that it will be attached
  // to the new sequence for the next testcase.
  DETACH_FROM_SEQUENCE(sequence_checker_);
  // We need to destroy all Remotes/Receivers before we start destroying other
  // objects (like callbacks).
  for (const TypeId& interface_type_id : interface_type_ids_) {
    auto instances_iter = instances_.find(interface_type_id);
    if (instances_iter != instances_.end()) {
      instances_iter->second.clear();
    }
  }
  interface_type_ids_.clear();
  instances_.clear();
}

void Context::StartDeserialization() {
  rollback_.clear();
}

void Context::EndDeserialization(Rollback rollback) {
  if (rollback == Rollback::kRollback) {
    for (const auto& entry : rollback_) {
      RemoveInstance(entry.first, entry.second);
    }
  }
  rollback_.clear();
}

void Context::RemoveInstance(TypeId type_id, uint32_t id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  auto instances_iter = instances_.find(type_id);
  if (instances_iter != instances_.end()) {
    auto& instance_map = instances_iter->second;

    // normalize id to [0, max_id]
    if (instance_map.size() > 0 && instance_map.rbegin()->first < id) {
      id = id % (instance_map.rbegin()->first + 1);
    }

    // choose the first valid entry after id
    auto instance = instance_map.lower_bound(id);
    if (instance == instance_map.end()) {
      mojolpmdbg("failed!\n");
      return;
    }

    instance_map.erase(instance);
  } else {
    mojolpmdbg("failed!\n");
  }
}

Context* GetContext() {
  static base::NoDestructor<Context> context;
  return context.get();
}

bool FromProto(const bool& input, bool& output) {
  output = input;
  return true;
}

bool ToProto(const bool& input, bool& output) {
  output = input;
  return true;
}

bool FromProto(const ::google::protobuf::int32& input, int8_t& output) {
  output = input;
  return true;
}

bool ToProto(const int8_t& input, ::google::protobuf::int32& output) {
  output = input;
  return true;
}

bool FromProto(const ::google::protobuf::int32& input, int16_t& output) {
  output = input;
  return true;
}

bool ToProto(const int16_t& input, ::google::protobuf::int32& output) {
  output = input;
  return true;
}

bool FromProto(const ::google::protobuf::int32& input, int32_t& output) {
  output = input;
  return true;
}

bool ToProto(const int32_t& input, ::google::protobuf::int32& output) {
  output = input;
  return true;
}

bool FromProto(const ::google::protobuf::int64& input, int64_t& output) {
  output = input;
  return true;
}

bool ToProto(const int64_t& input, ::google::protobuf::int64& output) {
  output = input;
  return true;
}

bool FromProto(const ::google::protobuf::uint32& input, uint8_t& output) {
  output = input;
  return true;
}

bool ToProto(const uint8_t& input, ::google::protobuf::uint32& output) {
  output = input;
  return true;
}

bool FromProto(const ::google::protobuf::uint32& input, uint16_t& output) {
  output = input;
  return true;
}

bool ToProto(const uint16_t& input, ::google::protobuf::uint32& output) {
  output = input;
  return true;
}

bool FromProto(const ::google::protobuf::uint32& input, uint32_t& output) {
  output = input;
  return true;
}

bool ToProto(const uint32_t& input, ::google::protobuf::uint32& output) {
  output = input;
  return true;
}

bool FromProto(const ::google::protobuf::uint64& input, uint64_t& output) {
  output = input;
  return true;
}

bool ToProto(const uint64_t& input, ::google::protobuf::uint64& output) {
  output = input;
  return true;
}

bool FromProto(const double& input, double& output) {
  output = input;
  return true;
}

bool ToProto(const double& input, double& output) {
  output = input;
  return true;
}

bool FromProto(const float& input, float& output) {
  output = input;
  return true;
}

bool ToProto(const float& input, float& output) {
  output = input;
  return true;
}

bool FromProto(const std::string& input, std::string& output) {
  output = input;
  return true;
}

bool ToProto(const std::string& input, std::string& output) {
  output = input;
  return true;
}

bool FromProto(const ::mojolpm::Handle& input, mojo::ScopedHandle& output) {
  return true;
}

bool ToProto(const mojo::ScopedHandle& input, ::mojolpm::Handle& output) {
  return true;
}

bool FromProto(const ::mojolpm::DataPipeConsumerHandle& input,
               mojo::ScopedDataPipeConsumerHandle& output) {
  bool result = false;

  if (input.instance_case() == ::mojolpm::DataPipeConsumerHandle::kOld) {
    auto old = mojolpm::GetContext()
                   ->GetAndRemoveInstance<mojo::ScopedDataPipeConsumerHandle>(
                       input.old());
    if (old) {
      output = std::move(*old.release());
    }
  } else {
    MojoCreateDataPipeOptions options;
    mojo::ScopedDataPipeConsumerHandle consumer;
    mojo::ScopedDataPipeProducerHandle producer;

    options.struct_size = sizeof(MojoCreateDataPipeOptions);
    options.flags = input.new_().flags();
    options.element_num_bytes =
        std::min(input.new_().element_num_bytes(), kPipeElementMaxSize);
    options.capacity_num_bytes =
        std::min(input.new_().capacity_num_bytes(), kPipeCapacityMaxSize);

    if (MOJO_RESULT_OK == mojo::CreateDataPipe(&options, producer, consumer)) {
      result = true;
      output = std::move(consumer);
      mojolpm::GetContext()->AddInstance(std::move(producer));
    }
  }

  return result;
}

bool ToProto(const mojo::ScopedDataPipeConsumerHandle& input,
             ::mojolpm::DataPipeConsumerHandle& output) {
  return true;
}

bool FromProto(const ::mojolpm::DataPipeProducerHandle& input,
               mojo::ScopedDataPipeProducerHandle& output) {
  bool result = false;

  if (input.instance_case() == ::mojolpm::DataPipeProducerHandle::kOld) {
    auto old = mojolpm::GetContext()
                   ->GetAndRemoveInstance<mojo::ScopedDataPipeProducerHandle>(
                       input.old());
    if (old) {
      output = std::move(*old.release());
    }
  } else {
    MojoCreateDataPipeOptions options;
    mojo::ScopedDataPipeConsumerHandle consumer;
    mojo::ScopedDataPipeProducerHandle producer;

    options.struct_size = sizeof(MojoCreateDataPipeOptions);
    options.flags = input.new_().flags();
    options.element_num_bytes =
        std::min(input.new_().element_num_bytes(), kPipeElementMaxSize);
    options.capacity_num_bytes =
        std::min(input.new_().capacity_num_bytes(), kPipeCapacityMaxSize);

    if (MOJO_RESULT_OK == mojo::CreateDataPipe(&options, producer, consumer)) {
      result = true;
      output = std::move(producer);
      mojolpm::GetContext()->AddInstance(std::move(consumer));
    }
  }

  return result;
}

bool ToProto(const mojo::ScopedDataPipeProducerHandle& input,
             ::mojolpm::DataPipeProducerHandle& output) {
  return true;
}

bool FromProto(const ::mojolpm::MessagePipeHandle& input,
               mojo::ScopedMessagePipeHandle& output) {
  return true;
}

bool ToProto(const mojo::ScopedMessagePipeHandle& input,
             ::mojolpm::MessagePipeHandle& output) {
  return true;
}

bool FromProto(const ::mojolpm::SharedBufferHandle& input,
               mojo::ScopedSharedBufferHandle& output) {
  bool result = false;

  if (input.instance_case() == ::mojolpm::SharedBufferHandle::kOld) {
    auto old =
        mojolpm::GetContext()
            ->GetAndRemoveInstance<mojo::ScopedSharedBufferHandle>(input.old());
    if (old) {
      output = std::move(*old.release());
    }
  } else {
    output = mojo::SharedBufferHandle::Create(
        std::min(input.new_().num_bytes(), kSharedBufferMaxSize));
    result = true;
  }

  return result;
}

bool ToProto(const mojo::ScopedSharedBufferHandle& input,
             ::mojolpm::SharedBufferHandle& output) {
  return true;
}

bool FromProto(const ::mojolpm::PlatformHandle& input,
               mojo::PlatformHandle& output) {
  return true;
}

bool ToProto(const mojo::PlatformHandle& input,
             ::mojolpm::PlatformHandle& output) {
  return true;
}

void HandleDataPipeRead(const ::mojolpm::DataPipeRead& input) {
  mojo::ScopedDataPipeConsumerHandle* consumer_ptr = nullptr;

  if (input.handle().instance_case() ==
      ::mojolpm::DataPipeConsumerHandle::kOld) {
    consumer_ptr =
        mojolpm::GetContext()->GetInstance<mojo::ScopedDataPipeConsumerHandle>(
            input.handle().old());
  } else {
    MojoCreateDataPipeOptions options;
    mojo::ScopedDataPipeConsumerHandle consumer;
    mojo::ScopedDataPipeProducerHandle producer;

    options.struct_size = sizeof(MojoCreateDataPipeOptions);
    options.flags = input.handle().new_().flags();
    options.element_num_bytes = std::min(
        input.handle().new_().element_num_bytes(), kPipeElementMaxSize);
    options.capacity_num_bytes = std::min(
        input.handle().new_().capacity_num_bytes(), kPipeCapacityMaxSize);

    if (MOJO_RESULT_OK == mojo::CreateDataPipe(&options, producer, consumer)) {
      int id = mojolpm::GetContext()->AddInstance(std::move(consumer));
      mojolpm::GetContext()->AddInstance(std::move(producer));
      consumer_ptr = mojolpm::GetContext()
                         ->GetInstance<mojo::ScopedDataPipeConsumerHandle>(id);
    }
  }

  if (consumer_ptr) {
    size_t size = size_t{input.size()};
    if (size > kPipeActionMaxSize) {
      size = kPipeActionMaxSize;
    }
    std::vector<uint8_t> data(size);
    size_t bytes_read = 0;
    consumer_ptr->get().ReadData(MOJO_READ_DATA_FLAG_NONE, data, bytes_read);
  }
}

void HandleDataPipeWrite(const ::mojolpm::DataPipeWrite& input) {
  mojo::ScopedDataPipeProducerHandle* producer_ptr = nullptr;

  if (input.handle().instance_case() ==
      ::mojolpm::DataPipeProducerHandle::kOld) {
    producer_ptr =
        mojolpm::GetContext()->GetInstance<mojo::ScopedDataPipeProducerHandle>(
            input.handle().old());
  } else {
    MojoCreateDataPipeOptions options;
    mojo::ScopedDataPipeConsumerHandle consumer;
    mojo::ScopedDataPipeProducerHandle producer;

    options.struct_size = sizeof(MojoCreateDataPipeOptions);
    options.flags = input.handle().new_().flags();
    options.element_num_bytes = std::min(
        input.handle().new_().element_num_bytes(), kPipeElementMaxSize);
    options.capacity_num_bytes = std::min(
        input.handle().new_().capacity_num_bytes(), kPipeCapacityMaxSize);

    if (MOJO_RESULT_OK == mojo::CreateDataPipe(&options, producer, consumer)) {
      mojolpm::GetContext()->AddInstance(std::move(consumer));
      int id = mojolpm::GetContext()->AddInstance(std::move(producer));
      producer_ptr = mojolpm::GetContext()
                         ->GetInstance<mojo::ScopedDataPipeProducerHandle>(id);
    }
  }

  if (producer_ptr) {
    size_t size = input.data().size();
    if (size > kPipeActionMaxSize) {
      size = kPipeActionMaxSize;
    }
    size_t bytes_written = 0;
    producer_ptr->get().WriteData(base::as_byte_span(input.data()),
                                  MOJO_WRITE_DATA_FLAG_NONE, bytes_written);
  }
}

void HandleSharedBufferWrite(const ::mojolpm::SharedBufferWrite& input) {
  mojo::ScopedSharedBufferHandle handle;
  if (!FromProto(input.handle(), handle)) {
    return;
  }
  size_t size = input.data().size();
  if (size > kSharedBufferActionMaxSize) {
    size = kSharedBufferActionMaxSize;
  }
  size = std::min(handle->GetSize(), static_cast<uint64_t>(size));
  auto mem = handle->Map(size);
  if (!mem) {
    return;
  }
  UNSAFE_TODO(std::memcpy(mem.get(), input.data().data(), size));
}

void HandleDataPipeConsumerClose(
    const ::mojolpm::DataPipeConsumerClose& input) {
  mojolpm::GetContext()->RemoveInstance<mojo::ScopedDataPipeConsumerHandle>(
      input.id());
}

void HandleDataPipeProducerClose(
    const ::mojolpm::DataPipeProducerClose& input) {
  mojolpm::GetContext()->RemoveInstance<mojo::ScopedDataPipeProducerHandle>(
      input.id());
}

void HandleSharedBufferRelease(const ::mojolpm::SharedBufferRelease& input) {
  mojolpm::GetContext()->RemoveInstance<mojo::ScopedSharedBufferHandle>(
      input.id());
}
}  // namespace mojolpm
