/**
    socket communication with encryption

    Copyright (c) 2020-2022 The Creators of Simphone

    See the file COPYING.LESSER.txt for copying permission.
**/

#include "config.h"
#include "spth.h"

#include "logger.h"
#include "table.h"
#include "error.h"
#include "utils.h"
#include "system.h"
#include "crypto.h"
#include "sssl.h"
#include "socket.h"
#include "network.h"
#include "contact.h"
#include "param.h"
#include "proto.h"
#include "limit.h"
#include "proxy.h"
#include "client.h"
#include "api.h"

#include <string.h>
#include <stdlib.h>
#include <errno.h>

#define SIM_MODULE "socket"

int socket_max_limit[8];
struct _socket socket_udp_sock;
simtype socket_table;

struct socket_context {
  struct _socket_crypt *crypto; /* socket crypto context */
  unsigned length;              /* currently filled up bytes */
  unsigned size;                /* total bytes in buffer */
  simbyte *buffer;              /* not in stack to save memory (allocate only as much as needed) */
};

static int socket_buffer_error_counts[2] = { 0, 0 };
static simbyte socket_decrypt_buffer[SIM_MAX_PACKET_SIZE];

static simtype proxy_proto_client;
static simsocket socket_nat_sock;
static int socket_nat_count = 0;

static int socket_init_limit (unsigned max) {
  const unsigned factor = SOCKET_MAX_FACTOR;
  unsigned maxclients = factor * param_get_number ("limit.clients"), maxprobes = param_get_number ("limit.probes");
  unsigned maxfds = param_get_number_min ("limit.maxfds", param_get_default_number ("limit.maxfds", 4));
  memset (socket_buffer_error_counts, 0, sizeof (socket_buffer_error_counts));
  socket_nat_sock = NULL;
  maxclients = ! maxclients || maxclients > max ? max : maxclients > 4 * factor ? maxclients : 4 * factor;
  socket_max_limit[SOCKET_MAX_NEW] = param_get_number_max ("limit.newsockets", (maxclients / factor) >> 1);
  socket_max_limit[SOCKET_MAX_FDS] = maxclients - (maxfds > maxclients - 2 ? maxclients - 2 : maxfds);
  socket_max_limit[SOCKET_MAX_CLIENTS] = (maxclients - socket_max_limit[SOCKET_MAX_NEW]) / factor;
  socket_max_limit[SOCKET_MAX_PROBES] = maxprobes && maxprobes < max ? maxprobes : max;
#ifndef _WIN32
  if (socket_max_limit[SOCKET_MAX_FDS] > socket_max_limit[SOCKET_MAX_CLIENTS])
    socket_max_limit[SOCKET_MAX_FDS] = socket_max_limit[SOCKET_MAX_CLIENTS];
  if (maxfds > max)
    param_set_number ("limit.maxfds", maxfds = max, SIM_PARAM_TEMPORARY);
  maxclients += maxfds + socket_max_limit[SOCKET_MAX_FDS];
#endif
  socket_max_limit[SOCKET_MAX_PROXY] = (max << 2) - maxclients - socket_max_limit[SOCKET_MAX_PROBES];
  socket_max_limit[SOCKET_MAX_MTU] = param_get_number ("socket.mtu");
  socket_max_limit[SOCKET_MAX_WAIT] = param_get_number ("socket.wait") * 1000;
  if (max < 4 || socket_max_limit[SOCKET_MAX_CLIENTS] < 2 || socket_max_limit[SOCKET_MAX_NEW] <= 0)
    return EMFILE;
  socket_table = table_new (max > 1027 ? 1027 : max);
  proxy_proto_client = table_new_long_const (3, SIM_NEW_STRING (SIM_CLIENT_CMD),
                                             SIM_NEW_NUMBER (SIM_CLIENT_CMD_END_ERROR),
                                             SIM_NEW_NUMBER (SIM_CLIENT_CMD_PING_PONG), NULL);
  socket_udp_sock.fd = INVALID_SOCKET;
  return SIM_OK;
}

#ifdef _WIN32
#undef EINPROGRESS
#define EINPROGRESS WSAEWOULDBLOCK
#undef EWOULDBLOCK
#define EWOULDBLOCK WSAEWOULDBLOCK
#undef EAGAIN
#define EAGAIN WSAEWOULDBLOCK
#undef EINTR
#define EINTR WSAEINTR /* not needed */
#undef ECONNRESET
#define ECONNRESET WSAECONNRESET
#undef ECONNABORTED
#define ECONNABORTED WSAECONNRESET
#undef ECONNREFUSED
#define ECONNREFUSED WSAECONNREFUSED

int socket_init (void) {
  WSADATA data;
  int ret = WSAStartup (2, &data);
  if (! ret) {
    unsigned max = param_get_number_min ("limit.maxsockets", 256);
    if (! max)
      max = param_get_max ("limit.maxsockets", 10240); /* nPth MAX_THREADS / 2 */
    LOG_DEBUG_ ("init max = %u\n", max);
    ret = socket_init_limit (max >> 2);
  }
  return ret;
}

#else

#include <sys/resource.h>

#include <fcntl.h>
#include <unistd.h>
#include <signal.h>

#include <netinet/in.h>
#include <arpa/inet.h>

int socket_init (void) {
  rlim_t max;
  struct rlimit limit;
  if (signal (SIGPIPE, SIG_IGN) == SIG_ERR)
    return errno ? errno : EINVAL;
  if (getrlimit (RLIMIT_NOFILE, &limit) < 0)
    return errno ? errno : ESRCH;
  if ((max = param_get_number_min ("limit.maxsockets", 256)) != 0) {
    if (max > limit.rlim_max)
      max = limit.rlim_max;
  } else if ((max = limit.rlim_max) <= 0)
    max = 1;
#if HAVE_LIBPTH
  /* select can cause blocking calls to valid sockets to fail with EBADF.
     setting the file descriptor limit to FD_SETSIZE prevents this */
  if (max > FD_SETSIZE)
    max = FD_SETSIZE;
#else
  /* Mac OS reports a dummy max limit. replace it with the known one */
  if (max != (unsigned) max)
    max = param_get_max ("limit.maxsockets", 10240);
#endif
  limit.rlim_cur = limit.rlim_max = max;
  if (setrlimit (RLIMIT_NOFILE, &limit) < 0) {
    LOG_ERROR_ ("init max = %u (error %d)\n", (unsigned) max, errno);
    return errno ? errno : EPERM;
  }
  LOG_DEBUG_ ("init max = %d\n", (int) max);
  return socket_init_limit ((socket_max_limit[SOCKET_MAX_SOCKETS] = (int) max) >> 2);
}
#endif /* _WIN32 */

void socket_uninit (void) {
  if (table_count (socket_table))
    LOG_ERROR_SIMTYPE_ (socket_table, 0, "sockets ");
  table_free (proxy_proto_client), proxy_proto_client = number_new (0);
  table_free (socket_table), socket_table = nil ();
}

simsocket socket_new (simsocket sock) {
  memset (sock, 0, sizeof (*sock));
  sock->fd = INVALID_SOCKET;
  sock->buffer = sock->proxy.ivs = sock->crypt.ivs = nil ();
  return sock;
}

int socket_create (simsocket sock, int fd) {
  int err = sock->err = SIM_OK, size;
  sock->fd = fd;
  sock->rcvtimeout = param_get_number ("socket.recv");
  sock->sndtimeout = param_get_number ("socket.send");
  sock->maxmb = param_get_number ("socket.maxmb");
  if ((size = param_get_number ("socket.tcp.recv")) > 0)
    socket_set_buffer (sock, size, true);
  if ((size = param_get_number ("socket.tcp.send")) > 0)
    socket_set_buffer (sock, size, false);
  if (! pth_mutex_init (sock->lock = sim_new (sizeof (pth_mutex_t)))) {
    err = errno;
    LOG_ERROR_ ("pth_mutex_init $%d error %d\n", fd, err);
    sim_free (sock->lock, sizeof (pth_mutex_t)), sock->lock = NULL;
  } else
    sock->bufferlen = 0;
  return err;
}

int socket_open (simsocket sock, simclient client) {
  int err, fd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
  socket_new (sock);
  if (! socket_table.ptr)
    return SIM_API_INIT;
  if (fd == INVALID_SOCKET) {
    err = socket_get_errno ();
    LOG_ERROR_ ("open error %d\n", err);
#ifndef _WIN32
    LOG_CODE_ERROR_ ((pth_log_threads (SIM_MODULE, SIM_LOG_ERROR), limit_log_sockets (SIM_MODULE, SIM_LOG_ERROR)));
#endif
    return err ? err : EINVAL;
  }
  if ((err = sim_socket_set_nonblock (fd, true)) == SIM_OK) {
    if (client)
      sock->flags = client == SOCKET_NO_SESSION ? SOCKET_FLAG_NO_SESSION : SOCKET_FLAG_CLIENT;
    if ((sock->client = client == SOCKET_NO_SESSION ? NULL : client) != NULL) {
      LOG_DEBUG_ ("open $%d '%s'\n", fd, sock->client->contact->nick.str);
    } else
      LOG_DEBUG_ ("open $%d\n", fd);
    if ((err = socket_create (sock, fd)) != SIM_OK) {
      close_socket (fd);
      sock->fd = INVALID_SOCKET;
      sock->client = NULL;
    } else if (SOCKET_ADD (fd).typ != SIMNIL)
      LOG_ERROR_ ("zombie socket $%d\n", fd);
  } else
    close_socket (fd);
  return err;
}

int socket_reopen (simsocket sock, simclient client, simcontact contact, int error) {
  if (sock->err == SIM_OK) {
    int flags = sock->flags;
    simnumber created = sock->created;
    sock->created = 0;
    socket_close (sock, contact);
    error = socket_open (sock, client);
    sock->flags |= flags & SOCKET_FLAG_CONNECT;
    sock->created = created;
  }
  return error;
}

int socket_get_name (simsocket sock, unsigned *ip, int *port, simbool peer) {
  int err;
  struct sockaddr_in sin;
  socklen_t len = sizeof (sin);
  memset (&sin, 0, sizeof (sin));
  err = peer ? getpeername (sock->fd, (struct sockaddr *) &sin, &len) :
               getsockname (sock->fd, (struct sockaddr *) &sin, &len);
  if (err) {
    err = socket_get_errno ();
    LOG_WARN_ ("%s $%d error %d\n", peer ? "getpeername" : "getsockname", sock->fd, err);
    memset (&sin, 0, sizeof (sin)); /* just in case */
    if (! err)
      err = EINVAL;
  }
  if (ip)
    *ip = ntohl (sin.sin_addr.s_addr);
  if (port)
    *port = ntohs (sin.sin_port);
  return err;
}

int socket_get_lock (simsocket sock) {
  if (sock) {
    LOG_XTRA_ ("RELOCK $%d -> $%d\n", socket_nat_sock->fd, sock->fd);
    socket_nat_sock = sock;
  }
  return socket_nat_sock ? socket_nat_sock->fd : socket_nat_count ? -socket_nat_count : SOCKET_NOT_LOCKED;
}

simnumber socket_get_stat (simsocket sock, unsigned j, simbool oldstat) {
  switch (j) {
    case CONTACT_STAT_RECEIVED:
      return oldstat ? SOCKET_GET_STAT_RECEIVED (&sock->old) : SOCKET_GET_STAT_RECEIVED (sock);
    case CONTACT_STAT_SENT:
      return oldstat ? SOCKET_GET_STAT_SENT (&sock->old) : SOCKET_GET_STAT_SENT (sock);
    case CONTACT_STAT_COUNT:
      return sock->created != 0;
    case CONTACT_STAT_DURATION:
      if (sock->created)
        return system_get_tick () - sock->created;
  }
  return 0;
}

static void socket_set_stat (simsocket sock, simcontact contact, int linger) {
  static const char *socket_linger_names[] = { " NO-UNLOCK", "", " NO-LINGER", " LINGER" };
  simsocket local = SOCKET_GET_STAT_SOCKET (sock);
  unsigned i, j = CONTACT_STATS_INPUT;
  if (contact != SOCKET_NO_SESSION && sock->client != SOCKET_NO_SESSION) {
    simbool old = sock->old.rcvbytes || sock->old.rcvheaders || sock->old.sndbytes || sock->old.sndheaders;
    if (! contact) {
#ifndef DONOT_DEFINE
      if (old)
        LOG_ERROR_ ("%s $%d [%llu+%llu : %llu+%llu]\n", sock->client ? "CLOSE2" : "close2", sock->fd,
                    sock->old.rcvbytes, sock->old.rcvheaders, sock->old.sndbytes, sock->old.sndheaders);
#endif
      old = false;
    } else if (contact == SOCKET_PROXY_SESSION) {
      contact = SOCKET_NO_SESSION;
      old = true;
    }
    if (old) {
      for (i = 0; i <= CONTACT_STAT_COUNT; i++)
        if (contact == SOCKET_NO_SESSION) {
          simnumber n = socket_get_stat (sock, i, false);
          proxy_set_stat (proxy_get_stat (PROXY_STATS_OUTPUT, i) + n, PROXY_STATS_OUTPUT, i);
        } else if (! (sock->flags & SOCKET_FLAG_LOCAL))
          contact->stats[CONTACT_STATS_OUTPUT][i] += socket_get_stat (sock, i, true);
      if (contact == SOCKET_NO_SESSION) {
        LOG_DEBUG_ ("%s $%d [%llu+%llu : %llu+%llu]\n",
                    socket_get_stat (sock, CONTACT_STAT_COUNT, false) ? "CLOSE2" : "close2", sock->fd,
                    sock->rcvbytes, sock->rcvheaders, sock->sndbytes, sock->sndheaders);
        SOCKET_INIT_STAT (sock);
      } else if (! (sock->flags & SOCKET_FLAG_LOCAL)) {
        LOG_DEBUG_ ("%s $%d [%llu+%llu : %llu+%llu] '%s'\n", sock->client ? "CLOSE2" : "close2", sock->fd,
                    sock->old.rcvbytes, sock->old.rcvheaders, sock->old.sndbytes, sock->old.sndheaders,
                    contact->nick.str);
        SOCKET_INIT_STAT (&sock->old);
      }
      LOG_CODE_ (SIM_MODULE_CONTACT, SIM_LOG_XTRA,
                 contact_log_stats (SIM_MODULE_CONTACT, SIM_LOG_XTRA, "TO",
                                    contact != SOCKET_NO_SESSION ? contact->id : CONTACT_ID_TEST));
    }
  }
  if (contact == SOCKET_NO_SESSION || ! (sock->flags & SOCKET_FLAG_HANDSHAKE)) {
    j = contact == SOCKET_NO_SESSION || sock->flags & SOCKET_FLAG_LOCAL ? CONTACT_STATS_MINE : CONTACT_STATS_SOCKET;
    contact = NULL;
  } else if (sock->client != SOCKET_NO_SESSION)
    j = contact && sock->flags & SOCKET_FLAG_CLIENT ? CONTACT_STATS_CLIENT : CONTACT_STATS_SOCKET;
#ifndef DONOT_DEFINE
  if (contact == contact_list.me && j == CONTACT_STATS_CLIENT) {
    LOG_ERROR_ ("%s $%d [%llu+%llu : %llu+%llu]\n",
                socket_get_stat (sock, CONTACT_STAT_COUNT, false) ? "CLOSE3" : "close3", sock->fd,
                local->rcvbytes, local->rcvheaders, local->sndbytes, local->sndheaders);
    j = CONTACT_STATS_SOCKET;
    contact = NULL;
  }
#endif
  LOG_DEBUG_ ("%s%d $%d:$%d [%llu+%llu : %llu+%llu]%s '%s'\n",
              socket_get_stat (sock, CONTACT_STAT_COUNT, false) ? "CLOSE" : "close", (CONTACT_STATS_MINE + 1 - j) >> 1,
              sock->fd, local->fd, local->rcvbytes, local->rcvheaders, local->sndbytes, local->sndheaders,
              socket_linger_names[(linger > 0 ? 1 : linger) - SOCKET_NO_UNLOCK], contact ? contact->nick.str : NULL);
  if (j != CONTACT_STATS_MINE) {
    for (i = 0; i <= CONTACT_STAT_COUNT; i++)
      if (! contact) {
        unsigned k = j == CONTACT_STATS_SOCKET ? PROXY_STATS_SOCKET : PROXY_STATS_INPUT;
        proxy_set_stat (proxy_get_stat (k, i) + SOCKET_GET_STAT (sock, i), k, i);
      } else
        contact->stats[j][i] += SOCKET_GET_STAT (sock, i);
    SOCKET_INIT_STAT (local);
    LOG_CODE_ (SIM_MODULE_CONTACT, SIM_LOG_XTRA,
               contact_log_stats (SIM_MODULE_CONTACT, SIM_LOG_XTRA, "T", contact ? contact->id : CONTACT_ID_TEST));
  }
}

void socket_set_udp (int fd) {
  system_qos_unset (&socket_udp_sock);
  socket_udp_sock.fd = fd;
  memset (&socket_udp_sock.qos, 0, sizeof (socket_udp_sock.qos));
}

int socket_set_buffer (simsocket sock, int size, simbool receive) {
  socklen_t len = sizeof (size);
  if (size > 0) {
    if (! setsockopt (sock->fd, SOL_SOCKET, receive ? SO_RCVBUF : SO_SNDBUF, (char *) &size, sizeof (size))) {
      LOG_DEBUG_ ("buffer $%d %s %d\n", sock->fd, receive ? "recv" : "send", size);
    } else if (! socket_buffer_error_counts[! receive]++)
      LOG_ERROR_ ("buffer $%d %s %d (error %d)\n", sock->fd, receive ? "recv" : "send", size, socket_get_errno ());
  }
  return getsockopt (sock->fd, SOL_SOCKET, receive ? SO_RCVBUF : SO_SNDBUF, (char *) &size, &len) ? 0 : size;
}

void socket_set_qos (simsocket sock, const void *sin, int tos) {
  if (sock->qos.tos != tos) {
    sock->qos.tos = tos;
    if (! system_qos_set (sock, (void *) sin)) {
      static const int socket_qos_values[] = { 0x00, 0x08, 0x28, 0x2E, 0x38, 0x38 };
      int dscp = socket_qos_values[tos] << 2;
      setsockopt (sock->fd, IPPROTO_IP, IP_TOS, (char *) &dscp, sizeof (dscp));
    }
  }
}

int sim_socket_set_reuse (int fd) {
  int err = SIM_OK;
  static const int one = 1;
  if (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, (const char *) &one, sizeof (one))) {
    err = socket_get_errno ();
    LOG_ERROR_ ("setsockopt $%d error %d\n", fd, err);
    return err ? err : EINVAL;
  }
#ifdef SO_REUSEPORT
  if (setsockopt (fd, SOL_SOCKET, SO_REUSEPORT, (const char *) &one, sizeof (one)))
    LOG_WARN_ ("setsockopt $%d SO_REUSEPORT error %d\n", fd, socket_get_errno ());
#endif
  return err;
}

int sim_socket_set_nonblock (int fd, unsigned long nonblock) {
  int err;
#ifndef _WIN32
  int oldmode = fcntl (fd, F_GETFL, NULL);
  if (oldmode >= 0) {
    if (nonblock) {
      if (fcntl (fd, F_SETFL, oldmode | O_NONBLOCK) < 0)
        oldmode = -1;
    } else if (fcntl (fd, F_SETFL, oldmode & ~O_NONBLOCK) < 0)
      oldmode = -1;
  }
  if (oldmode >= 0)
#else
  if (! ioctlsocket (fd, FIONBIO, &nonblock))
#endif
    return SIM_OK;
  err = socket_get_errno ();
  LOG_ERROR_ ("nonblock%ld $%d error %d\n", nonblock, fd, err);
  return err ? err : EINVAL;
}

void socket_destroy (simsocket sock, simcontact contact, int linger) {
  if (sock == socket_nat_sock && linger != SOCKET_NO_UNLOCK) {
    socket_nat_sock = NULL;
    LOG_XTRA_ ("close $%d: UNLOCK\n", sock->fd);
  }
  if (sock->lock) {
    pth_mutex_destroy (sock->lock);
    sim_free (sock->lock, sizeof (pth_mutex_t)), sock->lock = NULL;
  }
  string_free (sock->buffer), sock->buffer = nil ();
  if (sock->fd != INVALID_SOCKET) {
    simcustomer local = socket_check_server (sock);
    if (local) {
      local->sock->flags = sock->flags;
      proxy_customer_set_contact (local, contact);
    }
    socket_set_stat (sock, contact, linger);
    if (linger >= SOCKET_NO_LINGER) {
      struct linger li;
      li.l_onoff = 1;
      li.l_linger = linger;
      if (setsockopt (sock->fd, SOL_SOCKET, SO_LINGER, (char *) &li, sizeof (li)))
        LOG_ERROR_ ("setsockopt $%d SO_LINGER error %d\n", sock->fd, socket_get_errno ());
    }
    system_qos_unset (sock);
    SOCKET_DELETE (sock->fd);
    if (close_socket (sock->fd)) {
#ifdef __FreeBSD__
      if (socket_get_errno () == ECONNRESET) {
        LOG_WARN_ ("close $%d error %d\n", sock->fd, socket_get_errno ());
      } else
#endif
        LOG_ERROR_ ("close $%d error %d\n", sock->fd, socket_get_errno ());
    }
    sock->fd = INVALID_SOCKET;
    ssl_free (sock);
    crypt_close_socket (sock, false);
    crypt_close_socket (sock, true);
  }
  proxy_customer_release (sock->local, SIM_OK), sock->local = NULL;
}

int socket_close_lock_ (simsocket sock, simcontact contact) {
  void *lock = sock->lock;
  int err = lock ? socket_lock_ (sock, -1) : SIM_SOCKET_NO_ERROR;
  if (err == SIM_OK) {
    sock->lock = NULL;
    socket_close (sock, contact);
    sock->lock = lock;
    socket_unlock (sock, sock->fd);
    pth_mutex_destroy (lock);
    sim_free (lock, sizeof (pth_mutex_t));
    sock->lock = NULL;
  }
  socket_close (sock, contact);
  return err;
}

int _socket_lock_ (simsocket sock, int useconds, const char *function) {
  int err = SIM_OK;
  pth_event_t ev = useconds < 0 ? NULL : pth_event (PTH_EVENT_TIME, pth_timeout (0, useconds));
  if ((useconds >= 0 && ! ev) || ! pth_mutex_acquire_ (sock->lock, FALSE, ev)) {
    err = errno ? errno : ENOMEM;
    LOG_ANY_ (ev ? SIM_LOG_WARN : SIM_LOG_ERROR, "pth_mutex_acquire %s:$%d error %d\n", function, sock->fd, errno);
  }
  pth_event_free (ev, PTH_FREE_THIS);
  return err;
}

int _socket_unlock (simsocket sock, const char *function, int fd) {
  int err = SIM_OK;
  if (! pth_mutex_release (sock->lock)) {
    err = errno;
    LOG_ERROR_ ("pth_mutex_release %s:$%d error %d\n", function, fd, err);
  }
  return err;
}

static int sim_socket_bind (int fd, void *sin, unsigned sinlen, int *port) {
  int err = SIM_OK;
  struct sockaddr_in *saddr = sin;
  socklen_t len = sinlen;
  if (bind (fd, sin, sinlen)) {
    err = socket_get_errno ();
    LOG_NOTE_ ("bind $%d error %d\n", fd, err);
    if (! port)
      return err ? err : EINVAL;
    err = SIM_OK;
    saddr->sin_port = htons (0);
    if (bind (fd, sin, sinlen)) {
      err = socket_get_errno ();
      LOG_WARN_ ("bind $%d error %d\n", fd, err);
      return err ? err : EINVAL;
    }
  }
  if (port && ! getsockname (fd, sin, &len))
    *port = ntohs (saddr->sin_port);
  return err;
}

int socket_listen_port (int fd, int backlog, unsigned ip, int port, int *newport) {
  int err;
#ifndef _WIN32
  static const int one = 1;
#endif
  struct sockaddr_in sin;
  if (backlog >= 0) {
    LOG_DEBUG_ ("listen $%d %d %s:%d\n", fd, backlog, network_convert_ip (ip), port);
  } else
    LOG_DEBUG_ ("bind $%d %s:%d\n", fd, network_convert_ip (ip), port);
  if (port < 0 || port >= 0x10000)
    return SIM_SOCKET_BAD_PORT;
#ifndef _WIN32
  if (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, (const char *) &one, sizeof (one))) {
    err = socket_get_errno ();
    LOG_ERROR_ ("setsockopt $%d error %d\n", fd, err);
    return err ? err : EPERM;
  }
#endif
  memset (&sin, 0, sizeof (sin));
  sin.sin_family = AF_INET, sin.sin_addr.s_addr = htonl (ip), sin.sin_port = htons (port);
  if ((err = sim_socket_bind (fd, &sin, sizeof (sin), newport)) != SIM_OK || backlog < 0 || ! listen (fd, backlog))
    return err;
  err = socket_get_errno ();
  LOG_WARN_ ("listen $%d error %d\n", fd, err);
  return err ? err : EACCES;
}

int socket_listen_tcp (simsocket sock, unsigned ip, int port, int *newport) {
  int err = socket_open (sock, NULL);
  if (err == SIM_OK)
    if ((err = socket_listen_port (sock->fd, param_get_number ("socket.listen"), ip, port, newport)) != SIM_OK)
      socket_close (sock, NULL);
  return err;
}

int socket_listen_udp (unsigned ip, int port, int *fd, int *newport) {
  int err;
  if ((*fd = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) {
    err = socket_get_errno ();
    LOG_ERROR_ ("open udp error %d\n", err);
    return err ? err : EINVAL;
  }
  if ((err = sim_socket_set_nonblock (*fd, true)) != SIM_OK) {
    close_socket (*fd);
  } else if ((err = socket_listen_port (*fd, -1, ip, port, newport)) != SIM_OK)
    close_socket (*fd);
  return err;
}

int socket_accept (simsocket sock, simsocket newsock, unsigned *ip, int *port) {
  int err, fd;
  struct sockaddr_in sin;
  socklen_t len = sizeof (sin);
  memset (&sin, 0, sizeof (sin));
  LOG_DEBUG_ ("accept $%d\n", sock->fd);
  socket_new (newsock);
  if ((fd = accept (sock->fd, (struct sockaddr *) &sin, &len)) == INVALID_SOCKET) {
    err = socket_get_errno ();
  } else if ((err = sim_socket_set_nonblock (fd, true)) == SIM_OK) {
    *ip = ntohl (sin.sin_addr.s_addr), *port = ntohs (sin.sin_port);
    LOG_DEBUG_ ("accepted $%d -> $%d from %s:%d\n", sock->fd, fd, inet_ntoa (sin.sin_addr), *port);
    if ((err = socket_create (newsock, fd)) == SIM_OK)
      newsock->created = system_get_tick ();
    if (SOCKET_ADD (fd).typ != SIMNIL)
      LOG_ERROR_ ("zombie socket $%d\n", fd);
    newsock->rcvheaders = (newsock->sndheaders = SOCKET_HEADER_SYN + SOCKET_HEADER_FIN) + SOCKET_HEADER_TCP;
    return err;
  } else
    close_socket (fd);
  if (err == EINTR || err == EAGAIN || err == EWOULDBLOCK || err == ECONNRESET || err == ECONNABORTED) {
    LOG_WARN_ ("accept $%d error %d\n", sock->fd, err);
    err = SIM_SOCKET_ACCEPT;
  } else {
    LOG_ERROR_ ("accept $%d error %d\n", sock->fd, err);
#ifndef _WIN32
    LOG_CODE_ERROR_ ((pth_log_threads (SIM_MODULE, SIM_LOG_ERROR), limit_log_sockets (SIM_MODULE, SIM_LOG_ERROR)));
#endif
  }
  return err ? err : EACCES;
}

#ifndef _WIN32
int socket_select_pipe_ (int fd, int pipefd, int mseconds) {
  int ret;
#if ! HAVE_LIBPTH && ! defined(_WIN32)
  struct pollfd fds[2];
  fds[0].fd = fd;
  fds[0].events = POLLIN;
  fds[1].fd = pipefd;
  fds[1].events = POLLIN;
  ret = pth_poll_ (fds, 2, mseconds);
  return ret <= 0 ? ret : ret == 2 ? 3 : fds[0].revents & (POLLIN | POLLHUP | POLLERR) ? 1 : 2;
#else
  fd_set set;
  struct timeval tv;
  FD_ZERO (&set);
  FD_SET (fd, &set);
  FD_SET (pipefd, &set);
  tv.tv_sec = mseconds / 1000, tv.tv_usec = (mseconds % 1000) * 1000;
  ret = pth_select_ (1 + (fd > pipefd ? fd : pipefd), &set, NULL, NULL, mseconds >= 0 ? &tv : NULL);
  return ret <= 0 ? ret : ret == 2 ? 3 : FD_ISSET (fd, &set) ? 1 : 2;
#endif
}
#endif

int socket_select_readable_ (int fd, int seconds, int mseconds) {
#if ! HAVE_LIBPTH && ! defined(_WIN32)
  struct pollfd fds;
  fds.fd = fd;
  fds.events = POLLIN;
  return pth_poll_ (&fds, 1, seconds * 1000 + mseconds);
#else
  fd_set set;
  struct timeval tv;
  FD_ZERO (&set);
  FD_SET (fd, &set);
  tv.tv_sec = seconds, tv.tv_usec = mseconds * 1000;
  return pth_select_ (fd + 1, &set, NULL, NULL, &tv);
#endif
}

int socket_select_writable_ (int fd, int seconds, int mseconds) {
#if ! HAVE_LIBPTH && ! defined(_WIN32)
  struct pollfd fds;
  fds.fd = fd;
  fds.events = POLLOUT;
  if (seconds || mseconds)
    return pth_poll_ (&fds, 1, seconds * 1000 + mseconds);
  return poll (&fds, 1, seconds * 1000 + mseconds);
#else
  fd_set set;
  struct timeval tv;
  FD_ZERO (&set);
  FD_SET (fd, &set);
  tv.tv_sec = seconds, tv.tv_usec = mseconds * 1000;
  return seconds || mseconds ? pth_select_ (fd + 1, NULL, &set, NULL, &tv) : select (fd + 1, NULL, &set, NULL, &tv);
#endif
}

static simbool socket_callback_write (void *ctxt, simtype input) {
  struct socket_context *ctx = ctxt;
  sim_crypt_encrypt (ctx->crypto->encrypt, input, &ctx->buffer[ctx->length]);
  ctx->length += input.len;
  return true;
}

static simbool socket_callback_read (void *ctxt, simtype output) {
  struct socket_context *ctx = ctxt;
  simtype buf = pointer_new_len (&ctx->buffer[ctx->length], output.len);
  if (ctx->length + output.len > ctx->size)
    return false;
  if (! output.str) {
    if (output.len > sizeof (socket_decrypt_buffer))
      return false;
    sim_crypt_decrypt (ctx->crypto->decrypt, buf, socket_decrypt_buffer);
    memset (socket_decrypt_buffer, 0, output.len);
  } else
    sim_crypt_decrypt (ctx->crypto->decrypt, buf, output.str);
  ctx->length += output.len;
  return true;
}

static int socket_recv_ (simsocket sock, int bits, simtype output, unsigned *length) {
  int err;
  simnumber now = system_get_tick (), tick = now;
  if (sock->client && (sock->rcvbytes + sock->sndbytes) >> 20 >= sock->maxmb) {
    LOG_DEBUG_ ("exceeded $%d (%lld:%lld bytes)\n", sock->fd, sock->rcvbytes, sock->sndbytes);
    return SIM_SOCKET_EXCEEDED;
  }
  while (sock->err == SIM_OK) {
    if (sock->client && sock->client->flags & CLIENT_FLAG_CONNECTED)
      client_periodic (sock->client, now);
    if ((err = socket_select_readable_ (sock->fd, 1, 0)) > 0) {
      *length = sock->err == SIM_OK ? recv (sock->fd, output.ptr, output.len, 0) : 0;
      return (int) *length < 0 ? socket_get_errno () : sock->err;
    }
    *length = 0;
    if (err)
      return sock->err == SIM_OK ? socket_get_errno () : sock->err;
    if ((now = system_get_tick ()) - tick >= sock->rcvtimeout * (bits & SOCKET_RECV_SSL ? 2000 : 1000))
      break;
  }
  if (sock->err == SIM_OK)
    return SIM_SOCKET_RECV_TIMEOUT;
  return sock->err == SIM_SOCKET_RECV_TIMEOUT ? SIM_SOCKET_RECV_ERROR : sock->err;
}

int socket_recv_some_ (simsocket sock, int bits, simtype output, unsigned *length) {
  int err;
  /*LOG_XTRA_ ("recv $%d [%llu+%llu : %llu+%llu]\n", sock->fd,
               sock->rcvbytes, sock->rcvheaders, sock->sndbytes, sock->sndheaders);*/
  do {
    err = socket_recv_ (sock, bits, output, length);
  } while (err == EAGAIN || err == EWOULDBLOCK || err == EINTR);
  if (err == SIM_OK) {
    if ((int) *length < 0)
      return SIM_SOCKET_RECV_ERROR;
    sock->rcvbytes += *length;
  }
  return err;
}

int _socket_recv_all_ (simsocket sock, unsigned *headers, int bits, simtype output, const char *file, unsigned line) {
  int err;
  unsigned len;
  if (! (bits & SOCKET_RECV_PROXY) && socket_check_client (sock))
    for (;;) {
      if (output.len < (len = sock->bufferlen)) {
        memcpy (output.str, sock->buffer.str + sock->buffer.len - sock->bufferlen, output.len);
        sock->bufferlen = len - output.len;
        return SIM_OK;
      }
      memcpy (output.str, sock->buffer.str + sock->buffer.len - sock->bufferlen, len);
      string_free (sock->buffer), sock->buffer = nil ();
      sock->bufferlen = 0;
      output.str += len;
      if (! (output.len -= len))
        return SIM_OK;
      err = _socket_recv_proxy_ (sock, bits | SOCKET_RECV_PROXY, &sock->buffer, file, line);
      sock->bufferlen = sock->buffer.len;
      if (err != SIM_OK) {
        string_free (sock->buffer), sock->buffer = nil ();
        return err;
      }
    }
  for (len = 0; output.len; output.len -= len) {
    if ((err = socket_recv_some_ (sock, bits, output, &len)) != SIM_OK || ! len)
      return err == SIM_OK ? SIM_SOCKET_END : err;
    if (headers)
      *headers += SOCKET_CALC_OVERHEAD_TCP (len);
    output.str += len;
  }
  return SIM_OK;
}

int _socket_recv_ssl_ (simsocket sock, int bits, simtype *output, unsigned *headers, const char *file, unsigned line) {
  int err;
  unsigned hdrs = 0, len;
  struct _socket_crypt *crypto = bits & SOCKET_RECV_PROXY ? &sock->proxy : &sock->crypt;
  simbyte header[SOCKET_OVERHEAD_SSL];
  *output = nil ();
  if (headers)
    *headers = 0;
  LOG_XTRA_ ("recv $%d %s:%u %s%c%d\n", sock->fd, file, line, bits & SOCKET_RECV_PROXY ? "SEQ" : "seq",
             (crypto->decryptseq + 2) >> 62 & 1 ? '-' : '+', (int) crypto->decryptseq + 2);
  if ((err = _socket_recv_all_ (sock, &hdrs, bits, pointer_new_len (header, sizeof (header)), file, line)) == SIM_OK) {
    simbool ok = header[0] == 0x17 && header[1] == 3 && header[2] == 3;
    err = SIM_SOCKET_BAD_PACKET;
    if (ok && (len = header[3] << 8 | header[4]) != 0 && len <= SIM_MAX_PACKET_SIZE) {
      err = _socket_recv_all_ (sock, &hdrs, bits | SOCKET_RECV_SSL, *output = string_new (len), file, line);
      if (err == SIM_SOCKET_RECV_TIMEOUT) {
        err = SIM_SOCKET_RECV_FAILED;
      } else if (err == SIM_OK && (bits & SOCKET_RECV_PROXY || ! socket_check_client (sock))) {
        if (SOCKET_CALC_OVERHEAD_TCP (len) + SOCKET_HEADER_TCP == hdrs)
          hdrs = SOCKET_CALC_OVERHEAD_TCP (len + SOCKET_OVERHEAD_SSL);
        sock->rcvheaders += hdrs, sock->sndheaders += hdrs;
        if (headers)
          *headers = hdrs;
      }
    } else
      LOG_WARN_SIMTYPE_ (pointer_new_len (header, sizeof (header)), LOG_BIT_BIN, "bad header $%d ", sock->fd);
  }
  if (err != SIM_OK && err != SIM_SOCKET_RECV_TIMEOUT)
    LOG_ANY_ (sock->err == SIM_OK ? SIM_LOG_NOTE : SIM_LOG_INFO, "recv $%d error %d\n", sock->fd, err);
  return err;
}

int _socket_recv_proxy_ (simsocket sock, int bits, simtype *output, const char *file, unsigned line) {
  int err;
  unsigned hdrs;
  simtype table, buf;
  *output = nil ();
next:
  table = nil ();
  if ((err = _socket_recv_ssl_ (sock, bits | SOCKET_RECV_PROXY, &buf, &hdrs, file, line)) == SIM_OK) {
    struct _socket_crypt *crypto = bits & SOCKET_RECV_PROXY ? &sock->proxy : &sock->crypt;
    simnumber seq = crypto->decryptseq += 2;
    if ((err = _socket_recv_table (sock, proxy_proto_client, nil (), seq, buf, bits, &table, file, line)) != SIM_OK)
      LOG_DEBUG_ ("recv $%d error %d\n", sock->fd, err);
  }
  string_free (buf);
  if (err == SIM_OK) {
    if (table_get_string (table, SIM_CLIENT_CMD).typ != SIMNIL) {
      if (bits & SOCKET_RECV_PROXY) {
        sock->rcvbytes -= buf.len + SOCKET_OVERHEAD_SSL, sock->rcvheaders -= hdrs, sock->sndheaders -= hdrs;
        sock->old.rcvbytes += buf.len + SOCKET_OVERHEAD_SSL, sock->old.rcvheaders += hdrs, sock->old.sndheaders += hdrs;
      }
      err = proxy_recv_cmd_ (sock, table, (bits & SOCKET_RECV_PROXY) != 0);
      if (err == SIM_OK && bits & SOCKET_RECV_PROXY) {
        table_free (table);
        goto next;
      }
    } else if (! (*output = table_detach_string (table, SIM_CLIENT_DATA)).len) {
      string_free (*output), *output = nil ();
      LOG_SIMTYPE_ (SIM_MODULE_TABLE, SIM_LOG_WARN, table, 0, "bad data $%d %s:%u ", sock->fd, file, line);
      err = SIM_SOCKET_BAD_PACKET;
    }
  }
  table_free (table);
  return err;
}

int _socket_recv_table (simsocket sock, const simtype proto, const simtype proto2, simnumber sequence,
                        const simtype input, int bits, simtype *table, const char *file, unsigned line) {
  struct socket_context ctx;
  simbool measure = sock->flags & SOCKET_FLAG_CPU || table_get_table_type (proto) & SIMTABLE_LONG;
  simunsigned cpu = measure ? sim_system_cpu_get (SYSTEM_CPU_TIME_CYCLES, NULL) : 0;
  struct _socket_crypt *crypto = ctx.crypto = bits & SOCKET_RECV_PROXY ? &sock->proxy : &sock->crypt;
  int err = SIM_OK;
  unsigned blocksize = sim_crypt_auth_size (crypto->decrypt), bit = 0;
  simtype str, ptr;
  if (blocksize < crypto->decryptsize)
    blocksize = crypto->decryptsize;
  ctx.size = input.len - blocksize;
  ctx.buffer = input.str;
  ptr = pointer_new_len (&input.str[blocksize], input.len - blocksize * 2);
  *table = nil ();
  if (input.len <= (ctx.length = blocksize) + blocksize) {
    err = SIM_SOCKET_BAD_PACKET;
  } else if (! sim_crypt_auth_check (crypto->decrypt, ptr, pointer_new_len (input.str, blocksize),
                                     sequence, &input.str[input.len - blocksize])) {
    err = SIM_CRYPT_BAD_PACKET;
  } else {
    simbyte buf[2];
    if (! (table_get_table_type (proto) & SIMTABLE_LONG)) {
      *table = _sim_table_read (proto, proto2, socket_callback_read, &ctx, file, line);
    } else if (socket_callback_read (&ctx, pointer_new_len (&buf[0], sizeof (buf[0])))) {
      if (! buf[0]) {
        *table = _sim_table_read (proto, proto2, socket_callback_read, &ctx, file, line);
      } else if (socket_callback_read (&ctx, pointer_new_len (&buf[1], sizeof (buf[1])))) {
        socket_callback_read (&ctx, str = string_new (ctx.size - ctx.length));
        table_add (*table = table_new (2), SIM_SERVER_DATA, str);
        table_add_number (*table, SIM_SERVER_DATA_NUMBER, buf[0] << 8 | buf[1]);
        table_add_number (*table, SIM_SERVER_DATA_CYCLES, cpu);
        bit = LOG_BIT_BIN;
      }
    }
    if (table->typ == SIMNIL)
      err = SIM_CRYPT_BAD_TABLE;
  }
  if (table->typ != SIMNIL) {
    ctx.length -= blocksize;
  } else if (err != SIM_SOCKET_BAD_PACKET && line && LOG_CHECK_ANY_LEVEL_ (SIM_MODULE_CRYPTO, SIM_LOG_WARN)) {
    simbyte *tmp = socket_decrypt_buffer;
    sim_crypt_auth_start (crypto->decrypt, input.len - blocksize * 2, input.str, blocksize, sequence);
    sim_crypt_decrypt (crypto->decrypt, ptr, tmp);
    ptr = pointer_new_len (tmp, input.len - blocksize * 2);
    LOG_SIMTYPE_ (SIM_MODULE_CRYPTO, SIM_LOG_WARN, ptr, LOG_BIT_BIN, "bad %s $%d %s%c%d ",
                  err == SIM_CRYPT_BAD_PACKET ? "packet" : "table", sock->fd,
                  bits & SOCKET_RECV_PROXY ? "SEQ" : "seq", sequence & 1 ? '-' : '+', (int) sequence);
    sim_crypt_auth_stop (crypto->decrypt, socket_decrypt_buffer);
    memset (socket_decrypt_buffer, 0, input.len - blocksize * 2);
  } else if (line)
    LOG_CODE_ANY_ (SIM_MODULE_CRYPTO, SIM_LOG_WARN, "bad packet $%d %s%c%d length %d >= %d\n", sock->fd,
                   bits & SOCKET_RECV_PROXY ? "SEQ" : "seq", sequence & 1 ? '-' : '+', (int) sequence,
                   ctx.length, input.len - blocksize);
  if (! (bits & SOCKET_RECV_PROXY) && (sequence >= -1 || sequence <= -1048576) && table->typ == SIMTABLE)
    if (sequence <= ((simnumber) 1 << 62) - 1048576 || sequence >= ((simnumber) 1 << 62) - 1) {
      if (LOG_CHECK_ANY_LEVEL_ (bit ? SIM_MODULE_CRYPTO : SIM_MODULE_TABLE, SIM_LOG_XTRA)) {
        int pad = param_get_number ("xfer.padding");
        if (bit ? socket_size_client (sock, pad, &pad) > input.len : ! table_get (*table, SIM_CMD_XFER_DATA_BYTES).len)
          LOG_SIMTYPE_ (bit ? SIM_MODULE_CRYPTO : SIM_MODULE_TABLE, SIM_LOG_XTRA,
                        *table, bit, "recv $%d %s:%u ", sock->fd, file, line);
      }
      LOG_XTRA_ ("received $%d (%d elements, %d/%d bytes)\n", sock->fd,
                 table_count (*table), ctx.length, input.len + SOCKET_OVERHEAD_SSL);
    }
  if (sock->flags & SOCKET_FLAG_CPU)
    limit_set_cpu (cpu, sim_system_cpu_get (SYSTEM_CPU_TIME_CYCLES, NULL));
  return err;
}

int _socket_recv_table_ (simsocket sock, const simtype proto, const simtype proto2,
                         simtype *table, unsigned *bytes, unsigned *headers, const char *file, unsigned line) {
  int err;
  simtype buf;
  *table = nil ();
  if ((err = _socket_recv_ssl_ (sock, SOCKET_RECV_TCP, &buf, headers, file, line)) == SIM_OK) {
    simnumber seq = sock->crypt.decryptseq += 2;
    if ((err = _socket_recv_table (sock, proto, proto2, seq, buf, SOCKET_RECV_TCP, table, file, line)) != SIM_OK) {
      LOG_WARN_ ("recv $%d error %d\n", sock->fd, err);
    } else if (bytes)
      *bytes = buf.len + SOCKET_OVERHEAD_SSL;
  }
  string_free (buf);
  return err;
}

static int socket_send_some_ (simsocket sock, const simtype input, unsigned *length) {
  int err = SIM_SOCKET_SEND_TIMEOUT, timeout = 0, selecttimeout = ! (sock->flags & SOCKET_FLAG_NO_BLOCK);
  if (sock->client && (sock->rcvbytes + sock->sndbytes) >> 20 >= sock->maxmb) {
    LOG_DEBUG_ ("exceeded $%d (%lld:%lld bytes)\n", sock->fd, sock->rcvbytes, sock->sndbytes);
    return SIM_SOCKET_EXCEEDED;
  }
  while (++timeout <= sock->sndtimeout && sock->err == SIM_OK) {
    if ((err = socket_select_writable_ (sock->fd, selecttimeout, 0)) > 0) {
      *length = sock->err == SIM_OK ? send (sock->fd, input.ptr, input.len, 0) : 0;
      if ((int) *length >= 0)
        return sock->err;
      err = socket_get_errno ();
      break;
    }
    *length = 0;
    if (err)
      return sock->err == SIM_OK ? socket_get_errno () : sock->err;
    LOG_XTRA_ ("send $%d timeout%d (%d/%d seconds)\n", sock->fd, selecttimeout, timeout, sock->sndtimeout);
    err = SIM_SOCKET_SEND_TIMEOUT;
    if (! selecttimeout)
      return sock->err != SIM_OK ? sock->err : err;
  }
  return sock->err != SIM_OK ? sock->err : sock->client ? err : socket_cancel (sock, err);
}

int socket_send_all_ (simsocket sock, const simbyte *input, unsigned length) {
  int err;
  unsigned len;
  sock->sndbytes += length;
  /*LOG_XTRA_ ("send $%d [%llu+%llu : %llu+%llu]\n", sock->fd,
               sock->rcvbytes, sock->rcvheaders, sock->sndbytes, sock->sndheaders);*/
  for (len = 0; length; length -= len) {
    do {
      if ((err = socket_send_some_ (sock, pointer_new_len (input, length), &len)) == SIM_OK && len != length) {
        LOG_XTRA_ ("send $%d (%d/%d bytes)\n", sock->fd, len, length);
        if (sock->flags & SOCKET_FLAG_NO_BLOCK || (int) len < 0)
          err = SIM_SOCKET_BLOCKED;
      }
    } while (err == EAGAIN || err == EWOULDBLOCK || err == EINTR);
    if (err != SIM_OK || ! len)
      return err == SIM_OK ? SIM_SOCKET_END : err;
    input += len;
  }
  return SIM_OK;
}

int _socket_send_proxy_ (simsocket sock, const simtype input, int bits, const char *file, unsigned line) {
  int err, num = sim_get_random (PROXY_NUMBER_MAX + 1 - PROXY_NUMBER_MIN) + PROXY_NUMBER_MIN;
  simtype table = _sim_table_new (1, SIMTABLE_SHORT, file, line);
  table_add_number (table, SIM_CLIENT_DATA_NUMBER, num);
  table_add_pointer_len (table, SIM_CLIENT_DATA, input.str, input.len);
  err = socket_send_table_ (sock, table, bits, NULL);
  table_free (table);
  return err;
}

static int _socket_send_ssl_ (simsocket sock, simtype buffer, int bits, const char *file, unsigned line) {
  buffer.str[0] = 0x17;
  buffer.str[2] = buffer.str[1] = 3;
  buffer.str[3] = (simbyte) (buffer.len >> 8);
  buffer.str[4] = (simbyte) buffer.len;
  buffer.len += SOCKET_OVERHEAD_SSL;
  if (bits & SOCKET_SEND_PROXY || ! socket_check_client (sock)) {
    unsigned len = SOCKET_CALC_OVERHEAD_TCP (buffer.len);
    sock->rcvheaders += len, sock->sndheaders += len;
    return socket_send_all_ (sock, buffer.str, buffer.len);
  }
  return _socket_send_proxy_ (sock, buffer, SOCKET_SEND_PROXY, file, line);
}

static int socket_send_table (simsocket sock, const simtype table, simnumber sequence, int bits,
                              struct socket_context *ctxt) {
  simunsigned cpu = (bits | sock->flags) & SOCKET_FLAG_CPU ? sim_system_cpu_get (SYSTEM_CPU_TIME_CYCLES, NULL) : 0;
  struct _socket_crypt *crypto = ctxt->crypto = bits & SOCKET_SEND_PROXY ? &sock->proxy : &sock->crypt;
  int err = SIM_SOCKET_BAD_LENGTH, padding;
  unsigned len, addlen = SOCKET_OVERHEAD_SSL, maxlen = socket_size_max (sock, bits), blocksize;
  simnumber num;
  simtype data = *(simtype *) &table;
  if (data.typ == SIMTABLE && table_get_table_type (data) & SIMTABLE_SHORT) {
    num = table_get_number (data, SIM_SERVER_DATA_NUMBER);
    if (num < PROXY_NUMBER_MIN || num > PROXY_NUMBER_MAX)
      return SIM_SOCKET_NO_ERROR;
    if ((data = table_get_string (data, SIM_SERVER_DATA)).typ == SIMNIL)
      return SIM_SOCKET_NO_ERROR;
    len = data.len + 2;
    padding = bits & SOCKET_SEND_PAD ? crypto->padding : 1;
  } else {
    unsigned neg = (num = data.typ == SIMTABLE && table_get_table_type (data) & SIMTABLE_LONG ? -1 : 0) < 0;
    if ((len = type_size (data) + neg) < neg + 4)
      len = 0;
    if (! (bits & (SOCKET_SEND_PROXY | SOCKET_SEND_UDP)) && SOCKET_CHECK_PROXY (sock))
      addlen += SOCKET_OVERHEAD_PROXY;
    padding = bits & SOCKET_SEND_AUDIO ? 1 : crypto->padding;
  }
  if (len > 2 && len <= maxlen) {
    simtype iv = pointer_new_len (crypto->ivs.str, blocksize = sim_crypt_auth_size (crypto->encrypt)), tmp;
    if (blocksize < crypto->encryptsize)
      blocksize = crypto->encryptsize;
    ctxt->size = (len + blocksize * 2 + padding + addlen - 1) / padding * padding - addlen;
    if (ctxt->size > (maxlen += blocksize * 2))
      ctxt->size = maxlen;
    ctxt->buffer = sim_new (ctxt->size += SOCKET_OVERHEAD_SSL);
    tmp = pointer_new_len (ctxt->buffer + SOCKET_OVERHEAD_SSL, blocksize);
    if (! iv.str)
      random_get (random_public, iv = string_new (iv.len));
    do {
      if (iv.typ == SIMPOINTER) {
        memcpy (tmp.str, iv.str + iv.len, tmp.len);
      } else
        random_get (random_public, tmp);
      sim_crypt_auth_encrypt (crypto->encrypt, tmp, iv, 0, iv.str);
    } while (bits & SOCKET_SEND_UDP && SOCKET_CHECK_MAINLINE (tmp.str));
    sim_crypt_auth_start (crypto->encrypt, ctxt->size - SOCKET_OVERHEAD_SSL - tmp.len * 2, tmp.str, tmp.len,
                          ! (bits & SOCKET_SEND_UDP) ? (crypto->encryptseq += 2) : sequence);
    ctxt->length = SOCKET_OVERHEAD_SSL + tmp.len;
    if (num) {
      simbyte buf[2];
      buf[0] = (simbyte) (num > 0 ? (unsigned) num >> 8 : 0);
      buf[1] = (simbyte) num;
      socket_callback_write (ctxt, pointer_new_len (buf, (num > 0) + 1));
    }
    if (num > 0 ? socket_callback_write (ctxt, data) : table_write (data, socket_callback_write, ctxt, 0)) {
      if (ctxt->length < ctxt->size) {
        if (iv.typ == SIMPOINTER) {
          memset (&ctxt->buffer[ctxt->length], 0, ctxt->size - ctxt->length);
        } else
          random_get (random_public, pointer_new_len (&ctxt->buffer[ctxt->length], ctxt->size - ctxt->length));
      }
      if (ctxt->length < ctxt->size - tmp.len) {
        tmp.len = ctxt->size - tmp.len - ctxt->length;
        socket_callback_write (ctxt, pointer_new_len (&ctxt->buffer[ctxt->length], tmp.len));
      }
      err = SIM_OK;
    } else
      err = SIM_SOCKET_NO_ERROR;
    sim_crypt_auth_stop (crypto->encrypt, &ctxt->buffer[ctxt->length]);
    if (err != SIM_OK) /* tried to send something that's not a table */
      sim_free (ctxt->buffer, ctxt->size);
    string_free (iv);
  }
  if (cpu) {
    simcustomer local = socket_check_server (sock);
    if (local)
      local->sock->flags |= SOCKET_FLAG_CPU;
    limit_set_cpu (cpu, sim_system_cpu_get (SYSTEM_CPU_TIME_CYCLES, NULL));
  }
  return err;
}

int socket_send_table_ (simsocket sock, const simtype table, int bits, unsigned *bytes) {
  struct socket_context ctx;
  simclient client = sock->client;
  int err = socket_lock_ (sock, bits == SOCKET_SEND_AUDIO ? socket_max_limit[SOCKET_MAX_WAIT] : -1);
  if (err == SIM_OK) {
    if (client)
      sock = client->sock;
    if (client && client->flags & CLIENT_FLAG_BUFFERING && ! (bits & SOCKET_SEND_PROXY)) {
      LOG_ERROR_SIMTYPE_ (table, 0, "send $%d %s:%u ", sock->fd,
                          (char *) (table.typ == SIMTABLE ? table_get_name (table) : array_get_name (table)),
                          table.typ == SIMTABLE ? table_get_line (table) : array_get_line (table));
    } else if ((err = socket_send_table (sock, table, 0, bits, &ctx)) == SIM_OK) {
      unsigned bit = table_get_table_type (table) & SIMTABLE_SHORT ? LOG_BIT_BIN : 0;
      if (LOG_CHECK_ANY_LEVEL_ (bit ? SIM_MODULE_CRYPTO : SIM_MODULE_TABLE, SIM_LOG_XTRA))
        if (table.typ == SIMTABLE && strcmp (table_get_name (table), "BIO")) {
          int pad = param_get_number ("xfer.padding");
          if (bit ? socket_size_client (sock, pad, &pad) > ctx.size : ! table_get (table, SIM_CMD_XFER_DATA_BYTES).len)
            LOG_SIMTYPE_ (bit ? SIM_MODULE_CRYPTO : SIM_MODULE_TABLE, SIM_LOG_XTRA, table, bit,
                          "send $%d %s:%u ", sock->fd, (char *) table_get_name (table), table_get_line (table));
        }
      if ((err = sock->err) == SIM_OK) {
        if (bits == SOCKET_SEND_AUDIO)
          sock->flags |= SOCKET_FLAG_NO_BLOCK;
        if (bytes)
          *bytes = ctx.size;
        err = _socket_send_ssl_ (sock, pointer_new_len (ctx.buffer, ctx.size - SOCKET_OVERHEAD_SSL), bits,
                                 table.typ == SIMTABLE ? table_get_name (table) : array_get_name (table),
                                 table.typ == SIMTABLE ? table_get_line (table) : array_get_line (table));
        if (bits == SOCKET_SEND_AUDIO) {
          sock->flags &= ~SOCKET_FLAG_NO_BLOCK;
          if (err == SIM_SOCKET_SEND_TIMEOUT)
            err = SIM_OK;
        }
      }
      if ((bits & SOCKET_SEND_PROXY || ! socket_check_client (sock)) && table.typ == SIMTABLE)
        LOG_XTRA_ ("send $%d %s:%u (%d elements, %d/%d bytes) %s%c%d\n", sock->fd,
                   (char *) table_get_name (table), table_get_line (table), table_count (table), table_size (table, 0),
                   ctx.size, bits & SOCKET_SEND_PROXY ? "SEQ" : "seq",
                   ctx.crypto->encryptseq >> 62 & 1 ? '-' : '+', (int) ctx.crypto->encryptseq);
      sim_free (ctx.buffer, ctx.size);
    }
    socket_unlock (sock, sock->fd);
  } else if (bits == SOCKET_SEND_AUDIO)
    err = SIM_OK;
  if (err != SIM_OK)
    LOG_WARN_ ("send $%d error %d\n", sock->fd, err);
  return err;
}

int socket_send (simsocket sock, const simtype table, int bits, unsigned *bytes) {
  int err = SIM_SOCKET_BLOCKED;
  if (pth_mutex_try (sock->lock)) {
    if (socket_select_writable (sock->fd) > 0) {
      sock->flags |= SOCKET_FLAG_NO_BLOCK;
      if ((err = socket_send_table_ (sock, table, bits, bytes)) != SIM_OK && err != SIM_CRYPT_BAD_TABLE && sock->client)
        sock->client->flags |= CLIENT_FLAG_ERROR;
      err = SIM_OK;
      sock->flags &= ~SOCKET_FLAG_NO_BLOCK;
    } else
      LOG_DEBUG_ ("%s:%u $%d busy '%s'\n", (char *) table_get_name (table), table_get_line (table),
                  sock->fd, sock->client ? sock->client->contact->nick.str : NULL);
    socket_unlock (sock, sock->fd);
  } else
    LOG_NOTE_ ("%s:%u $%d busy error %d '%s'\n", (char *) table_get_name (table), table_get_line (table),
               sock->fd, errno, sock->client ? sock->client->contact->nick.str : NULL);
  return err;
}

int socket_send_udp (simsocket sock, const simtype table, simnumber sequence, unsigned ip, int port, int bits,
                     unsigned *bytes) {
  struct socket_context ctx;
  int err = SIM_SOCKET_BAD_PORT;
  if (port > 0 && port < 0x10000) {
    struct _socket_crypt *crypto = bits & SOCKET_SEND_PROXY ? &sock->proxy : &sock->crypt;
    int padding = crypto->padding;
    if (! (bits & SOCKET_SEND_AUDIO))
      crypto->padding = param_get_default_number ("socket.padding", 1);
    err = socket_send_table (sock, table, sequence, bits, &ctx);
    crypto->padding = padding;
  }
  if (err == SIM_OK) {
    simbyte *buf = ctx.buffer + SOCKET_OVERHEAD_SSL;
    unsigned len = ctx.size - SOCKET_OVERHEAD_SSL;
    simsocket local = SOCKET_GET_STAT_SOCKET (sock);
    if ((local->rcvbytes + local->sndbytes) >> 20 < sock->maxmb) {
      struct sockaddr_in sin;
      memset (&sin, 0, sizeof (sin));
      sin.sin_family = AF_INET, sin.sin_addr.s_addr = htonl (ip), sin.sin_port = htons (port);
      socket_set_qos (&socket_udp_sock, &sin, param_get_number ("socket.qos"));
      err = sendto (socket_udp_sock.fd, (char *) buf, len, 0, (struct sockaddr *) &sin, sizeof (sin));
      if (bytes)
        *bytes = len;
      err = err == (int) len ? SIM_OK : err < 0 && (err = socket_get_errno ()) != 0 ? err : SIM_SOCKET_END;
      if (err == SIM_OK) {
        if ((sequence != -1 ? sequence != ((simnumber) 1 << 62) - 1 : bits == SOCKET_SEND_UDP)) {
          if (sequence != -1 || ! (sock->flags & SOCKET_FLAG_LOCAL))
            local->sndbytes += len, local->sndheaders += SOCKET_CALC_OVERHEAD_UDP (len);
        } else if (! (bits & SOCKET_SEND_AUDIO) && ! (sock->flags & SOCKET_FLAG_LOCAL)) {
          simnumber n = proxy_get_stat (PROXY_STATS_OUTPUT, CONTACT_STAT_SENT);
          proxy_set_stat (n + len + SOCKET_CALC_OVERHEAD_UDP (len), PROXY_STATS_OUTPUT, CONTACT_STAT_SENT);
        }
      }
    } else {
      LOG_DEBUG_ ("exceeded $%d (%lld:%lld bytes)\n", sock->fd, sock->rcvbytes, sock->sndbytes);
      err = SIM_SOCKET_EXCEEDED;
    }
    sim_free (ctx.buffer, ctx.size);
  }
  if (err != SIM_OK)
    LOG_NOTE_ ("send $%d error %d\n", sock->fd, err);
  return err;
}

unsigned socket_size_max (simsocket sock, int bits) {
  int max = (bits & SOCKET_SEND_SSL ? SIM_MAX_PACKET_SIZE : sock->crypt.maxsize);
  if (sock->crypt.encrypt) {
    unsigned blocksize = sim_crypt_auth_size (sock->crypt.encrypt);
    max -= (blocksize < sock->crypt.encryptsize ? sock->crypt.encryptsize : blocksize) * 2;
  }
  if (! (bits & (SOCKET_SEND_PROXY | SOCKET_SEND_UDP)) && SOCKET_CHECK_PROXY (sock))
    max -= SOCKET_OVERHEAD_PROXY;
  return max < 0 ? 0 : max;
}

unsigned socket_size_client (simsocket sock, int padding, int *size) {
  int min, max = socket_size_max (sock, SOCKET_SEND_TCP);
  padding = padding >= sock->crypt.padding ? padding - padding % sock->crypt.padding : sock->crypt.padding;
  max = max / 2 - (min = max - sock->crypt.maxsize + padding - SOCKET_OVERHEAD_SSL) - 1;
  *size = (max -= max % padding) + padding;
  return max + min;
}

unsigned socket_size_table (simsocket sock, const simtype table, int bits) {
  struct socket_context ctx;
  struct _socket_crypt *crypto = bits & SOCKET_SEND_PROXY ? &sock->proxy : &sock->crypt;
  simnumber seq = crypto->encryptseq;
  int err = socket_send_table (sock, table, 0, bits, &ctx);
  crypto->encryptseq = seq;
  if (err != SIM_OK)
    return 0;
  sim_free (ctx.buffer, ctx.size);
  return ctx.size;
}

int socket_connect_socks_ (simsocket sock, simsocket lock, unsigned *ip, int port,
                           const char *host, int command, simbool local) {
  simbool localip = false;
  simnumber tick = system_get_tick (), now = tick;
  int err, timeout = param_get_number ("socket.connect") * 1000, socks = param_get_number ("net.tor.port");
  unsigned socketip = sim_network_parse_ip (param_get_pointer ("socket.ip"));
  struct sockaddr_in sin;
  LOG_DEBUG_ ("connect $%d -> %s:%d\n", sock->fd, host ? host : network_convert_ip (*ip), port);
  if (port <= 0 || port >= 0x10000)
    return sock->err == SIM_OK ? SIM_SOCKET_BAD_PORT : sock->err;
  /* bind to the set network interface unless already bound to another one */
  if (socketip) {
    socklen_t len = sizeof (sin);
    memset (&sin, 0, sizeof (sin));
    if (getsockname (sock->fd, (struct sockaddr *) &sin, &len) || ! sin.sin_addr.s_addr) {
      sin.sin_addr.s_addr = htonl (socketip);
      if (sim_socket_bind (sock->fd, &sin, sizeof (sin), NULL) != SIM_OK) {
        LOG_WARN_ ("network interface unknown %s\n", inet_ntoa (sin.sin_addr));
        sin.sin_addr.s_addr = htonl (0);
        if ((err = sim_socket_bind (sock->fd, &sin, sizeof (sin), NULL)) != SIM_OK) {
          LOG_ERROR_ ("connect $%d error %d\n", sock->fd, err);
          return err;
        }
      }
    }
  }
  err = sock->err;
  /* grab the connect lock if required */
  if (param_get_number ("nat.lock")) {
    if ((socket_nat_sock || socket_nat_count) && socket_nat_sock != lock) {
      int fd = socket_nat_sock ? socket_nat_sock->fd : -socket_nat_count;
      LOG_NOTE_ ("connect $%d: WAIT $%d START\n", sock->fd, fd);
      do {
        pth_usleep_ (100000);
      } while ((socket_nat_sock || (lock && socket_nat_count)) && sock->err == SIM_OK);
      tick = now;
      now = system_get_tick ();
      if (sock->err != SIM_OK) {
        err = sock->err;
        LOG_NOTE_ ("connect $%d: WAIT $%d CANCEL (error %d)\n", sock->fd, fd, err);
      } else if (lock) {
        socket_nat_sock = lock;
        LOG_NOTE_ ("connect $%d: WAIT $%d STOP %d (%lld ms)\n", sock->fd, fd, lock->fd, now - tick);
      } else
        LOG_NOTE_ ("connect $%d: WAIT $%d STOP (%lld ms)\n", sock->fd, fd, now - tick);
    } else if (socket_nat_sock != lock) {
      socket_nat_sock = lock;
      LOG_XTRA_ ("connect $%d: LOCK $%d\n", sock->fd, lock->fd);
    } else if (lock)
      LOG_XTRA_ ("connect $%d: RELOCK $%d\n", sock->fd, lock->fd);
  } else
    socket_nat_sock = NULL;
  socket_nat_count++;
  /* resolve hostname to ip address (if specified and NOT using socks) */
  if (err == SIM_OK) {
    memset (&sin, 0, sizeof (sin));
    sin.sin_family = AF_INET;
    if (socks <= 0 || local) {
      if (host) {
        simtype ips;
        if ((err = network_dns_resolve_ (sock, timeout, host, port, false, &ips)) == SIM_OK) {
          now = system_get_tick ();
          if (ips.len != 1)
            LOG_NOTE_ ("host %s resolves to %d addresses\n", host, ips.len);
          *ip = (unsigned) ips.arr[1].num;
          LOG_DEBUG_ ("connect $%d -> %s:%d (%lld ms)\n", sock->fd, network_convert_ip (*ip), port, now - tick);
          err = sock->err;
        }
        array_free (ips);
      }
      sin.sin_addr.s_addr = htonl (*ip);
      sin.sin_port = htons (port);
    } else {
      sin.sin_addr.s_addr = htonl (sim_network_parse_ip (param_get_pointer ("net.tor.ip")));
      sin.sin_port = htons (socks);
      LOG_DEBUG_ ("socks $%d at %s:%d\n", sock->fd, inet_ntoa (sin.sin_addr), socks);
    }
  }
  /* initiate connect and wait for it to succeed, fail or time out */
  if (err == SIM_OK && connect (sock->fd, (struct sockaddr *) &sin, sizeof (sin))) {
    if ((err = socket_get_errno ()) == EINPROGRESS) {
      while ((err = sock->err) == SIM_OK) {
#ifdef _WIN32
        fd_set ok, nok;
        struct timeval tv;
        FD_ZERO (&ok);
        FD_SET (sock->fd, &ok);
        FD_ZERO (&nok);
        FD_SET (sock->fd, &nok);
        tv.tv_sec = 1, tv.tv_usec = 0;
        if ((err = pth_select_ (sock->fd + 1, NULL, &ok, &nok, &tv)) > 0) {
          err = FD_ISSET (sock->fd, &nok) ? WSAECONNREFUSED : SIM_OK;
#else
        if ((err = socket_select_writable_ (sock->fd, 1, 0)) > 0) {
          socklen_t len = sizeof (err);
          if (getsockopt (sock->fd, SOL_SOCKET, SO_ERROR, &err, &len) && (err = socket_get_errno ()) == 0)
            err = EINVAL;
#endif
          break;
        }
        if (! err) {
          err = SIM_SOCKET_TIMEOUT;
        } else if ((err = socket_get_errno ()) != EINTR) {
          if (! err)
            err = SIM_SOCKET_TIMEOUT;
          break;
        }
        if (timeout && (simnumber) system_get_tick () - now >= timeout)
          break;
      }
    } else if (! err)
      err = SIM_SOCKET_TIMEOUT;
    if (socks > 0 && err != SIM_OK && sock->err == SIM_OK)
      event_send_name (NULL, SIM_EVENT_ERROR, SIM_EVENT_ERROR_SOCKS, number_new (err));
  }
  /* finalize; includes sending connect (+ resolve) request to socks proxy (and waiting for it to finish) */
  socket_nat_count--;
  if (sock->err != SIM_OK)
    err = sock->err;
  if (err == SIM_OK && (localip = sim_network_check_local (*ip)) == true && socks > 0)
    err = ECONNREFUSED;
  if (err == SIM_OK) {
    if (socks > 0 && ! local) {
      if (! host || param_get_number ("net.dns.alt")) {
        err = network_connect_socks_ (sock, ip, port, host, command);
      } else
        err = ERROR_BASE_CARES - 11; /* ARES_ECONNREFUSED */
    }
    sock->sndbytes = sock->rcvbytes = 0;
    if (err == SIM_OK) {
      if (! ((sock->flags |= SOCKET_FLAG_CONNECT) & SOCKET_FLAG_NO_SESSION))
        sock->created = tick;
      sock->sndheaders = (sock->rcvheaders = SOCKET_HEADER_SYN + SOCKET_HEADER_FIN) + SOCKET_HEADER_TCP;
      LOG_DEBUG_ ("connected $%d -> %s:%d (%lld ms)\n", sock->fd,
                  network_convert_ip (*ip), port, system_get_tick () - now);
      if (! localip && ! client_check_local (*ip, true)) {
        simself.flags |= port == SIM_PROTO_PORT ? SIM_STATUS_FLAG_SSL_OUT : SIM_STATUS_FLAG_TCP_OUT;
        LOG_CODE_DEBUG_ (network_log_flags (SIM_MODULE, SIM_LOG_DEBUG));
      }
    }
  } else
    LOG_DEBUG_ ("connect $%d error %d (%lld ms)\n", sock->fd, err, system_get_tick () - now);
  return err;
}

void socket_log_thread (const char *module, int level, const void *thread) {
  simtype key, val;
  simwalker ctx;
  if (socket_table.ptr && table_walk_first (&ctx, socket_table)) {
    log_any_ (module, level, ":");
    while ((val = table_walk_next_string (&ctx, &key)).typ != SIMNIL)
      if (val.ptr == thread)
        log_any_ (module, level, " $%d", *(int *) key.ptr);
  }
}
