// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#include "platform/globals.h"
#if defined(DART_HOST_OS_FUCHSIA)

#include "bin/eventhandler.h"
#include "bin/eventhandler_fuchsia.h"

#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <unistd.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/object.h>
#include <zircon/syscalls/port.h>

#include "bin/fdutils.h"
#include "bin/lockers.h"
#include "bin/socket.h"
#include "bin/thread.h"
#include "bin/utils.h"
#include "platform/hashmap.h"
#include "platform/syslog.h"
#include "platform/utils.h"

// The EventHandler for Fuchsia uses its "ports v2" API:
// https://fuchsia.googlesource.com/fuchsia/+/HEAD/zircon/docs/syscalls/port_create.md
// This API does not have epoll()-like edge triggering (EPOLLET). Since clients
// of the EventHandler expect edge-triggered notifications, we must simulate it.
// When a packet from zx_port_wait() indicates that a signal is asserted for a
// handle, we unsubscribe from that signal until the event that asserted the
// signal can be processed. For example:
//
// 1. We get ZX_SOCKET_WRITABLE from zx_port_wait() for a handle.
// 2. We send kOutEvent to the Dart thread.
// 3. We unsubscribe from further ZX_SOCKET_WRITABLE signals for the handle.
// 4. Some time later the Dart thread actually does a write().
// 5. After writing, the Dart thread resubscribes to write events.
//
// We use the same procedure for ZX_SOCKET_READABLE, and read()/accept().

// define EVENTHANDLER_LOG_ERROR to get log messages only for errors.
// define EVENTHANDLER_LOG_INFO to get log messages for both information and
//   errors.
// #define EVENTHANDLER_LOG_INFO 1
#define EVENTHANDLER_LOG_ERROR 1
#if defined(EVENTHANDLER_LOG_INFO) || defined(EVENTHANDLER_LOG_ERROR)
#define LOG_ERR(msg, ...)                                                      \
  {                                                                            \
    int err = errno;                                                           \
    Syslog::PrintErr("Dart EventHandler ERROR: %s:%d: " msg, __FILE__,         \
                     __LINE__, ##__VA_ARGS__);                                 \
    errno = err;                                                               \
  }
#if defined(EVENTHANDLER_LOG_INFO)
#define LOG_INFO(msg, ...)                                                     \
  Syslog::Print("Dart EventHandler INFO: %s:%d: " msg, __FILE__, __LINE__,     \
                ##__VA_ARGS__)
#else
#define LOG_INFO(msg, ...)
#endif  // defined(EVENTHANDLER_LOG_INFO)
#else
#define LOG_ERR(msg, ...)
#define LOG_INFO(msg, ...)
#endif  // defined(EVENTHANDLER_LOG_INFO) || defined(EVENTHANDLER_LOG_ERROR)

namespace dart {
namespace bin {

intptr_t IOHandle::Read(void* buffer, intptr_t num_bytes) {
  MutexLocker ml(&mutex_);
  const ssize_t read_bytes = NO_RETRY_EXPECTED(read(fd_, buffer, num_bytes));
  const int err = errno;
  LOG_INFO("IOHandle::Read: fd = %ld. read %ld bytes\n", fd_, read_bytes);

  // Track the number of bytes available to read.
  if (read_bytes > 0) {
    available_bytes_ -=
        (available_bytes_ >= read_bytes) ? read_bytes : available_bytes_;
  }

  // If we have read all available bytes, or if there was an error, then
  // re-enable read events. We re-enable read events even if read() returns
  // an error. The error might be, e.g. EWOULDBLOCK, in which case
  // resubscription is necessary. Logic in the caller decides which errors
  // are real, and which are ignore-and-continue.
  if ((available_bytes_ == 0) || (read_bytes < 0)) {
    // Resubscribe to read events.
    read_events_enabled_ = true;
    if (wait_key_ == 0) {
      LOG_ERR("IOHandle::Read calling AsyncWaitLocked with wait_key_ == 0");
    }
    if (!AsyncWaitLocked(ZX_HANDLE_INVALID, POLLIN, wait_key_)) {
      LOG_ERR("IOHandle::AsyncWait failed for fd = %ld\n", fd_);
    }
  }

  errno = err;
  return read_bytes;
}

intptr_t IOHandle::Write(const void* buffer, intptr_t num_bytes) {
  MutexLocker ml(&mutex_);
  const ssize_t written_bytes =
      NO_RETRY_EXPECTED(write(fd_, buffer, num_bytes));
  const int err = errno;
  LOG_INFO("IOHandle::Write: fd = %ld. wrote %ld bytes\n", fd_, written_bytes);

  // Resubscribe to write events.
  write_events_enabled_ = true;
  if (wait_key_ == 0) {
    LOG_ERR("IOHandle::Write calling AsyncWaitLocked with wait_key_ == 0");
  }
  if (!AsyncWaitLocked(ZX_HANDLE_INVALID, POLLOUT, wait_key_)) {
    LOG_ERR("IOHandle::AsyncWait failed for fd = %ld\n", fd_);
  }

  errno = err;
  return written_bytes;
}

intptr_t IOHandle::Accept(RawAddr* addr) {
  MutexLocker ml(&mutex_);
  const intptr_t socket =
      NO_RETRY_EXPECTED(accept(fd_, &addr->addr, &addr->size));
  const int err = errno;
  LOG_INFO("IOHandle::Accept: fd = %ld. socket = %ld\n", fd_, socket);

  // Re-subscribe to read events.
  read_events_enabled_ = true;
  if (wait_key_ == 0) {
    LOG_ERR("IOHandle::Accept calling AsyncWaitLocked with wait_key_ == 0");
  }
  if (!AsyncWaitLocked(ZX_HANDLE_INVALID, POLLIN, wait_key_)) {
    LOG_ERR("IOHandle::AsyncWait failed for fd = %ld\n", fd_);
  }

  errno = err;
  return socket;
}

intptr_t IOHandle::AvailableBytes() {
  MutexLocker ml(&mutex_);
  ASSERT(fd_ >= 0);
  intptr_t available = FDUtils::AvailableBytes(fd_);
  LOG_INFO("IOHandle::AvailableBytes(): fd = %ld, bytes = %ld\n", fd_,
           available);
  if (available < 0) {
    // If there is an error, we set available to 1 to trigger a read event that
    // then propagates the error.
    available = 1;
  }
  available_bytes_ = available;
  return available;
}

void IOHandle::Close() {
  MutexLocker ml(&mutex_);
  VOID_NO_RETRY_EXPECTED(close(fd_));
}

uint32_t IOHandle::MaskToEpollEvents(intptr_t mask) {
  MutexLocker ml(&mutex_);
  // Do not ask for POLLERR and POLLHUP explicitly as they are
  // triggered anyway.
  uint32_t events = 0;
  // Do not subscribe to read closed events when kCloseEvent has already been
  // sent to the Dart thread.
  if (close_events_enabled_) {
    events |= POLLRDHUP;
  }
  if (read_events_enabled_ && ((mask & (1 << kInEvent)) != 0)) {
    events |= POLLIN;
  }
  if (write_events_enabled_ && ((mask & (1 << kOutEvent)) != 0)) {
    events |= POLLOUT;
  }
  return events;
}

intptr_t IOHandle::EpollEventsToMask(intptr_t events) {
  if ((events & POLLERR) != 0) {
    // Return error only if POLLIN is present.
    return ((events & POLLIN) != 0) ? (1 << kErrorEvent) : 0;
  }
  intptr_t event_mask = 0;
  if ((events & POLLIN) != 0) {
    event_mask |= (1 << kInEvent);
  }
  if ((events & POLLOUT) != 0) {
    event_mask |= (1 << kOutEvent);
  }
  if ((events & (POLLHUP | POLLRDHUP)) != 0) {
    event_mask |= (1 << kCloseEvent);
  }
  return event_mask;
}

bool IOHandle::AsyncWaitLocked(zx_handle_t port,
                               uint32_t events,
                               uint64_t key) {
  LOG_INFO("IOHandle::AsyncWaitLocked: fd = %ld\n", fd_);
  if (key == 0) {
    LOG_ERR("IOHandle::AsyncWaitLocked called with key == 0");
  }
  // The call to fdio_unsafe_fd_to_io() in the DescriptorInfo constructor may
  // have returned nullptr. If it did, propagate the problem up to Dart.
  if (fdio_ == nullptr) {
    LOG_ERR("fdio_unsafe_fd_to_io(%ld) returned nullptr\n", fd_);
    return false;
  }

  zx_handle_t handle;
  zx_signals_t signals;
  fdio_unsafe_wait_begin(fdio_, events, &handle, &signals);
  if (handle == ZX_HANDLE_INVALID) {
    LOG_ERR("fd = %ld fdio_unsafe_wait_begin returned an invalid handle\n",
            fd_);
    return false;
  }

  // Remember the port. Use the remembered port if the argument "port" is
  // ZX_HANDLE_INVALID.
  ASSERT((port != ZX_HANDLE_INVALID) || (port_ != ZX_HANDLE_INVALID));
  if ((port_ == ZX_HANDLE_INVALID) || (port != ZX_HANDLE_INVALID)) {
    port_ = port;
  }

  wait_key_ = key;
  LOG_INFO("zx_object_wait_async(fd = %ld, signals = %x)\n", fd_, signals);
  zx_status_t status =
      zx_object_wait_async(handle, port_, key, signals, ZX_WAIT_ASYNC_ONCE);
  if (status != ZX_OK) {
    LOG_ERR("zx_object_wait_async failed: %s\n", zx_status_get_string(status));
    return false;
  }

  return true;
}

bool IOHandle::AsyncWait(zx_handle_t port, uint32_t events, uint64_t key) {
  MutexLocker ml(&mutex_);
  return AsyncWaitLocked(port, events, key);
}

void IOHandle::CancelWait(zx_handle_t port, uint64_t key) {
  MutexLocker ml(&mutex_);
  LOG_INFO("IOHandle::CancelWait: fd = %ld\n", fd_);
  ASSERT(port != ZX_HANDLE_INVALID);
  if (key == 0) {
    LOG_ERR("IOHandle::CancelWait calling zx_port_cancel_key with key == 0");
  }
  zx_status_t status = zx_port_cancel_key(port, 0u, key);
  if ((status != ZX_OK) && (status != ZX_ERR_NOT_FOUND)) {
    LOG_ERR("zx_port_cancel_key failed: %s\n", zx_status_get_string(status));
  }
}

uint32_t IOHandle::WaitEnd(zx_signals_t observed) {
  MutexLocker ml(&mutex_);
  uint32_t events = 0;
  fdio_unsafe_wait_end(fdio_, observed, &events);
  LOG_INFO("IOHandle::WaitEnd: fd = %ld, events = %x\n", fd_, events);
  return events;
}

// This function controls the simulation of edge-triggering. It is responsible
// for removing events from the event mask when they should be suppressed, and
// for suppressing future events. Events are unsuppressed by their respective
// operations by the Dart thread on the socket---that is, where the
// *_events_enabled_ flags are set to true.
intptr_t IOHandle::ToggleEvents(intptr_t event_mask) {
  MutexLocker ml(&mutex_);
  // If write events are disabled, then remove the kOutEvent bit from the
  // event mask.
  if (!write_events_enabled_) {
    LOG_INFO(
        "IOHandle::ToggleEvents: fd = %ld "
        "de-asserting kOutEvent\n",
        fd_);
    event_mask = event_mask & ~(1 << kOutEvent);
  }
  // If the kOutEvent bit is set, then suppress future write events until the
  // Dart thread writes.
  if ((event_mask & (1 << kOutEvent)) != 0) {
    LOG_INFO(
        "IOHandle::ToggleEvents: fd = %ld "
        "asserting kOutEvent and disabling\n",
        fd_);
    write_events_enabled_ = false;
  }

  // If read events are disabled, then remove the kInEvent bit from the event
  // mask.
  if (!read_events_enabled_) {
    LOG_INFO(
        "IOHandle::ToggleEvents: fd = %ld "
        "de-asserting kInEvent\n",
        fd_);
    event_mask = event_mask & ~(1 << kInEvent);
  }
  // We may get In events without available bytes, so we must make sure there
  // are actually bytes, or we will never resubscribe (due to a short-circuit
  // on the Dart side).
  //
  // This happens due to how packets get enqueued on the port with all signals
  // asserted at that time. Sometimes we enqueue a packet due to
  // zx_object_wait_async e.g. for POLLOUT (writability) while the socket is
  // readable and while we have a Read queued up on the Dart side. This packet
  // will also have POLLIN (readable) asserted. We may then perform the Read
  // and drain the socket before our zx_port_wait is serviced, at which point
  // when we process the packet for POLLOUT with its stale POLLIN (readable)
  // signal, the socket is no longer actually readable.
  //
  // As a detail, negative available bytes (errors) are handled specially; see
  // IOHandle::AvailableBytes for more information.
  if ((event_mask & (1 << kInEvent)) != 0) {
    if (FDUtils::AvailableBytes(fd_) != 0) {
      LOG_INFO(
          "IOHandle::ToggleEvents: fd = %ld "
          "asserting kInEvent and disabling with bytes available\n",
          fd_);
      read_events_enabled_ = false;
    }
    // Also suppress future read events if we get a kCloseEvent. This is to
    // account for POLLIN being set by Fuchsia when the socket is read-closed.
    if ((event_mask & (1 << kCloseEvent)) != 0) {
      LOG_INFO(
          "IOHandle::ToggleEvents: fd = %ld "
          "asserting kInEvent and disabling due to a close event\n",
          fd_);
      read_events_enabled_ = false;
    }
  }

  // If the close events are disabled, then remove the kCloseEvent bit from the
  // event mask.
  if (!close_events_enabled_) {
    LOG_INFO(
        "IOHandle::ToggleEvents: fd = %ld "
        "de-asserting kCloseEvent\n",
        fd_);
    event_mask = event_mask & ~(1 << kCloseEvent);
  }
  // If the kCloseEvent bit is set, then suppress future close events, they will
  // be ignored by the Dart thread. See _NativeSocket.multiplex in
  // socket_patch.dart.
  if ((event_mask & (1 << kCloseEvent)) != 0) {
    LOG_INFO(
        "IOHandle::ToggleEvents: fd = %ld "
        "asserting kCloseEvent and disabling\n",
        fd_);
    close_events_enabled_ = false;
  }
  return event_mask;
}

void EventHandlerImplementation::AddToPort(zx_handle_t port_handle,
                                           DescriptorInfo* di) {
  const uint32_t events = di->io_handle()->MaskToEpollEvents(di->Mask());
  const uint64_t key = reinterpret_cast<uint64_t>(di);
  if (key == 0) {
    LOG_ERR(
        "EventHandlerImplementation::AddToPort calling AsyncWait with key == "
        "0");
  }
  if (!di->io_handle()->AsyncWait(port_handle, events, key)) {
    di->NotifyAllDartPorts(1 << kCloseEvent);
  }
}

void EventHandlerImplementation::RemoveFromPort(zx_handle_t port_handle,
                                                DescriptorInfo* di) {
  const uint64_t key = reinterpret_cast<uint64_t>(di);
  if (key == 0) {
    LOG_ERR(
        "EventHandlerImplementation::RemoveFromPort calling CancelWait with "
        "key == 0");
  }
  di->io_handle()->CancelWait(port_handle, key);
}

EventHandlerImplementation::EventHandlerImplementation()
    : socket_map_(&SimpleHashMap::SamePointerValue, 16) {
  shutdown_ = false;
  // Create the port.
  port_handle_ = ZX_HANDLE_INVALID;
  zx_status_t status = zx_port_create(0, &port_handle_);
  if (status != ZX_OK) {
    // This is a FATAL because the VM won't work at all if we can't create this
    // port.
    FATAL("zx_port_create failed: %s\n", zx_status_get_string(status));
  }
  ASSERT(port_handle_ != ZX_HANDLE_INVALID);
}

static void DeleteDescriptorInfo(void* info) {
  DescriptorInfo* di = reinterpret_cast<DescriptorInfo*>(info);
  LOG_INFO("Closed %ld\n", di->io_handle()->fd());
  di->Close();
  delete di;
}

EventHandlerImplementation::~EventHandlerImplementation() {
  socket_map_.Clear(DeleteDescriptorInfo);
  zx_handle_close(port_handle_);
  port_handle_ = ZX_HANDLE_INVALID;
}

void EventHandlerImplementation::UpdatePort(intptr_t old_mask,
                                            DescriptorInfo* di) {
  const intptr_t new_mask = di->Mask();
  if ((old_mask != 0) && (new_mask == 0)) {
    RemoveFromPort(port_handle_, di);
  } else if ((old_mask == 0) && (new_mask != 0)) {
    AddToPort(port_handle_, di);
  } else if ((old_mask != 0) && (new_mask != 0)) {
    ASSERT((old_mask == new_mask) || !di->IsListeningSocket());
    RemoveFromPort(port_handle_, di);
    AddToPort(port_handle_, di);
  }
}

DescriptorInfo* EventHandlerImplementation::GetDescriptorInfo(
    intptr_t fd,
    bool is_listening) {
  IOHandle* handle = reinterpret_cast<IOHandle*>(fd);
  ASSERT(handle->fd() >= 0);
  SimpleHashMap::Entry* entry =
      socket_map_.Lookup(GetHashmapKeyFromFd(handle->fd()),
                         GetHashmapHashFromFd(handle->fd()), true);
  ASSERT(entry != nullptr);
  DescriptorInfo* di = reinterpret_cast<DescriptorInfo*>(entry->value);
  if (di == nullptr) {
    // If there is no data in the hash map for this file descriptor a
    // new DescriptorInfo for the file descriptor is inserted.
    if (is_listening) {
      di = new DescriptorInfoMultiple(fd);
    } else {
      di = new DescriptorInfoSingle(fd);
    }
    entry->value = di;
  }
  ASSERT(fd == di->fd());
  return di;
}

void EventHandlerImplementation::WakeupHandler(intptr_t id,
                                               Dart_Port dart_port,
                                               int64_t data) {
  COMPILE_ASSERT(sizeof(InterruptMessage) <= sizeof(zx_packet_user_t));
  zx_port_packet_t pkt;
  InterruptMessage* msg = reinterpret_cast<InterruptMessage*>(&pkt.user);
  pkt.key = kInterruptPacketKey;
  msg->id = id;
  msg->dart_port = dart_port;
  msg->data = data;
  zx_status_t status = zx_port_queue(port_handle_, &pkt);
  if (status != ZX_OK) {
    // This is a FATAL because the VM won't work at all if we can't send any
    // messages to the EventHandler thread.
    FATAL("zx_port_queue failed: %s\n", zx_status_get_string(status));
  }
}

void EventHandlerImplementation::HandleInterrupt(InterruptMessage* msg) {
  if (msg->id == kTimerId) {
    LOG_INFO("HandleInterrupt read timer update\n");
    timeout_queue_.UpdateTimeout(msg->dart_port, msg->data);
    return;
  } else if (msg->id == kShutdownId) {
    LOG_INFO("HandleInterrupt read shutdown\n");
    shutdown_ = true;
    return;
  }
  ASSERT((msg->data & COMMAND_MASK) != 0);
  LOG_INFO("HandleInterrupt command:\n");
  Socket* socket = reinterpret_cast<Socket*>(msg->id);
  RefCntReleaseScope<Socket> rs(socket);
  if (socket->fd() == -1) {
    return;
  }
  IOHandle* io_handle = reinterpret_cast<IOHandle*>(socket->fd());
  const intptr_t fd = io_handle->fd();
  DescriptorInfo* di =
      GetDescriptorInfo(socket->fd(), IS_LISTENING_SOCKET(msg->data));
  ASSERT(io_handle == di->io_handle());
  if (IS_COMMAND(msg->data, kShutdownReadCommand)) {
    ASSERT(!di->IsListeningSocket());
    // Close the socket for reading.
    LOG_INFO("\tSHUT_RD: %ld\n", fd);
    VOID_NO_RETRY_EXPECTED(shutdown(fd, SHUT_RD));
  } else if (IS_COMMAND(msg->data, kShutdownWriteCommand)) {
    ASSERT(!di->IsListeningSocket());
    // Close the socket for writing.
    LOG_INFO("\tSHUT_WR: %ld\n", fd);
    VOID_NO_RETRY_EXPECTED(shutdown(fd, SHUT_WR));
  } else if (IS_COMMAND(msg->data, kCloseCommand)) {
    // Close the socket and free system resources and move on to next
    // message.
    const intptr_t old_mask = di->Mask();
    Dart_Port port = msg->dart_port;
    if (port != ILLEGAL_PORT) {
      di->RemovePort(port);
    }
    const intptr_t new_mask = di->Mask();
    UpdatePort(old_mask, di);

    LOG_INFO("\tCLOSE: %ld: %lx -> %lx\n", fd, old_mask, new_mask);
    if (di->IsListeningSocket()) {
      // We only close the socket file descriptor from the operating
      // system if there are no other dart socket objects which
      // are listening on the same (address, port) combination.
      ListeningSocketRegistry* registry = ListeningSocketRegistry::Instance();

      MutexLocker locker(registry->mutex());

      if (registry->CloseSafe(socket)) {
        ASSERT(new_mask == 0);
        socket_map_.Remove(GetHashmapKeyFromFd(fd), GetHashmapHashFromFd(fd));
        di->Close();
        delete di;
        socket->CloseFd();
      }
      socket->SetClosedFd();
    } else {
      ASSERT(new_mask == 0);
      socket_map_.Remove(GetHashmapKeyFromFd(fd), GetHashmapHashFromFd(fd));
      di->Close();
      delete di;
      socket->CloseFd();
    }
    if (port != 0) {
      const bool success = DartUtils::PostInt32(port, 1 << kDestroyedEvent);
      if (!success) {
        LOG_INFO("Failed to post destroy event to port %ld\n", port);
      }
    }
  } else if (IS_COMMAND(msg->data, kReturnTokenCommand)) {
    const int count = TOKEN_COUNT(msg->data);
    const intptr_t old_mask = di->Mask();
    LOG_INFO("\t Return Token: %ld: %lx\n", fd, old_mask);
    di->ReturnTokens(msg->dart_port, count);
    UpdatePort(old_mask, di);
  } else if (IS_COMMAND(msg->data, kSetEventMaskCommand)) {
    // `events` can only have kInEvent/kOutEvent flags set.
    const intptr_t events = msg->data & EVENT_MASK;
    ASSERT(0 == (events & ~(1 << kInEvent | 1 << kOutEvent)));

    const intptr_t old_mask = di->Mask();
    LOG_INFO("\t Set Event Mask: %ld: %lx %lx\n", fd, old_mask,
             msg->data & EVENT_MASK);
    di->SetPortAndMask(msg->dart_port, msg->data & EVENT_MASK);
    UpdatePort(old_mask, di);
  } else {
    UNREACHABLE();
  }
}

void EventHandlerImplementation::HandlePacket(zx_port_packet_t* pkt) {
  if (pkt->key == 0) {
    LOG_ERR("HandlePacket called with pkt->key==0");
    return;
  }
  LOG_INFO("HandlePacket: Got event packet: key=%lx\n", pkt->key);
  LOG_INFO("HandlePacket: Got event packet: type=%x\n", pkt->type);
  LOG_INFO("HandlePacket: Got event packet: status=%d\n", pkt->status);

  if (pkt->type == ZX_PKT_TYPE_USER) {
    ASSERT(pkt->key == kInterruptPacketKey);
    InterruptMessage* msg = reinterpret_cast<InterruptMessage*>(&pkt->user);
    HandleInterrupt(msg);
    return;
  }

  if (pkt->type != ZX_PKT_TYPE_SIGNAL_ONE) {
    LOG_ERR("HandlePacket: Got unexpected packet type: key=%x\n", pkt->type);
    return;
  }

  // Handle pkt->type == ZX_PKT_TYPE_SIGNAL_ONE
  LOG_INFO("HandlePacket: Got event packet: observed = %x\n",
           pkt->signal.observed);
  LOG_INFO("HandlePacket: Got event packet: count = %ld\n", pkt->signal.count);
  DescriptorInfo* di = reinterpret_cast<DescriptorInfo*>(pkt->key);
  zx_signals_t observed = pkt->signal.observed;
  const intptr_t old_mask = di->Mask();
  const uint32_t epoll_event = di->io_handle()->WaitEnd(observed);
  intptr_t event_mask = IOHandle::EpollEventsToMask(epoll_event);
  if ((event_mask & (1 << kErrorEvent)) != 0) {
    di->NotifyAllDartPorts(event_mask);
  } else if (event_mask != 0) {
    event_mask = di->io_handle()->ToggleEvents(event_mask);
    if (event_mask != 0) {
      Dart_Port port = di->NextNotifyDartPort(event_mask);
      ASSERT(port != 0);
      bool success = DartUtils::PostInt32(port, event_mask);
      if (!success) {
        // This can happen if e.g. the isolate that owns the port has died
        // for some reason.
        LOG_INFO("Failed to post event to port %ld\n", port);
      }
    }
  }
  UpdatePort(old_mask, di);
}

int64_t EventHandlerImplementation::GetTimeout() const {
  if (!timeout_queue_.HasTimeout()) {
    return kInfinityTimeout;
  }
  int64_t millis =
      timeout_queue_.CurrentTimeout() - TimerUtils::GetCurrentMonotonicMillis();
  return (millis < 0) ? 0 : millis;
}

void EventHandlerImplementation::HandleTimeout() {
  if (timeout_queue_.HasTimeout()) {
    int64_t millis = timeout_queue_.CurrentTimeout() -
                     TimerUtils::GetCurrentMonotonicMillis();
    if (millis <= 0) {
      DartUtils::PostNull(timeout_queue_.CurrentPort());
      timeout_queue_.RemoveCurrent();
    }
  }
}

void EventHandlerImplementation::Poll(uword args) {
  EventHandler* handler = reinterpret_cast<EventHandler*>(args);
  EventHandlerImplementation* handler_impl = &handler->delegate_;
  ASSERT(handler_impl != nullptr);

  zx_port_packet_t pkt;
  while (!handler_impl->shutdown_) {
    int64_t millis = handler_impl->GetTimeout();
    ASSERT((millis == kInfinityTimeout) || (millis >= 0));

    LOG_INFO("zx_port_wait(millis = %ld)\n", millis);
    zx_status_t status = zx_port_wait(handler_impl->port_handle_,
                                      millis == kInfinityTimeout
                                          ? ZX_TIME_INFINITE
                                          : zx_deadline_after(ZX_MSEC(millis)),
                                      &pkt);
    if (status == ZX_ERR_TIMED_OUT) {
      handler_impl->HandleTimeout();
    } else if (status != ZX_OK) {
      FATAL("zx_port_wait failed: %s\n", zx_status_get_string(status));
    } else {
      handler_impl->HandleTimeout();
      handler_impl->HandlePacket(&pkt);
    }
  }
  DEBUG_ASSERT(ReferenceCounted<Socket>::instances() == 0);
  handler->NotifyShutdownDone();
}

void EventHandlerImplementation::Start(EventHandler* handler) {
  Thread::Start("dart:io EventHandler", &EventHandlerImplementation::Poll,
                reinterpret_cast<uword>(handler));
}

void EventHandlerImplementation::Shutdown() {
  SendData(kShutdownId, 0, 0);
}

void EventHandlerImplementation::SendData(intptr_t id,
                                          Dart_Port dart_port,
                                          int64_t data) {
  WakeupHandler(id, dart_port, data);
}

void* EventHandlerImplementation::GetHashmapKeyFromFd(intptr_t fd) {
  // The hashmap does not support keys with value 0.
  return reinterpret_cast<void*>(fd + 1);
}

uint32_t EventHandlerImplementation::GetHashmapHashFromFd(intptr_t fd) {
  // The hashmap does not support keys with value 0.
  return dart::Utils::WordHash(fd + 1);
}

}  // namespace bin
}  // namespace dart

#endif  // defined(DART_HOST_OS_FUCHSIA)
