// Copyright (c) 2013, 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_WINDOWS)

#include "bin/builtin.h"
#include "bin/eventhandler.h"
#include "bin/file.h"
#include "bin/lockers.h"
#include "bin/socket.h"
#include "bin/socket_base_win.h"
#include "bin/thread.h"
#include "bin/utils.h"
#include "bin/utils_win.h"
#include "platform/syslog.h"

namespace dart {
namespace bin {

Socket::Socket(intptr_t fd)
    : ReferenceCounted(),
      fd_(fd),
      isolate_port_(Dart_GetMainPortId()),
      port_(ILLEGAL_PORT),
      udp_receive_buffer_(nullptr) {
  ASSERT(fd_ != kClosedFd);
  Handle* handle = reinterpret_cast<Handle*>(fd_);
  ASSERT(handle != nullptr);
}

void Socket::CloseFd() {
  ASSERT(fd_ != kClosedFd);
  Handle* handle = reinterpret_cast<Handle*>(fd_);
  ASSERT(handle != nullptr);
  handle->Release();
  SetClosedFd();
}

void Socket::SetClosedFd() {
  fd_ = kClosedFd;
}

static intptr_t Create(const RawAddr& addr) {
  SOCKET s = socket(addr.ss.ss_family, SOCK_STREAM, 0);
  if (s == INVALID_SOCKET) {
    return -1;
  }

  linger l;
  l.l_onoff = 1;
  l.l_linger = 10;
  int status = setsockopt(s, SOL_SOCKET, SO_LINGER, reinterpret_cast<char*>(&l),
                          sizeof(l));
  if (status != NO_ERROR) {
    FATAL("Failed setting SO_LINGER on socket");
  }

  ClientSocket* client_socket = new ClientSocket(s);
  return reinterpret_cast<intptr_t>(client_socket);
}

static intptr_t Connect(intptr_t fd,
                        const RawAddr& addr,
                        const RawAddr& bind_addr) {
  ASSERT(reinterpret_cast<Handle*>(fd)->is_client_socket());
  ClientSocket* handle = reinterpret_cast<ClientSocket*>(fd);
  SOCKET s = handle->socket();

  // Note: unlike |connect| |ConnectEx| requires *bound* socket, so we must
  // manually bind() it to some local address before we can issue a connect
  // request to the remote address.
  int status = bind(s, &bind_addr.addr, bind_addr.size);
  if (status != NO_ERROR) {
    const int rc = WSAGetLastError();
    handle->mark_closed();  // Destructor asserts that socket is marked closed.
    handle->Release();
    closesocket(s);
    SetLastError(rc);
    return -1;
  }

  auto buffer = OverlappedBuffer::AllocateConnectBuffer(handle);
  status = EventHandler::delegate()->connect_ex()(s, &addr.addr, addr.size,
                                                  nullptr, 0, nullptr,
                                                  buffer->GetCleanOverlapped());
  if (status == TRUE) {
    handle->ConnectComplete();
    return fd;
  } else if (WSAGetLastError() == ERROR_IO_PENDING) {
    buffer.release();  // Ownership passed to event handler.
    return fd;
  }

  const int rc = WSAGetLastError();
  // Cleanup in case of error.
  handle->Close();
  handle->Release();
  SetLastError(rc);
  return -1;
}

intptr_t Socket::CreateConnect(const RawAddr& addr) {
  intptr_t fd = Create(addr);
  if (fd < 0) {
    return fd;
  }

  RawAddr bind_addr;
  memset(&bind_addr, 0, sizeof(bind_addr));
  bind_addr.ss.ss_family = addr.ss.ss_family;
  if (addr.ss.ss_family == AF_INET) {
    bind_addr.in.sin_addr.s_addr = INADDR_ANY;
    bind_addr.size = sizeof(sockaddr_in);
  } else if (addr.ss.ss_family == AF_INET6) {
    bind_addr.in6.sin6_addr = in6addr_any;
    bind_addr.size = sizeof(sockaddr_in6);
  } else if (addr.ss.ss_family == AF_UNIX) {
    // Local address is an anonymous unix socket.
    bind_addr.size = sizeof(ADDRESS_FAMILY);
  } else {
    SetLastError(ERROR_NOT_SUPPORTED);
    return -1;
  }

  return Connect(fd, addr, bind_addr);
}

intptr_t Socket::CreateUnixDomainConnect(const RawAddr& addr) {
  return CreateConnect(addr);
}

intptr_t Socket::CreateBindConnect(const RawAddr& addr,
                                   const RawAddr& source_addr) {
  intptr_t fd = Create(addr);
  if (fd < 0) {
    return fd;
  }

  return Connect(fd, addr, source_addr);
}

intptr_t Socket::CreateUnixDomainBindConnect(const RawAddr& addr,
                                             const RawAddr& source_addr) {
  return CreateBindConnect(addr, source_addr);
}

intptr_t ServerSocket::Accept(intptr_t fd) {
  ListenSocket* listen_socket = reinterpret_cast<ListenSocket*>(fd);
  ClientSocket* client_socket = listen_socket->Accept();
  if (client_socket != nullptr) {
    return reinterpret_cast<intptr_t>(client_socket);
  } else {
    return -1;
  }
}

intptr_t Socket::CreateBindDatagram(const RawAddr& addr,
                                    bool reuseAddress,
                                    bool reusePort,
                                    int ttl) {
  SOCKET s = socket(addr.ss.ss_family, SOCK_DGRAM, IPPROTO_UDP);
  if (s == INVALID_SOCKET) {
    return -1;
  }

  // Ensure the socket doesn't get closed if used to send data to unreachable.
  BOOL value = FALSE;
  DWORD bytes;
  WSAIoctl(s, SIO_UDP_CONNRESET, &value, sizeof value, NULL, 0, &bytes, nullptr,
           nullptr);

  int status;
  if (reuseAddress) {
    BOOL optval = true;
    status = setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
                        reinterpret_cast<const char*>(&optval), sizeof(optval));
    if (status == SOCKET_ERROR) {
      DWORD rc = WSAGetLastError();
      closesocket(s);
      SetLastError(rc);
      return -1;
    }
  }

  if (reusePort) {
    // ignore reusePort - not supported on this platform.
    Syslog::PrintErr(
        "Dart Socket ERROR: %s:%d: `reusePort` not supported for "
        "Windows.",
        __FILE__, __LINE__);
  }

  // Can't use SocketBase::SetMulticastHops here - we'd need to create
  // the DatagramSocket object and reinterpret_cast it here, just for that
  // method to reinterpret_cast it again.
  int ttlValue = ttl;
  int ttlLevel = addr.addr.sa_family == AF_INET ? IPPROTO_IP : IPPROTO_IPV6;
  int ttlOptname =
      addr.addr.sa_family == AF_INET ? IP_MULTICAST_TTL : IPV6_MULTICAST_HOPS;
  if (setsockopt(s, ttlLevel, ttlOptname, reinterpret_cast<char*>(&ttlValue),
                 sizeof(ttlValue)) != 0) {
    DWORD rc = WSAGetLastError();
    closesocket(s);
    SetLastError(rc);
    return -1;
  }

  status = bind(s, &addr.addr, addr.size);
  if (status == SOCKET_ERROR) {
    DWORD rc = WSAGetLastError();
    closesocket(s);
    SetLastError(rc);
    return -1;
  }

  DatagramSocket* datagram_socket = new DatagramSocket(s);
  return reinterpret_cast<intptr_t>(datagram_socket);
}

intptr_t ServerSocket::CreateBindListen(const RawAddr& addr,
                                        intptr_t backlog,
                                        bool v6_only) {
  SOCKET s = socket(addr.ss.ss_family, SOCK_STREAM,
                    addr.ss.ss_family == AF_UNIX ? 0 : IPPROTO_TCP);
  if (s == INVALID_SOCKET) {
    return -1;
  }

  int status;
  if (addr.ss.ss_family != AF_UNIX) {
    BOOL optval = true;
    status = setsockopt(s, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
                        reinterpret_cast<const char*>(&optval), sizeof(optval));
    if (status == SOCKET_ERROR) {
      DWORD rc = WSAGetLastError();
      closesocket(s);
      SetLastError(rc);
      return -1;
    }
  }

  if (addr.ss.ss_family == AF_INET6) {
    BOOL optval = v6_only;
    setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY,
               reinterpret_cast<const char*>(&optval), sizeof(optval));
  }

  status = bind(s, &addr.addr, addr.size);
  if (status == SOCKET_ERROR) {
    DWORD rc = WSAGetLastError();
    closesocket(s);
    SetLastError(rc);
    return -1;
  }

  ListenSocket* listen_socket = new ListenSocket(s, addr.ss.ss_family);

  if (addr.ss.ss_family != AF_UNIX) {
    // Test for invalid socket port 65535 (some browsers disallow it).
    if ((SocketAddress::GetAddrPort(addr) == 0) &&
        (SocketBase::GetPort(reinterpret_cast<intptr_t>(listen_socket)) ==
         65535)) {
      // Don't close fd until we have created new. By doing that we ensure
      // another port.
      intptr_t new_s = CreateBindListen(addr, backlog, v6_only);
      DWORD rc = WSAGetLastError();
      closesocket(s);
      listen_socket->Release();
      SetLastError(rc);
      return new_s;
    }
  }

  status = listen(s, backlog > 0 ? backlog : SOMAXCONN);
  if (status == SOCKET_ERROR) {
    DWORD rc = WSAGetLastError();
    closesocket(s);
    listen_socket->Release();
    SetLastError(rc);
    return -1;
  }

  return reinterpret_cast<intptr_t>(listen_socket);
}

intptr_t ServerSocket::CreateUnixDomainBindListen(const RawAddr& addr,
                                                  intptr_t backlog) {
  return CreateBindListen(addr, backlog);
}

bool ServerSocket::StartAccept(intptr_t fd) {
  auto socket = reinterpret_cast<ListenSocket*>(fd);
  if (!socket->StartAccept()) {
    DWORD error = socket->last_error();
    socket->Close();
    // Release the reference to socket - it is not going to be attached
    // to a |Socket| object because |StartAccept| returns |false|.
    socket->Release();
    SetLastError(error);
    return false;
  }
  return true;
}

}  // namespace bin
}  // namespace dart

#endif  // defined(DART_HOST_OS_WINDOWS)
