// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_DEBUG_WASM_GDB_SERVER_TRANSPORT_H_
#define V8_DEBUG_WASM_GDB_SERVER_TRANSPORT_H_

#include <sstream>
#include <vector>
#include "src/base/macros.h"
#include "src/debug/wasm/gdb-server/gdb-remote-util.h"

#if _WIN32
#include <windows.h>
#include <winsock2.h>

typedef SOCKET SocketHandle;

#define CloseSocket closesocket
#define InvalidSocket INVALID_SOCKET
#define SocketGetLastError() WSAGetLastError()
static const int kErrInterrupt = WSAEINTR;
typedef int ssize_t;
typedef int socklen_t;

#else  // _WIN32

#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/tcp.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string>

typedef int SocketHandle;

#define CloseSocket close
#define InvalidSocket (-1)
#define SocketGetLastError() errno
static const int kErrInterrupt = EINTR;

#endif  // _WIN32

namespace v8 {
namespace internal {
namespace wasm {
namespace gdb_server {

class SocketTransport;

// Acts as a factory for Transport objects bound to a specified TCP port.
class SocketBinding {
 public:
  // Wrap existing socket handle.
  explicit SocketBinding(SocketHandle socket_handle);

  // Bind to the specified TCP port.
  static SocketBinding Bind(uint16_t tcp_port);

  bool IsValid() const { return socket_handle_ != InvalidSocket; }

  // Create a transport object from this socket binding
  std::unique_ptr<SocketTransport> CreateTransport();

  // Get port the socket is bound to.
  uint16_t GetBoundPort();

 private:
  SocketHandle socket_handle_;
};

class V8_EXPORT_PRIVATE TransportBase {
 public:
  virtual ~TransportBase() {}

  // Waits for an incoming connection on the bound port.
  virtual bool AcceptConnection() = 0;

  // Read {len} bytes from this transport, possibly blocking until enough data
  // is available.
  // {dst} must point to a buffer large enough to contain {len} bytes.
  // Returns true on success.
  // Returns false if the connection is closed; in that case the {dst} may have
  // been partially overwritten.
  virtual bool Read(char* dst, int32_t len) = 0;

  // Write {len} bytes to this transport.
  // Return true on success, false if the connection is closed.
  virtual bool Write(const char* src, int32_t len) = 0;

  // Return true if there is data to read.
  virtual bool IsDataAvailable() const = 0;

  // If we are connected to a debugger, gracefully closes the connection.
  // This should be called when a debugging session gets closed.
  virtual void Disconnect() = 0;

  // Shuts down this transport, gracefully closing the existing connection and
  // also closing the listening socket. This should be called when the GDB stub
  // shuts down, when the program terminates.
  virtual void Close() = 0;

  // Blocks waiting for one of these two events to occur:
  // - A network event (a new packet arrives, or the connection is dropped),
  // - A thread event is signaled (the execution stopped because of a trap or
  // breakpoint).
  virtual void WaitForDebugStubEvent() = 0;

  // Signal that this transport should leave an alertable wait state because
  // the execution of the debuggee was stopped because of a trap or breakpoint.
  virtual bool SignalThreadEvent() = 0;
};

class Transport : public TransportBase {
 public:
  explicit Transport(SocketHandle s);
  ~Transport() override;

  // TransportBase
  bool Read(char* dst, int32_t len) override;
  bool Write(const char* src, int32_t len) override;
  bool IsDataAvailable() const override;
  void Disconnect() override;
  void Close() override;

  static const int kBufSize = 4096;

 protected:
  // Copy buffered data to *dst up to len bytes and update dst and len.
  void CopyFromBuffer(char** dst, int32_t* len);

  // Read available data from the socket. Return false on EOF or error.
  virtual bool ReadSomeData() = 0;

  std::unique_ptr<char[]> buf_;
  int32_t pos_;
  int32_t size_;
  SocketHandle handle_bind_;
  SocketHandle handle_accept_;
};

#if _WIN32

class SocketTransport : public Transport {
 public:
  explicit SocketTransport(SocketHandle s);
  ~SocketTransport() override;
  SocketTransport(const SocketTransport&) = delete;
  SocketTransport& operator=(const SocketTransport&) = delete;

  // TransportBase
  bool AcceptConnection() override;
  void Disconnect() override;
  void WaitForDebugStubEvent() override;
  bool SignalThreadEvent() override;

 private:
  bool ReadSomeData() override;

  HANDLE socket_event_;
  HANDLE faulted_thread_event_;
};

#else  // _WIN32

class SocketTransport : public Transport {
 public:
  explicit SocketTransport(SocketHandle s);
  ~SocketTransport() override;
  SocketTransport(const SocketTransport&) = delete;
  SocketTransport& operator=(const SocketTransport&) = delete;

  // TransportBase
  bool AcceptConnection() override;
  void WaitForDebugStubEvent() override;
  bool SignalThreadEvent() override;

 private:
  bool ReadSomeData() override;

  int faulted_thread_fd_read_;
  int faulted_thread_fd_write_;
};

#endif  // _WIN32

}  // namespace gdb_server
}  // namespace wasm
}  // namespace internal
}  // namespace v8

#endif  // V8_DEBUG_WASM_GDB_SERVER_TRANSPORT_H_
