// Copyright 2018 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 "base/fuchsia/async_dispatcher.h"

#include <lib/async/default.h>
#include <lib/async/task.h>
#include <lib/async/wait.h>
#include <lib/zx/handle.h>
#include <lib/zx/time.h>
#include <zircon/syscalls.h>

#include "base/fuchsia/fuchsia_logging.h"

namespace base {

namespace {

template <typename T>
uintptr_t key_from_ptr(T* ptr) {
  return reinterpret_cast<uintptr_t>(ptr);
};

}  // namespace

class AsyncDispatcher::WaitState : public LinkNode<WaitState> {
 public:
  explicit WaitState(AsyncDispatcher* async_dispatcher) {
    async_dispatcher->wait_list_.Append(this);
  }
  ~WaitState() { RemoveFromList(); }

  async_wait_t* wait() {
    // WaitState objects are allocated in-place in the |state| field of an
    // enclosing async_wait_t, so async_wait_t address can be calculated by
    // subtracting state offset in async_wait_t from |this|.
    static_assert(std::is_standard_layout<async_wait_t>(),
                  "async_wait_t is expected to have standard layout.");
    return reinterpret_cast<async_wait_t*>(reinterpret_cast<uint8_t*>(this) -
                                           offsetof(async_wait_t, state));
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(WaitState);
};

class AsyncDispatcher::TaskState : public LinkNode<TaskState> {
 public:
  explicit TaskState(LinkNode<TaskState>* previous_task) {
    InsertAfter(previous_task);
  }
  ~TaskState() { RemoveFromList(); }

  async_task_t* task() {
    // TaskState objects are allocated in-place in the |state| field of an
    // enclosing async_task_t, so async_task_t address can be calculated by
    // subtracting state offset in async_task_t from |this|.
    static_assert(std::is_standard_layout<async_task_t>(),
                  "async_task_t is expected to have standard layout.");
    return reinterpret_cast<async_task_t*>(reinterpret_cast<uint8_t*>(this) -
                                           offsetof(async_task_t, state));
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(TaskState);
};

AsyncDispatcher::AsyncDispatcher() : ops_storage_({}) {
  zx_status_t status = zx::port::create(0u, &port_);
  ZX_DCHECK(status == ZX_OK, status);

  status = zx::timer::create(0u, ZX_CLOCK_MONOTONIC, &timer_);
  ZX_DCHECK(status == ZX_OK, status);
  status = timer_.wait_async(port_, key_from_ptr(&timer_), ZX_TIMER_SIGNALED,
                             ZX_WAIT_ASYNC_REPEATING);
  ZX_DCHECK(status == ZX_OK, status);

  status = zx::event::create(0, &stop_event_);
  ZX_DCHECK(status == ZX_OK, status);
  status = stop_event_.wait_async(port_, key_from_ptr(&stop_event_),
                                  ZX_EVENT_SIGNALED, ZX_WAIT_ASYNC_REPEATING);
  ZX_DCHECK(status == ZX_OK, status);

  ops_storage_.v1.now = NowOp;
  ops_storage_.v1.begin_wait = BeginWaitOp;
  ops_storage_.v1.cancel_wait = CancelWaitOp;
  ops_storage_.v1.post_task = PostTaskOp;
  ops_storage_.v1.cancel_task = CancelTaskOp;
  ops_storage_.v1.queue_packet = QueuePacketOp;
  ops_storage_.v1.set_guest_bell_trap = SetGuestBellTrapOp;
  ops = &ops_storage_;

  DCHECK(!async_get_default());
  async_set_default(this);
}

AsyncDispatcher::~AsyncDispatcher() {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK_EQ(async_get_default(), this);

  // Some waits and tasks may be canceled while the dispatcher is being
  // destroyed, so pop-from-head until none remain.

  while (!wait_list_.empty()) {
    WaitState* state = wait_list_.head()->value();
    async_wait_t* wait = state->wait();
    state->~WaitState();
    wait->handler(this, wait, ZX_ERR_CANCELED, nullptr);
  }

  while (!task_list_.empty()) {
    TaskState* state = task_list_.head()->value();
    async_task_t* task = state->task();
    state->~TaskState();
    task->handler(this, task, ZX_ERR_CANCELED);
  }

  async_set_default(nullptr);
}

zx_status_t AsyncDispatcher::DispatchOrWaitUntil(zx_time_t deadline) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  zx_port_packet_t packet = {};
  zx_status_t status = port_.wait(zx::time(deadline), &packet);
  if (status != ZX_OK)
    return status;

  if (packet.type == ZX_PKT_TYPE_SIGNAL_ONE ||
      packet.type == ZX_PKT_TYPE_SIGNAL_REP) {
    if (packet.key == key_from_ptr(&timer_)) {
      // |timer_| has expired.
      DCHECK(packet.signal.observed & ZX_TIMER_SIGNALED);
      DispatchTasks();
      return ZX_OK;
    } else if (packet.key == key_from_ptr(&stop_event_)) {
      // Stop() was called.
      DCHECK(packet.signal.observed & ZX_EVENT_SIGNALED);
      status = zx_object_signal(stop_event_.get(), ZX_EVENT_SIGNALED, 0);
      ZX_DCHECK(status == ZX_OK, status);
      return ZX_ERR_CANCELED;
    } else {
      DCHECK_EQ(packet.type, ZX_PKT_TYPE_SIGNAL_ONE);
      async_wait_t* wait = reinterpret_cast<async_wait_t*>(packet.key);

      // Clean the state before invoking the handler: it may destroy the wait.
      WaitState* state = reinterpret_cast<WaitState*>(&wait->state);
      state->~WaitState();

      wait->handler(this, wait, packet.status, &packet.signal);

      return ZX_OK;
    }
  }

  NOTREACHED();
  return ZX_ERR_INTERNAL;
}

void AsyncDispatcher::Stop() {
  // Can be called on any thread.
  zx_status_t status =
      zx_object_signal(stop_event_.get(), 0, ZX_EVENT_SIGNALED);
  ZX_DCHECK(status == ZX_OK, status);
}

zx_time_t AsyncDispatcher::NowOp(async_t* async) {
  DCHECK(async);
  return zx_clock_get(ZX_CLOCK_MONOTONIC);
}

zx_status_t AsyncDispatcher::BeginWaitOp(async_t* async, async_wait_t* wait) {
  return static_cast<AsyncDispatcher*>(async)->BeginWait(wait);
}

zx_status_t AsyncDispatcher::CancelWaitOp(async_t* async, async_wait_t* wait) {
  return static_cast<AsyncDispatcher*>(async)->CancelWait(wait);
}

zx_status_t AsyncDispatcher::PostTaskOp(async_t* async, async_task_t* task) {
  return static_cast<AsyncDispatcher*>(async)->PostTask(task);
}

zx_status_t AsyncDispatcher::CancelTaskOp(async_t* async, async_task_t* task) {
  return static_cast<AsyncDispatcher*>(async)->CancelTask(task);
}

zx_status_t AsyncDispatcher::QueuePacketOp(async_t* async,
                                           async_receiver_t* receiver,
                                           const zx_packet_user_t* data) {
  return ZX_ERR_NOT_SUPPORTED;
}

zx_status_t AsyncDispatcher::SetGuestBellTrapOp(async_t* async,
                                                async_guest_bell_trap_t* trap,
                                                zx_handle_t guest,
                                                zx_vaddr_t addr,
                                                size_t length) {
  return ZX_ERR_NOT_SUPPORTED;
}

zx_status_t AsyncDispatcher::BeginWait(async_wait_t* wait) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  static_assert(sizeof(AsyncDispatcher::WaitState) <= sizeof(async_state_t),
                "WaitState is too big");
  WaitState* state = new (&wait->state) WaitState(this);
  zx_status_t status =
      zx::unowned_handle(wait->object)
          ->wait_async(port_, reinterpret_cast<uintptr_t>(wait), wait->trigger,
                       ZX_WAIT_ASYNC_ONCE);

  if (status != ZX_OK)
    state->~WaitState();

  return status;
}

zx_status_t AsyncDispatcher::CancelWait(async_wait_t* wait) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  zx_status_t status =
      port_.cancel(wait->object, reinterpret_cast<uintptr_t>(wait));
  if (status == ZX_OK) {
    WaitState* state = reinterpret_cast<WaitState*>(&(wait->state));
    state->~WaitState();
  }

  return status;
}

zx_status_t AsyncDispatcher::PostTask(async_task_t* task) {
  // Can be called on any thread.
  AutoLock lock(lock_);

  // Find correct position for the new task in |task_list_| to keep the list
  // sorted by deadline. This implementation has O(N) complexity, but it's
  // acceptable - async task are not expected to be used frequently.
  // TODO(sergeyu): Consider using a more efficient data structure if tasks
  // performance becomes important.
  LinkNode<TaskState>* node;
  for (node = task_list_.head(); node != task_list_.end();
       node = node->previous()) {
    if (task->deadline >= node->value()->task()->deadline)
      break;
  }

  static_assert(sizeof(AsyncDispatcher::TaskState) <= sizeof(async_state_t),
                "TaskState is too big");

  // Will insert new task after |node|.
  new (&task->state) TaskState(node);

  if (reinterpret_cast<TaskState*>(&task->state) == task_list_.head()) {
    // Task inserted at head. Earliest deadline changed.
    RestartTimerLocked();
  }

  return ZX_OK;
}

zx_status_t AsyncDispatcher::CancelTask(async_task_t* task) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  AutoLock lock(lock_);

  if (!task->state.reserved[0])
    return ZX_ERR_NOT_FOUND;

  TaskState* state = reinterpret_cast<TaskState*>(&task->state);
  state->~TaskState();

  return ZX_OK;
}

void AsyncDispatcher::DispatchTasks() {
  // Snapshot now value to set implicit bound for the tasks that will run before
  // DispatchTasks() returns. This also helps to avoid calling zx_clock_get()
  // more than necessary.
  zx_time_t now = zx_clock_get(ZX_CLOCK_MONOTONIC);

  while (true) {
    async_task_t* task;
    {
      AutoLock lock(lock_);
      if (task_list_.empty())
        break;

      TaskState* task_state = task_list_.head()->value();
      task = task_state->task();

      if (task->deadline > now) {
        RestartTimerLocked();
        break;
      }

      task_state->~TaskState();

      // ~TaskState() is expected to reset the state to 0. The destructor
      // removes the task from the |task_list_| and LinkNode::RemoveFromList()
      // sets both its fields to nullptr, which is equivalent to resetting the
      // state to 0.
      DCHECK_EQ(task->state.reserved[0], 0u);
    }

    // The handler is responsible for freeing the |task| or it may reuse it.
    task->handler(this, task, ZX_OK);
  }
}

void AsyncDispatcher::RestartTimerLocked() {
  lock_.AssertAcquired();

  if (task_list_.empty())
    return;
  zx_time_t deadline = task_list_.head()->value()->task()->deadline;
  zx_status_t status = timer_.set(zx::time(deadline), zx::duration());
  ZX_DCHECK(status == ZX_OK, status);
}

}  // namespace base
