text: /*
    +----------------------------------------------------------------------+
    | Swoole                                                               |
    +----------------------------------------------------------------------+
    | This source file is subject to version 2.0 of the Apache license,    |
    | that is bundled with this package in the file LICENSE, and is        |
    | available through the world-wide-web at the following url:           |
    | http://www.apache.org/licenses/LICENSE-2.0.html                      |
    | If you did not receive a copy of the Apache2.0 license and are unable|
    | to obtain it through the world-wide-web, please send a note to       |
    | license@swoole.com so we can mail you a copy immediately.            |
    +----------------------------------------------------------------------+
    | Author: Tianfeng Han  <mikan.tenny@gmail.com>                        |
    |         Twosee  <twose@qq.com>                                       |
    +----------------------------------------------------------------------+
  */
  
  #pragma once
  
  #include "coroutine.h"
  #include "ssl.h"
  #include "socks5.h"
  
  #include <vector>
  #include <string>
  
  #define SW_DEFAULT_SOCKET_DNS_TIMEOUT       -1
  #define SW_DEFAULT_SOCKET_CONNECT_TIMEOUT    1
  #define SW_DEFAULT_SOCKET_READ_TIMEOUT      -1
  #define SW_DEFAULT_SOCKET_WRITE_TIMEOUT     -1
  
  namespace swoole
  {
  enum swTimeout_type
  {
      SW_TIMEOUT_DNS = 1 << 0,
      SW_TIMEOUT_CONNECT = 1 << 1,
      SW_TIMEOUT_READ = 1 << 2,
      SW_TIMEOUT_WRITE = 1 << 3,
      SW_TIMEOUT_RDWR = SW_TIMEOUT_READ | SW_TIMEOUT_WRITE,
      SW_TIMEOUT_ALL = SW_TIMEOUT_DNS | SW_TIMEOUT_CONNECT | SW_TIMEOUT_RDWR,
  };
  
  static constexpr enum swTimeout_type swTimeout_type_list[] =
  {
      SW_TIMEOUT_DNS, SW_TIMEOUT_CONNECT, SW_TIMEOUT_READ, SW_TIMEOUT_WRITE
  };
  }
  
  namespace swoole { namespace coroutine {
  //-------------------------------------------------------------------------------
  class Socket
  {
  public:
      static double default_dns_timeout;
      static double default_connect_timeout;
      static double default_read_timeout;
      static double default_write_timeout;
  
      swSocket *socket = nullptr;
      int errCode = 0;
      const char *errMsg = "";
      std::string errString;
  
      bool open_length_check = false;
      bool open_eof_check = false;
      bool http2 = false;
  
      swProtocol protocol = {};
      struct _swSocks5 *socks5_proxy = nullptr;
      struct _http_proxy* http_proxy = nullptr;
  
  #ifdef SW_USE_OPENSSL
      bool open_ssl = false;
      swSSL_option ssl_option = {};
  #endif
  
      Socket(int domain, int type, int protocol);
      Socket(int _fd, int _domain, int _type, int _protocol);
      Socket(enum swSocket_type type = SW_SOCK_TCP);
      Socket(int _fd, enum swSocket_type _type);
      Socket(swSocket *sock, Socket *socket);
      ~Socket();
      bool connect(std::string host, int port, int flags = 0);
      bool connect(const struct sockaddr *addr, socklen_t addrlen);
      bool shutdown(int how = SHUT_RDWR);
      bool cancel(const enum swEvent_type event);
      bool close();
  
      inline bool is_connect()
      {
          return activated && !closed;
      }
  
      bool check_liveness();
      ssize_t peek(void *__buf, size_t __n);
      ssize_t recv(void *__buf, size_t __n);
      ssize_t send(const void *__buf, size_t __n);
      ssize_t read(void *__buf, size_t __n);
      ssize_t write(const void *__buf, size_t __n);
      ssize_t recvmsg(struct msghdr *msg, int flags);
      ssize_t sendmsg(const struct msghdr *msg, int flags);
      ssize_t recv_all(void *__buf, size_t __n);
      ssize_t send_all(const void *__buf, size_t __n);
      ssize_t recv_packet(double timeout = 0);
      bool poll(enum swEvent_type type);
      Socket* accept(double timeout = 0);
      bool bind(std::string address, int port = 0);
      bool listen(int backlog = 0);
      bool sendfile(const char *filename, off_t offset, size_t length);
      ssize_t sendto(const std::string &host, int port, const void *__buf, size_t __n);
      ssize_t recvfrom(void *__buf, size_t __n);
      ssize_t recvfrom(void *__buf, size_t __n, struct sockaddr *_addr, socklen_t *_socklen);
  #ifdef SW_USE_OPENSSL
      bool ssl_check_context();
      bool ssl_handshake();
      bool ssl_verify(bool allow_self_signed);
      std::string ssl_get_peer_cert();
  #endif
  
      static inline enum swSocket_type convert_to_type(int domain, int type, int protocol = 0)
      {
          switch (domain)
          {
          case AF_INET:
              return type == SOCK_STREAM ? SW_SOCK_TCP : SW_SOCK_UDP;
          case AF_INET6:
              return type == SOCK_STREAM ? SW_SOCK_TCP6 : SW_SOCK_UDP6;
          case AF_UNIX:
              return type == SOCK_STREAM ? SW_SOCK_UNIX_STREAM : SW_SOCK_UNIX_DGRAM;
          default:
              return SW_SOCK_TCP;
          }
      }
  
      static inline enum swSocket_type convert_to_type(std::string &host)
      {
          if (host.compare(0, 6, "unix:/", 0, 6) == 0)
          {
              host = host.substr(sizeof("unix:") - 1);
              host.erase(0, host.find_first_not_of('/') - 1);
              return SW_SOCK_UNIX_STREAM;
          }
          else if (host.find(':') != std::string::npos)
          {
              return SW_SOCK_TCP6;
          }
          else
          {
              return SW_SOCK_TCP;
          }
      }
  
      static inline void init_reactor(swReactor *reactor)
      {
          swReactor_set_handler(reactor, SW_FD_CORO_SOCKET | SW_EVENT_READ, readable_event_callback);
          swReactor_set_handler(reactor, SW_FD_CORO_SOCKET | SW_EVENT_WRITE, writable_event_callback);
          swReactor_set_handler(reactor, SW_FD_CORO_SOCKET | SW_EVENT_ERROR, error_event_callback);
      }
  
      inline enum swSocket_type get_type()
      {
          return type;
      }
  
      inline int get_sock_domain()
      {
          return sock_domain;
      }
  
      inline int get_sock_type()
      {
          return sock_type;
      }
  
      inline int get_sock_protocol()
      {
          return sock_protocol;
      }
  
      inline int get_fd()
      {
          return sock_fd;
      }
  
      inline int get_bind_port()
      {
          return bind_port;
      }
  
      bool getsockname(swSocketAddress *sa);
      bool getpeername(swSocketAddress *sa);
      const char* get_ip();
      int get_port();
  
      inline bool has_bound(const enum swEvent_type event = SW_EVENT_RDWR)
      {
          return get_bound_co(event) != nullptr;
      }
  
      inline Coroutine* get_bound_co(const enum swEvent_type event)
      {
          if (event & SW_EVENT_READ)
          {
              if (read_co)
              {
                  return read_co;
              }
          }
          if (event & SW_EVENT_WRITE)
          {
              if (write_co)
              {
                  return write_co;
              }
          }
          return nullptr;
      }
  
      inline long get_bound_cid(const enum swEvent_type event = SW_EVENT_RDWR)
      {
          Coroutine *co = get_bound_co(event);
          return co ? co->get_cid() : 0;
      }
  
      inline void check_bound_co(const enum swEvent_type event)
      {
          long cid = get_bound_cid(event);
          if (sw_unlikely(cid))
          {
              swFatalError(
                  SW_ERROR_CO_HAS_BEEN_BOUND,
                  "Socket#%d has already been bound to another coroutine#%ld, "
                  "%s of the same socket in coroutine#%ld at the same time is not allowed",
                  sock_fd, cid,
                  (event == SW_EVENT_READ ? "reading" : (event == SW_EVENT_WRITE ? "writing" :
                          (read_co && write_co ? "reading or writing" : (read_co ? "reading" : "writing")))),
                  Coroutine::get_current_cid()
              );
          }
      }
  
      inline void set_err(int e)
      {
          errCode = errno = e;
          errMsg = e ? swoole_strerror(e) : "";
      }
  
      inline void set_err(int e, const char *s)
      {
          errCode = errno = e;
          errMsg = s;
      }
  
  
      inline void set_err(int e, std::string s)
      {
          errCode = errno = e;
          errString = s;
          errMsg = errString.c_str();
      }
  
      /* set connect read write timeout */
      inline void set_timeout(double timeout, int type = SW_TIMEOUT_ALL)
      {
          if (timeout == 0)
          {
              return;
          }
          if (type & SW_TIMEOUT_DNS)
          {
              dns_timeout = timeout;
          }
          if (type & SW_TIMEOUT_CONNECT)
          {
              connect_timeout = timeout;
          }
          if (type & SW_TIMEOUT_READ)
          {
              read_timeout = timeout;
          }
          if (type & SW_TIMEOUT_WRITE)
          {
              write_timeout = timeout;
          }
      }
  
      inline void set_timeout(struct timeval *timeout, int type = SW_TIMEOUT_ALL)
      {
          set_timeout((double) timeout->tv_sec + ((double) timeout->tv_usec / 1000 / 1000), type);
      }
  
      inline double get_timeout(enum swTimeout_type type = SW_TIMEOUT_ALL)
      {
          SW_ASSERT_1BYTE(type);
          if (type == SW_TIMEOUT_DNS)
          {
              return dns_timeout;
          }
          else if (type == SW_TIMEOUT_CONNECT)
          {
              return connect_timeout;
          }
          else if (type == SW_TIMEOUT_READ)
          {
              return read_timeout;
          }
          else // if (type == SW_TIMEOUT_WRITE)
          {
              return write_timeout;
          }
      }
  
      inline bool set_option(int level, int optname, int optval)
      {
          if (setsockopt(sock_fd, level, optname, &optval, sizeof(optval)) != 0)
          {
              swSysWarn("setsockopt(%d, %d, %d, %d) failed", sock_fd, level, optname, optval);
              return false;
          }
          return true;
      }
  
      inline swString* get_read_buffer()
      {
          if (sw_unlikely(!read_buffer))
          {
              read_buffer = swString_new(SW_BUFFER_SIZE_BIG);
          }
          return read_buffer;
      }
  
      inline swString* get_write_buffer()
      {
          if (sw_unlikely(!write_buffer))
          {
              write_buffer = swString_new(SW_BUFFER_SIZE_BIG);
          }
          return write_buffer;
      }
  
      inline void set_zero_copy(bool enable)
      {
          zero_copy = enable;
      }
  
  #ifdef SW_USE_OPENSSL
      inline bool is_ssl_enable()
      {
          return socket && ssl_handshaked;
      }
  
      bool ssl_shutdown();
  #endif
  
  private:
      enum swSocket_type type;
      int sock_domain = 0;
      int sock_type = 0;
      int sock_protocol = 0;
      int sock_fd = -1;
  
      Coroutine *read_co = nullptr;
      Coroutine *write_co = nullptr;
  #ifdef SW_USE_OPENSSL
      enum swEvent_type want_event = SW_EVENT_NULL;
  #endif
  
      std::string connect_host;
      int connect_port = 0;
  
      std::string bind_address;
      int bind_port = 0;
      int backlog = 0;
  
      double dns_timeout = default_dns_timeout;
      double connect_timeout = default_connect_timeout;
      double read_timeout = default_read_timeout;
      double write_timeout = default_write_timeout;
      swTimer_node *read_timer = nullptr;
      swTimer_node *write_timer = nullptr;
  
      swString *read_buffer = nullptr;
      swString *write_buffer = nullptr;
      swSocketAddress bind_address_info = {};
  
  #ifdef SW_USE_OPENSSL
      bool ssl_is_server = false;
      bool ssl_handshaked = false;
      SSL_CTX *ssl_context = nullptr;
      std::string ssl_host_name;
      bool ssl_create(SSL_CTX *ssl_context);
  #endif
  
      bool activated = true;
      bool shutdown_read = false;
      bool shutdown_write = false;
      bool closed = false;
  
      bool zero_copy = false;
  
      static void timer_callback(swTimer *timer, swTimer_node *tnode);
      static int readable_event_callback(swReactor *reactor, swEvent *event);
      static int writable_event_callback(swReactor *reactor, swEvent *event);
      static int error_event_callback(swReactor *reactor, swEvent *event);
  
      inline void init_sock_type(enum swSocket_type _type);
      inline bool init_sock();
      bool init_reactor_socket(int fd);
  
      inline void init_options()
      {
          if (type == SW_SOCK_TCP || type == SW_SOCK_TCP6)
          {
              set_option(IPPROTO_TCP, TCP_NODELAY, 1);
          }
          protocol.package_length_type = 'N';
          protocol.package_length_size = 4;
          protocol.package_length_offset = 0;
          protocol.package_body_offset = 0;
          protocol.package_max_length = SW_INPUT_BUFFER_SIZE;
      }
  
      bool add_event(const enum swEvent_type event);
      bool wait_event(const enum swEvent_type event, const void **__buf = nullptr, size_t __n = 0);
  
      inline bool is_available(const enum swEvent_type event)
      {
          if (event != SW_EVENT_NULL)
          {
              check_bound_co(event);
          }
          if (sw_unlikely(closed))
          {
              set_err(ECONNRESET);
              return false;
          }
          return true;
      }
  
      // TODO: move to client.cc
      bool socks5_handshake();
      bool http_proxy_handshake();
  
      class timer_controller
      {
      public:
          timer_controller(swTimer_node **timer_pp, double timeout, Socket *sock, swTimerCallback callback) :
              timer_pp(timer_pp), timeout(timeout), socket_(sock), callback(callback)
          {
          }
          bool start()
          {
              if (timeout != 0 && !*timer_pp)
              {
                  enabled = true;
                  if (timeout > 0)
                  {
                      *timer_pp = swoole_timer_add((long) (timeout * 1000), SW_FALSE, callback, socket_);
                      return *timer_pp != nullptr;
                  }
                  else // if (timeout < 0)
                  {
                      *timer_pp = (swTimer_node *) -1;
                  }
              }
              return true;
          }
          ~timer_controller()
          {
              if (enabled && *timer_pp)
              {
                  if (*timer_pp != (swTimer_node *) -1)
                  {
                      swoole_timer_del(*timer_pp);
                  }
                  *timer_pp = nullptr;
              }
          }
      private:
          bool enabled = false;
          swTimer_node** timer_pp;
          double timeout;
          Socket *socket_;
          swTimerCallback callback;
      };
  
  public:
      class timeout_setter
      {
      public:
          timeout_setter(Socket *socket, double timeout, const enum swTimeout_type type) :
              socket_(socket), timeout(timeout), type(type)
          {
              if (timeout == 0)
              {
                  return;
              }
              for (uint8_t i = 0; i < SW_ARRAY_SIZE(swTimeout_type_list); i++)
              {
                  if (type & swTimeout_type_list[i])
                  {
                      original_timeout[i] = socket->get_timeout(swTimeout_type_list[i]);
                      if (timeout != original_timeout[i])
                      {
                          socket->set_timeout(timeout, swTimeout_type_list[i]);
                      }
                  }
              }
          }
          ~timeout_setter()
          {
              if (timeout == 0)
              {
                  return;
              }
              for (uint8_t i = 0; i < SW_ARRAY_SIZE(swTimeout_type_list); i++)
              {
                  if (type & swTimeout_type_list[i])
                  {
                      if (timeout != original_timeout[i])
                      {
                          socket_->set_timeout(original_timeout[i], swTimeout_type_list[i]);
                      }
                  }
              }
          }
      protected:
          Socket *socket_;
          double timeout;
          enum swTimeout_type type;
          double original_timeout[sizeof(swTimeout_type_list)] = {};
      };
  
      class timeout_controller: public timeout_setter
      {
      public:
          timeout_controller(Socket *socket, double timeout, const enum swTimeout_type type) :
                  timeout_setter(socket, timeout, type)
          {
          }
          inline bool has_timedout(const enum swTimeout_type type)
          {
              SW_ASSERT_1BYTE(type);
              if (timeout > 0)
              {
                  if (sw_unlikely(startup_time == 0))
                  {
                      startup_time = swoole_microtime();
                  }
                  else
                  {
                      double used_time = swoole_microtime() - startup_time;
                      if (sw_unlikely(timeout - used_time < SW_TIMER_MIN_SEC))
                      {
                          socket_->set_err(ETIMEDOUT);
                          return true;
                      }
                      socket_->set_timeout(timeout - used_time, type);
                  }
              }
              return false;
          }
      protected:
          double startup_time = 0;
      };
  };
  std::vector<std::string> dns_lookup(const char *domain, double timeout = 2.0);
  //-------------------------------------------------------------------------------
  }}
  