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

#include "ipc/ipc_perftest_util.h"

#include "base/logging.h"
#include "base/run_loop.h"
#include "ipc/ipc_channel_proxy.h"
#include "ipc/ipc_perftest_messages.h"
#include "mojo/core/embedder/embedder.h"
#include "mojo/core/test/multiprocess_test_helper.h"

namespace IPC {

scoped_refptr<base::SingleThreadTaskRunner> GetIOThreadTaskRunner() {
  scoped_refptr<base::TaskRunner> runner = mojo::core::GetIOTaskRunner();
  return scoped_refptr<base::SingleThreadTaskRunner>(
      static_cast<base::SingleThreadTaskRunner*>(runner.get()));
}

ChannelReflectorListener::ChannelReflectorListener() : channel_(NULL) {
  VLOG(1) << "Client listener up";
}

ChannelReflectorListener::~ChannelReflectorListener() {
  VLOG(1) << "Client listener down";
}

void ChannelReflectorListener::Init(Sender* channel,
                                    const base::Closure& quit_closure) {
  DCHECK(!channel_);
  channel_ = channel;
  quit_closure_ = quit_closure;
}

bool ChannelReflectorListener::OnMessageReceived(const Message& message) {
  CHECK(channel_);
  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP(ChannelReflectorListener, message)
    IPC_MESSAGE_HANDLER(TestMsg_Hello, OnHello)
    IPC_MESSAGE_HANDLER(TestMsg_Ping, OnPing)
    IPC_MESSAGE_HANDLER(TestMsg_SyncPing, OnSyncPing)
    IPC_MESSAGE_HANDLER(TestMsg_Quit, OnQuit)
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()
  return handled;
}

void ChannelReflectorListener::OnHello() {
  channel_->Send(new TestMsg_Hello);
}

void ChannelReflectorListener::OnPing(const std::string& payload) {
  channel_->Send(new TestMsg_Ping(payload));
}

void ChannelReflectorListener::OnSyncPing(const std::string& payload,
                                          std::string* response) {
  *response = payload;
}

void ChannelReflectorListener::OnQuit() {
  quit_closure_.Run();
}

void ChannelReflectorListener::Send(IPC::Message* message) {
  channel_->Send(message);
}

LockThreadAffinity::LockThreadAffinity(int cpu_number)
    : affinity_set_ok_(false) {
#if defined(OS_WIN)
  const DWORD_PTR thread_mask = static_cast<DWORD_PTR>(1) << cpu_number;
  old_affinity_ = SetThreadAffinityMask(GetCurrentThread(), thread_mask);
  affinity_set_ok_ = old_affinity_ != 0;
#elif defined(OS_LINUX)
  cpu_set_t cpuset;
  CPU_ZERO(&cpuset);
  CPU_SET(cpu_number, &cpuset);
  auto get_result = sched_getaffinity(0, sizeof(old_cpuset_), &old_cpuset_);
  DCHECK_EQ(0, get_result);
  auto set_result = sched_setaffinity(0, sizeof(cpuset), &cpuset);
  // Check for get_result failure, even though it should always succeed.
  affinity_set_ok_ = (set_result == 0) && (get_result == 0);
#endif
  if (!affinity_set_ok_)
    LOG(WARNING) << "Failed to set thread affinity to CPU " << cpu_number;
}

LockThreadAffinity::~LockThreadAffinity() {
  if (!affinity_set_ok_)
    return;
#if defined(OS_WIN)
  auto set_result = SetThreadAffinityMask(GetCurrentThread(), old_affinity_);
  DCHECK_NE(0u, set_result);
#elif defined(OS_LINUX)
  auto set_result = sched_setaffinity(0, sizeof(old_cpuset_), &old_cpuset_);
  DCHECK_EQ(0, set_result);
#endif
}

MojoPerfTestClient::MojoPerfTestClient()
    : listener_(new ChannelReflectorListener()) {
  mojo::core::test::MultiprocessTestHelper::ChildSetup();
}

MojoPerfTestClient::~MojoPerfTestClient() = default;

int MojoPerfTestClient::Run(MojoHandle handle) {
  handle_ = mojo::MakeScopedHandle(mojo::MessagePipeHandle(handle));
  LockThreadAffinity thread_locker(kSharedCore);

  base::RunLoop run_loop;
  std::unique_ptr<ChannelProxy> channel = IPC::ChannelProxy::Create(
      handle_.release(), Channel::MODE_CLIENT, listener_.get(),
      GetIOThreadTaskRunner(), base::ThreadTaskRunnerHandle::Get());
  listener_->Init(channel.get(), run_loop.QuitWhenIdleClosure());
  run_loop.Run();
  return 0;
}

ReflectorImpl::ReflectorImpl(mojo::ScopedMessagePipeHandle handle,
                             const base::Closure& quit_closure)
    : quit_closure_(quit_closure),
      binding_(this, IPC::mojom::ReflectorRequest(std::move(handle))) {}

ReflectorImpl::~ReflectorImpl() {
  ignore_result(binding_.Unbind().PassMessagePipe().release());
}

void ReflectorImpl::Ping(const std::string& value, PingCallback callback) {
  std::move(callback).Run(value);
}

void ReflectorImpl::SyncPing(const std::string& value, PingCallback callback) {
  std::move(callback).Run(value);
}

void ReflectorImpl::Quit() {
  if (quit_closure_)
    quit_closure_.Run();
}

}  // namespace IPC
